Angular2 脏检查过程

在本文中我将会深入讨论Angular 2 中的变更检测系统。

高层次概览

一个Angular 2 应用就是一颗组件树。

Angular 2 应用是一个反馈系统,变更检测是它的核心。

每一个组件都有一个变更检测器(change detector ),负责检测模板中所定义的数据绑定。绑定示例:{{todo.text}} 和[todo]=”t”。变更检测器会传播绑定,以深度优先的顺序从根节点向叶子节点传播。(换句话说,数据会从根节点流向叶子节点---译者注。

Angular 2 里面并没有设计一种通用的机制来实现双向数据绑定(但是,你仍然可以实现双向绑定行为以及ng-model特性。更多内容请点这里。)。这就是为什么变更检测路径是有向树而且不可以带有闭环的原因。这种结构让检测系统极其高效。更重要的是,它可以保证系统具备更强的可预测性,并且更加方便debug。

有多快?

默认情况下,变更检测会遍历组件树中的每一个节点,看看是不是发生了变化,而且对于浏览器发出的每一个事件都会进行一轮检测。这种做法乍一看非常低效,而实际上Angular 2 变更检测系统可以在几个毫秒内(具体数值和平台有关)进行成百上千次这样的简单检测。至于我们是怎么达成如此感人的效率的,那是另一篇文章的话题了。

Angular必须采用保守的策略,每一次都检查所有节点,因为JavaScript语言并没有在对象变更方面给我们提供任何保证(这里的意思是说,当一个普通的JavaScript对象里面的某个属性发生了修改的时候,我们没办法精确地知道到底是哪个属性被改了---译者注。)。但是,如果我们使用不可变对象(immutable object)或者可观察(observable object)对象,我们就可以知道对象中的某个特定的属性发生了变化。以前Angular无法利用这一点,而现在可以了。

Immutable(不可变)对象

如果一个组件只依赖于它的那些输入属性,而这些属性是不可变类型,那么只有当其中一个输入属性发生变化的时候这个组件才会发生改变。这样一来,我们就可以在变更检测树里面跳过这个组件的子树,直到它的某个输入属性触发变更事件的时候再去检测。当发生变更事件的时候,我们对组件所在的子树进行一次检测,然后立即禁用变更检测器直到发生下一次变化为止(下图中灰色的方块表示被禁用的变更检测器)。

我们采用比较激进的方式使用不可变对象,那么在大多数时间里面,变更检测树里面大块地方都会处于禁用状态。

这一机制是如何实现的并不重要。你只要把变更检测策略设置为OnPush就可以了。

请注意,组件仍然可以拥有可变的状态,只要这个状态只会因为输入属性发生改变而改变,或者因为组件模板内部触发的事件而改变即可。OnPush策略唯一禁止的事情是依赖于共享的可变状态。更多细节请点这里。

Observable(可观察) 对象

如果组件只依赖于它的那些输入属性,并且这些属性是可观察的,那么只有这些属性之一触发事件的时候组件才会发生改变。这样一来,我们就可以在变更检测树里面跳过这个组件的子树,直到发生这样的事件为止。在触发事件之后,我们可以对这颗子树进行单次检测,然后立即禁用直到发生下次变更。

虽然这里的处理方式看起来和不可变对象那一小节很类似,但是实际上是完全不同的。如果你的组件树是由不可变对象绑定构成的,发生一次变化就必须从根组件开始遍历所有组件。而处理可观察(observable)对象的方法却不是这样的。

我来草拟一个很小的例子示范一下这个问题。

ObservableTodosCmp的模板:

ObservableTodoCmp最终的样子:

如你所见,这里Todos组件只有一个todos数组的引用,这个数组是可观察的(observable)。所以,组件无法感知到数组里面每一个todo的变化。

处理这个问题的方法是,当其中一个可观察的todo触发事件的时候,从根组件开始一路检测到真正发生了变化的Todo组件为止。变更检测系统会保证这一过程。

假设我们的应用只使用可观察对象。出现以上情况的时候,Angular就会检查所有对象。

所以,第一趟检查完成之后的状态看起来就像这样:

比方说,这时候第一个可观察的todo触发了一个事件。那么,系统将会切换到以下状态:

在App_ChangeDetector、Todos_ChangeDetector,以及第一个Todo_ChangeDetector检查完成之后,系统又会回到以下状态:

假设发生变化的次数非常少,并且组件构成的是一颗平衡树,那么使用可观察对象会把复杂度从O(N)降低到O(logN),其中N是系统中数据绑定的总数量。

此功能并没有绑定到任何一个特定的库上面。把Angular切换到其它任何observable library都只需要修改几行代码而已。

可观察对象会导致级联更新吗?

可观察对象名声比较差,因为它们可能会导致级联更新。有使用过基于可观察模型的框架来构建大型应用经验的人都知道我在说什么。一个可观察对象发生更新可能会导致一大堆可观察对象触发更新,然后就这样一直级联下去。最后,在检测过程中的某个不确定的地方,视图会被更新。这种系统非常难以debug。

如上面的例子所示,在Angular 2 里面使用可观察对象不会出现这种问题。当可观察对象触发事件的时候,只是标记出一条路径,从组件一直延伸到根,在下次检测的过程中会沿着这条路径进行。然后,普通的变更检测过程开始介入,以深度优先顺序开始遍历组件树中的节点。所以,无论你是否使用可观察对象,更新的顺序都不会发生改变。这一点非常重要。使用可观察对象变成了一种非常简单的优化手段,而且并不会改变你理解系统的方式。

为了这些好处我必须在每个地方都使用observable/immutable对象吗?

不,你没有必要这样做。你可以只在应用里面的某个局部使用可观察对象(例如,在某个巨大的table里面),然后那个部分就可以获得巨大的性能提升。你甚至可以构建基于两种数据类型的组件,从而可以同时获得它们所带来的好处。例如,一个 “observable component”可以包含一个 “immutable component”,然后这个组件内部可以再包含一个“observable component”。即使在这种情况下,在传播变更的时候,变更检测系统一样能够最小化必要检测的次数。

小结

● Angular 2 应用是一个反馈式系统。

● 变更检测系统会按照从根到叶子的顺序传播数据绑定。

● 与Angular 1.x不同,Angular 2中的变更检测路径是一颗有向树。结果就是,整个系统性能更高并且可预测性更好。

● 默认情况下,变更检测系统会遍历整棵组件树。但是,如果你使用不可变对象或者可观察对象,你就可以享受到它们带来的优势,只要检测组件树里面“真正发生变化”的部分即可。

● 这些优化手段可以成为变更检测系统的组成部分,但是又不会破坏变更检测系统所提供的功能。

原文发布于微信公众号 - 交互设计前端开发与后端程序设计(interaction_Designer)

原文发表时间:2017-07-19

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏linux驱动个人学习

Linux CFS调度器之虚拟时钟vruntime与调度延迟--Linux进程的管理与调度(二十六)

CFS为了实现公平,必须惩罚当前正在运行的进程,以使那些正在等待的进程下次被调度。

2264
来自专栏程序你好

Apache Spark大数据处理 - 性能分析(实例)

今天的任务是将伦敦自行车租赁数据分为两组,周末和工作日。将数据分组到更小的子集进行进一步处理是一种常见的业务需求,我们将看到Spark如何帮助我们完成这项任务。

1613
来自专栏XAI

【定制化图像开放平台】入门实例之手写数字模型训练

本帖主要用手写数字为例进行一个简单入门实例总结(非官方) 平台网站:http://ai.baidu.com/customize/app/model/ 定制化图像...

42116
来自专栏程序员叨叨叨

4.3 CG 编译

计算机只能理解和执行由 0、1 序列(电压序列)构成的机器语言,所以汇编语言和高级语言程序都需要进行翻译才能被计算机所理解,担负这一任务的程序称为语言处理程序,...

1202
来自专栏mukekeheart的iOS之旅

MySQL学习笔记(一)

一、MySQL基础知识 MySQL 是一个真正的多用户、多线程 SQL 数据库服务器。 SQL(结构化查询语言)是世界上最流行的和标准化的数据库语言。MySQL...

2508
来自专栏彭湖湾的编程世界

【mock】后端不来过夜半,闲敲mock落灯花 (mockjs+Vuex+Vue实战)

mock的由来【假】 赵师秀:南宋时期的一位前端工程师 诗词背景:在一个梅雨纷纷的夜晚,正处于项目编码阶段,书童却带来消息:写后端的李秀才在几个时辰前就赶往临安...

26311
来自专栏用户画像

3.2.3页面置换算法

进程运行时,若其访问的页面不在内存而徐将其调入,但内存已无空闲时间时,就需要从内存中调出一页程序或数据,送入磁盘的对换区。 而选择调入页面的算法就称为页面置...

4253
来自专栏AI科技大本营的专栏

TensorFlow tfjs 0.10.3 发布

TensorFlow tfjs 0.10.3 近日正式发布,新版本主要有以下改进内容,AI科技大本营对其编译如下。 ▌资源

1332
来自专栏Golang语言社区

【golang】调优工具 pprof

Golang 提供了 pprof 包(runtime/pprof)用于输出运行时的 profiling 数据,这些数据可以被 pprof 工具(或者 go to...

1503
来自专栏游戏杂谈

Unity中巧用协程和游戏对象的生命周期处理游戏重启的问题

主要用到协程(Coroutines)和游戏对象的生命周期(GameObject Lifecycle)基础知识,巧妙解决了游戏重启的问题。

4172

扫码关注云+社区

领取腾讯云代金券