网站首页 > 基础教程 正文
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
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 或对此感兴趣,欢迎各位关注、留言,大家的支持就是我的动力!
猜你喜欢
- 2024-11-14 angular8 ui-grid升级方案 angular8升级angular10
- 2024-11-14 angular8 日常开发避坑指南(30个)
- 2024-11-14 Angular 11.1.0-next.2 发布 angular 11发布的影响
- 2024-11-14 最近写Vue,真是累死人了!没有Angular爽,谁能帮帮我?[吐槽]
- 2024-11-14 「Angular项目实战」Angular2+如何去除URL中的#号
- 2024-11-14 Angular9构建一个后台管理系统(二)
- 2024-11-14 AngularJs入门,一个简单的demo angular实战
- 2024-11-14 Angular1升级到Angular2之组件样式封装
- 2024-11-14 逆袭之路系列-AngularJS 1.2版本编程入门-01
- 2024-11-14 Angular9构建一个后台管理系统(一)
- 06-18单例模式谁都会,破坏单例模式听说过吗?
- 06-18Objective-c单例模式的正确写法「藏」
- 06-18单例模式介绍(单例模式都有哪些)
- 06-18前端设计-单例模式在实战中的应用技巧
- 06-18PHP之单例模式(php单例模式连接数据库)
- 06-18设计模式:单例模式及C及C++实现示例
- 06-18python的单例模式(单例 python)
- 06-18你认为最简单的单例模式,东西还挺多
- 最近发表
- 标签列表
-
- jsp (69)
- gitpush (78)
- gitreset (66)
- python字典 (67)
- dockercp (63)
- gitclone命令 (63)
- dockersave (62)
- linux命令大全 (65)
- pythonif (86)
- location.href (69)
- dockerexec (65)
- tail-f (79)
- queryselectorall (63)
- location.search (79)
- bootstrap教程 (74)
- 单例 (62)
- linuxgzip (68)
- 字符串连接 (73)
- html标签 (69)
- c++初始化列表 (64)
- mysqlinnodbmyisam区别 (63)
- arraylistadd (66)
- mysqldatesub函数 (63)
- window10java环境变量设置 (66)
- c++虚函数和纯虚函数的区别 (66)