5

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

 2 years ago
source link: https://blog.51cto.com/u_15345191/5107636
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

原创

博读代码 2022-03-16 11:01:27 博主文章分类:Angular ©著作权

文章标签 变化检测 异步任务 angular 脏值 前端 文章分类 其他 前端开发 阅读数653

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK