Angular 2 Change Detection - 1
Change Detection (变化检测) 是 Angular 2 中最重要的一个特性。当组件中的数据发生变化的时候,Angular 2 能检测到数据变化并自动刷新视图反映出相应的变化。 在介绍变化检测之前,我们要先介绍一下浏览器中渲染的概念,渲染是将模型映射到视图的过程。模型的值可以是 JavaScript 中的原始数据类型、对象、数组或其他数据对象。然而视图可以是页面中的段落、表单、按钮等其他元素,这些页面元素内部使用 DOM (Document Object Model) 来表示。
为了更好地理解,我们来看一个具体的示例: <h4 id="greeting"></h4> <script> document.getElementById("greeting").innerHTML = "Hello World!"; </script> 这个例子很简单,因为模型不会变化,所以页面只会渲染一次。如果数据模型在运行时会不断变化,那么整个过程将变得复杂。因此为了保证数据与视图的同步,页面将会进行多次渲染。接下来我们来考虑一下以下几个问题:
而变化检测的基本目的就是解决上述问题。在 Angular 2 中当组件内的模型发生变化的时候,组件内的变化检测器就会检测到更新,然后通知视图刷新。因此变化检测器有两个主要的任务:
接下来我们来分析一下什么是变化,变化是怎么产生的。 变化和事件变化是旧模型与新模型之间的区别,换句话说变化产生了一个新的模型。让我们来看一下下面的代码: import { Component } from '@angular/core'; @Component({ selector: 'exe-counter',template: ` <p>当前值:{{ counter }}</p> <button (click)="countUp()"> + </button>` }) export class CounterComponent { counter = 0; countUp() { this.counter++; } } 页面首次渲染完后,计数器的当前值为0。当我们点击 我们继续看下一个例子: import { Component,OnInit } from '@angular/core'; @Component({ selector: 'exe-counter',template: ` <p>当前值:{{ counter }}</p> ` }) export class CounterComponent implements OnInit { counter = 0; ngOnInit() { setInterval(() => { this.counter++; },1000); } } 该组件通过 import { Component,OnInit } from '@angular/core'; import { Http } from '@angular/http'; @Component({ selector: 'exe-counter',template: ` <p>当前值:{{ counter }}</p> ` }) export class CounterComponent implements OnInit { counter = 0; constructor(private http: Http) {} ngOnInit() { this.http.get('/counter-data.json') .map(res => res.json()) .subscribe(data => { this.counter = data.value; }); } } 该组件在进行初始化的时候,会发送一个 现在我们来总结一下,引起模型变化的三类事件源:
这些事件源有一个共同的特性,即它们都是异步操作。那我们可以这样认为,所有的异步操作都有可能会引起模型的变化。 非常好,你已经了解了引起模型变化的事件源和触发变化的时机点。但是你还不知道,是由谁来负责通知相应的变化给视图。接下来,我们将讨论一种允许 Angular 随时检测到变化的机制,它被称为 ZonesZone 是下一个 ECMAScript 规范的建议之一。Angular 团队实现了 JavaScript 版本的 zone.js ,它是用于拦截和跟踪异步工作的机制。 Zone 是一个全局的对象,用来配置有关如何拦截和跟踪异步回调的规则。Zone 有以下能力:
我们来看一个简单的示例: Zone.current.fork({}).run(function () { Zone.current.inTheZone = true; setTimeout(function () { console.log('in the zone: ' + !!Zone.current.inTheZone); },0); }); console.log('in the zone: ' + !!Zone.current.inTheZone); 以上代码运行后的结果是: in the zone: false in the zone: true 是不是感觉很神奇!在Angular 2 中,有一个 NgZone,它是专门为 Angular 2 定制的 zone。在正式介绍它之前,我们先来看一下 Angular 1.x 中的一个例子: <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Angular 1.x Demo</title> <script src="//cdn.bootcss.com/angular.js/1.6.3/angular.min.js"></script> </head> <body ng-app="exeApp"> <div ng-controller="MainCtrl"> <h4>Hello {{ name }}</h4> </div> <script type="text/javascript"> angular.module('exeApp',[]) .controller('MainCtrl',['$scope',function ($scope) { $scope.name = 'Angular'; setTimeout(function () { $scope.name = 'Angular 2'; },2000); }]); </script> </body> </html> 以上代码运行后的输出结果:
用过 Angular 1.x 的同学,应该很清楚可以通过 Angular 1.x 中的 为什么我们都是使用定时器,而在 Angular 2 中模型发生变化后,却能自动通知视图进行刷新呢 ?我们来分析一下,首先在浏览器中新开一个 Tab 页,在控制台输入: window.setTimeout.toString() "function setTimeout() { [native code] }" 然后再打开一个 Angular 2 应用的页面,在控制台同样输入: window.setTimeout.toString() "function setTimeout(){return f(this,arguments)}" 我们发现在 Angular 2 中,setTimeout 方法已经被重写了,最简单的实现方式如下: var originSetTimeout = window.setTimeout; window.setTimeout = function(fn,delay) { console.log('setTimeout has been called'); originSetTimeout(fn,delay); } 其实在 Angular 2 应用程序启动之前,Zone 采用猴子补丁 (Monkey-patched) 的方式,将 JavaScript 中的异步任务都进行了包装,这使得这些异步任务都能运行在 Zone 的执行上下文中,每个异步任务在 Zone 中都是一个任务,除了提供了一些供开发者使用的钩子外,默认情况下 Zone 重写了以下方法:
Zone 内部源码片段: var set = 'set'; var clear = 'clear'; var blockingMethods = ['alert','prompt','confirm']; var _global = typeof window === 'object' && window || typeof self === 'object' && self || global; patchTimer(_global,set,clear,'Timeout'); patchTimer(_global,'Interval'); patchTimer(_global,'Immediate'); patchTimer(_global,'request','cancel','AnimationFrame'); patchTimer(_global,'mozRequest','mozCancel','webkitRequest','webkitCancel','AnimationFrame'); NgZoneNgZone 是基于 Zone 实现的,它是Zone派生出来的一个子Zone,在 Angular 环境内注册的异步事件都运行在这个子 Zone 内 (因为NgZone拥有整个运行环境的执行上下文),它扩展了自有的一些 API 并添加了一些功能性的方法到它的执行上下文中。 在 Angular 源码中,有一个 class ApplicationRef { private _views: InternalViewRef[] = []; constructor(private zone: NgZone) { this.zone.onMicrotaskEmpty.subscribe(() => { this.zone.run(() => { this.tick(); }); }); } tick() { if (this._runningTick) { throw new Error('ApplicationRef.tick is called recursively'); } this._views.forEach((view) => view.detectChanges()); } } 现在我们先来总结一下前面所讲的内容:
要完全理解 Zone 的工作原理是比较困难的,对我们大部分的人来说,只要知道 Angular 内部是通过它来跟踪异步任务,然后执行变化检测任务就可以了。 我有话说1.在 Angular 2 项目中怎么访问 Zone 打补丁前的方法,如 setTimeout、clearTimeout 等 因为 Zone 内部通过内建的 function __symbol__(name) { return '__zone_symbol__' + name; } 因此我们可以在浏览器的控制台中运行: Object.keys(window).forEach((key) => { if(key.indexOf('zone_symbol') > 0) { console.log(key); } }); 运行后控制台的输出结果如下:
2.前面介绍 Zone 使用的示例,为什么控制台会输出那样的结果 ? // 加载Zone.js给浏览器中的一些异步操作打上补丁 // 创建Root Zone // 调用Zone.current对象上的fork方法创建新的zone,我们称之为childZone Zone.current.fork({}).run(function () { // 运行run方法,Zone.current被设置为函数被执行时所属的Zone,即childZone Zone.current.inTheZone = true; // 这里注册了一个定时器。由于被打过了猴子补丁,这里调用的并不是 // 浏览器"默认"的setTimeout方法。因此,这里实际上是在配置代理。这里 // 要重点指出的是这个代理会保留一个指向创建时所属Zone的引用即childZone, // 稍后会用到这个引用。 setTimeout(function () { // 定时时间到,此时的Zone.current的值会被重置为childZone console.log('in the zone: ' + !!Zone.current.inTheZone); },0); // 代码执行完 Zone.current属性被重置为Root Zone // Zone的生命周期里的钩子函数会被触发 }); console.log('in the zone: ' + !!Zone.current.inTheZone); 如果还是不好理解的话,我们可以想象一下同步的过程: const rootZone = Zone.current; // 创建一个新的Zone const childZone = Zone.current.fork({}); // 设置当前的zone Zone.current = zone; // 为当前的zone添加inTheZone属性 Zone.current.inTheZone = true; console.log('in the zone: ' + !!Zone.current.inTheZone); // 退出当前的zone Zone.current = rootZone; console.log('in the zone: ' + !!Zone.current.inTheZone); 总结这篇文章我们先介绍了浏览器中渲染的概念,然后通过三个示例引出了引起模型变化的事件源并总结了它们之间的共性,此外我们还介绍了 Angular 1.x 项目中初学者容易遇到的问题,并基于该问题引入了 Zone 和 NgZone 的概念,最后我们简单介绍了 Zone.js 的内部工作原理。下一篇文章我们将详细介绍 Angular 2 组件中的变化检测器。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
- [全程建模]UML中用例图里的外部系统的表示方式有规定么?
- b/w docker-compose build vs docker build生成的docker镜像
- angularjs – 根据角度js中的按钮单击删除选择选项
- angularjs的进度条
- python-如何以编程方式在jenkins作业配置页面中设置github
- angular – 如何以编程方式关闭ng-bootstrap模式?
- (实验三)《数据结构》第三章 顺序栈与链栈的验证
- 使用bash中的while循环打印增量日期
- WebService、RMI、RPC、XML-RPC、JSON-RPC、SOAP、REST(re
- cxf webservice 生成客户端代码以及调用