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

Posted 博读代码

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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项目实战】Angular2的脏值检测机制_异步任务

因为在 Angular 中组件是以树的形式组织起来的,相应地,检测器也是一棵树的形状。

当一个异步事件发生时,​​检查会从根组件开始,自上而下对树上的所有子组件进行检查。

相比 Angular1 中的带有环的结构,这样的单向数据流效率更高,而且容易预测。

(1)child.component.ts

import  Component, Input  from @angular/core;

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

(2)parent.component.ts

import  Component, Input  from @angular/core;

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

(3)app.component.ts

import  Component  from @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 或对此感兴趣,欢迎各位关注、留言,大家的支持就是我的动力!

以上是关于Angular项目实战Angular2的脏值检测机制的主要内容,如果未能解决你的问题,请参考以下文章

详解ANGULAR2组件中的变化检测机制(对比ANGULAR1的脏检测)

详解angular2组件中的变化检测机制(对比angular1的脏检测)

Angular项目实战Angular2+如何去除URL中的#号

脏值检测

中兴公司面试总结

2017年前端开发Angular.JS从入门到上手企业开发视频