Angular项目实战Angular2的脏值检测机制

上下观古今,起伏千万途。这篇文章主要讲述Angular项目实战Angular2的脏值检测机制相关的知识,希望能为你提供帮助。
Change Detection  (变化检测) 是 Angular 2 中最重要的一个特性。当组件中的数据发生变化的时候,Angular 2 能检测到数据变化并自动刷新视图反映出相应的变化。
那么,Angular 2 是如何知道数据发生?了?改变?又是如何知道需要修改的  DOM  位置,准确地用最小范围去修改 DOM 呢?
本期跟大家分享一下,Angular2的脏值检测机制。


?NgZone?在 Angular 2 中,有一个 NgZone,它是专门为 Angular 2 定制的 zone。
zone.js 这个工具给所有 javascript 异步事件 都提供了一个上下文。zone.js 可以实现异步任务的跟踪、分析、错误记录。NgZone 是基于 Zone 实现的,它是 Zone 派生出来的一个子 Zone,在 Angular 环境内注册的异步事件都运行在这个子 Zone 内 (因为 NgZone 拥有整个运行环境的执行上下文),它扩展了自有的一些 API,并添加了一些功能性的方法到它的执行上下文中。


在 Angular 源码中,有一个 ApplicationRef_ 类,其作用是用来监听 NgZone 中的 onMicrotaskEmpty 事件。无论何时,只要触发这个事件,那么将会执行一个 tick 方法用来告诉 Angular 去执行变化检测。
Angular 会在初始化的时候调用 zone,下面的代码是 Angular 的 ApplicationRef_ 的构造函数中的一部分,this._zone 是 NgZone 的一个实例。而NgZone 是 zone 的一个简单封装,当异步事件结束的时候由 onMicrotaskEmpty 提示 Angular 更新视图。

this._zone.onMicrotaskEmpty.subscribe(
next: () =>
this._zone.run(() => this.tick(); );

);

tick() 函数会对所有附在 ApplicationRef_ 上的视图进行?脏?检查。
这也就是为什么我们在需要手动调用?脏?检查的时候,一般会使用 tick() 或 setTimeout() 的原因。
tick(): void
this._views.forEach((view) => view.ref.detectChanges())

用过 Angular 1.x 的同学,应该很清楚,当我们使用第三方库方法或 settimeout 的时候,由于脱离了 angular 上下文了,需要用 $timeout 服务或手动调用 $scope.$digest() 方法来通知视图刷新。


这对于初学者来说,是很麻烦的一件事情。但在 angular2 中,不需要再使用 Angular 1.x 中的 $timeout 服务或手动调用 $scope.$digest() 方法来刷新视图。
那么,Angular 2 是如何做到模型发生变化后,自动通知视图进行刷新呢?


其实在 Angular 2 应用程序启动之前,Zone 采用猴子补丁 (Monkey-patched) 的方式,将 javaScript 中的异步任务都进行了包装,这使得这些异步任务都能运行在 Zone 的执行上下文中,每个异步任务在 Zone 中都是一个任务。除了提供一些供开发者使用的钩子外,默认情况下 Zone 重写了以下方法:setInterval、clearInterval、setTimeout、clearTimeout
alert、prompt、confirm
requestAnimationFrame、cancelAnimationFrame
addEventListener、removeEventListener


??脏?检查过程?在 Angular 中,每一个组件都有它自己的检测器(detector),用于负责检查其自身模板上绑定的变量,所以每一个组件都可以独立地决定是否进行?脏?检查。

因为在 Angular 中组件是以树的形式组织起来的,相应地,检测器也是一棵树的形状。
当一个异步事件发生时,?脏?检查会从根组件开始,自上而下对树上的所有子组件进行检查。
相比 Angular1 中的带有环的结构,这样的单向数据流效率更高,而且容易预测。
(1)child.component.ts
importComponent, Inputfrom @angular/core;

@Component(
selector: exe-child,
template: `
< p> text < /p>
`
)
export classChildComponent
@Input() text: string;

(2)parent.component.ts
importComponent, Inputfrom @angular/core;

@Component(
selector: exe-parent,
template: `
< exe-child [text]="name"> < /exe-child>
`
)
export classParentComponent
name: string = Semlinker;

(3)app.component.ts
importComponentfrom @angular/core;

@Component(
selector: exe-app,
template: `
< exe-parent> < /exe-parent>
`
)
export classAppComponent

上面的例子中,ParentComponent   组件会比 ChildComponent   组件更早执行变化检测。
因此,在执行变化检测时 ParentComponent 组件中的 name 属性,会传递到 ChildComponent 组件的输入属性 text 中。
此时,ChildComponent 组件检测到 text 属性发生变化,因此组件内的 p 元素内的文本值从空字符串变成 ‘Semlinker’ 。
这看起来虽然很简单,但非常重要。另外,对于单次变化检测,每个组件只检查一次。


??脏?检查策略:OnPush?现在默认脏检查方法是从根组件开始,遍历所有的子组件进行脏检查,但这种检查方式的性能存在很大问题。
如果我们能让组件只在其输入改变的时候才进行?脏?检查,那性能会得到大大提高。
Angular 提供了 OnPush  ?脏?检查策略,可以用下面的方式使用:
@Component(
selector: todos,
changeDetection: ChangeDetectionStrategy.OnPush,
templateUrl: todos.component.html
)
export classTodosComponent
@Input()
todos: Todo[];

使用 OnPush 后,组件只有在输入改变的时候才会进行?脏?检查,这里的改变是指:使用 === 判断为 false。
因此在上面的例子中,即使往 todos 数组中通过 push 添加新数据也不会触发?脏?检查,只有给 todos 重新赋值才会触发。
这样子,我们就有机会在?脏?检查中跳过一个组件的子树,减少检查次数。


?小结?Angular2 在 Zone 的基础上进行封装了自己的 NgZone,实现了脏值检查自动更新的机制,相比于 Angular1 来说使用体验更好。另外,我们也可以根据自己的需要使用 OnPush 进行性能提升。


【Angular项目实战Angular2的脏值检测机制】下期给大家分享更多实战中的点滴,如果大家喜欢 Angular 或对此感兴趣,欢迎各位关注、留言,大家的支持就是我的动力!

    推荐阅读