干货丨细说Angular 中的变化检测(Change Detection)

姓名:韩宜真

学号:17020120095

转载自:https://mp.weixin.qq.com/s/iLceOG3MqnCSzcv3S71ujA

【嵌牛导读】本文介绍了Angular 中的变化检测。

【嵌牛鼻子】变化检测

【嵌牛提问】Angular 中的变化检测是怎样的?

【嵌牛正文】

导读

作者刘雄风主要负责Angular的基础组件的开发工作。在这篇文章中,他为我们介绍了Angular中的变化检测(Change Detection)是什么,工作的机制是怎么样的,以及怎样利用好变化检测,从而使得Angular应用有着较好的运行效率。

在使用angular进行开发中,我们常用到angular中的绑定---模型到视图的输入绑定、视图到模型的输出绑定以及视图与模型的双向绑定,而这些绑定的值之所以能在视图与模型之间保持同步,正是得益于Angular中的变化检测。

变化检测是啥?

简单来说,变化检测就是Angular用来检测视图与模型之间绑定的值是否发生了改变,当检测到模型中绑定的值发生改变时,则同步到视图上;

反之,当检测到视图上绑定的值发生改变时,则回调对应的绑定函数。

什么情况下会引起变化检测呢?

变化检测的关键在于如何最小粒度的检测到绑定的值是否发生了改变。

那么在什么情况下会导致这些绑定的值发生变化呢?

我们可以看一下我们常用的几种场景:

我们在视图上通过插值表达式绑定了Myapp 中的name属性,当点击按钮时,改变了name属性的值,这时就导致了绑定的值发生了变化。

再来看另外一种场景:

我们在MyApp这个组件的生命周期钩子函数里向服务器端发送了一个ajax请求,当这个请求返回结果时,同样的,会改变当前视图上绑定的name属性的值。

类似的,我们还可能设定一些定时任务,这些定时任务也可能会改变与视图绑定的值:

其实,我们不难发现上述三种情况都有一个共同点,即这些导致绑定值发生改变的事件都是异步发生的。

如果这些异步的事件在发生时能够通知到Angular框架,那么Angular框架就能及时的检测到变化。

那么Angular框架如何才能获知到这些异步事件的发生呢?

我们不妨来看一看异步事件在Javascript中执行的过程:

左边表示将要运行的代码,这里的stack表示javascript的运行栈,而webApi则是浏览器中提供的一些javascript的api,TaskQueue表示javascript中任务队列,因为javascript是单线程的,异步任务在任务队列中执行。

当上述代码在javascript中执行时,首先func1 进入运行栈,func1执行完毕后,setTimeout进入运行栈,执行setTimeout 过程中将回调函数cb 加入到任务队列,然后setTimeout出栈,接着执行func2函数,func2函数执行完毕时,运行栈为空,接着任务队列中cb 进入运行栈得到执行。

可以看出异步任务首先会进入任务队列,当运行栈中的同步任务都执行完毕时,异步任务进入运行栈得到执行。

如果这些异步的任务执行前与执行后能提供一些钩子函数,通过这些钩子函数,Angular便能获知异步任务的执行。

事实上,angular正是使用zonejs来做到的。

Zonejs通过猴子补丁的方式,对webApi中的一些异步任务的API在运行时进行替换,替换后的api提供了一些钩子函数。

Angular中的变化检测是个怎样的过程呢?

通过上面的介绍,我们大致明白了变化检测是如何被触发的,那么Angular中的变化检测是如何执行的呢?

首先我们需要知道的是,对于每一个组件,都有一个对应的变化检测器;

即每一个component都对应有一个changeDetector,我们可以在component中通过依赖注入来获取到changeDetector。

而我们的多个component是一个树状结构的组织,由于一个component对应一个changeDetector,那么changeDetector之间同样是一个树状结构的组织。

最后我们需要记住的一点是,每次变化检测都是从树根开始的。

枯燥无味的理论到此结束,下面通过一些例子来直观的感受一下。

 Main.component.ts :

Movie.component.ts:

上述代码中,MainComponent通过 <movie></movie> 标签嵌入了MovieComponent,从树状结构上来说,MainComponent是MovieComponent的根节点,而MovieComponent是MainComponent的叶子节点。当我们点击MainComponent的第一个button时,会回调到changeActorProperties方法,然后会触发变化检测的执行。首先变化检测从MainComponent开始:

ü 检测slogan 值是否发生了变化----没有发生变化

ü 检测 title 值是否发生了变化-----没有发生变化

ü 检测 actor 值是否发生了变化-----没有发生变化

你可能对于 actor的检测结果感到疑惑,不是明明改变了actor的属性值吗?实质上在对actor检测只检测actor 本身的引用值是否发生了改变,改变actor的属性值并未改变actor 本身的引用,因此是没有发生变化。而当我们点击MainComponent的第二个button ,重新new了一个 actor ,这时变化检测才会检测到 actor发生了改变。

然后变化检测进入到叶子节点--MovieComponent:

ü 检测title 值是否发生了改变---没有发生变化

ü 检测 actor.firstName 是否发生了变化--发生了改变

ü 检测 actor.lastName 是否发生了改变--发生了改变

因为MovieComponent再也没有了叶子节点,所以变化检测将更新DOM,同步视图与模型之间的变化。

看到这里你可能会想,这机制未免也有点太简单粗暴了吧,假如我的应用中有成百上千个component,随便一个component 触发了检测,那么都需要从根节点到叶子节点重新检测一遍。对的,Angular 的开发团队已经考虑到了这个问题,上述的检测机制只是一种默认的检测机制,Angular还提供一种OnPush的检测机制,还是同样的例子,下面看一下OnPush检测机制是咋样的:

Main.component.ts :

与上面的代码相比,只在@Component中添加了

即将检测机制设置为OnPush。同样的,当我们点击第一个button时,将会发生如下变化检测:

ü 检测slogan 值是否发生了变化----没有发生变化

ü 检测 title 值是否发生了变化-----没有发生变化

ü 检测 actor 值是否发生了变化-----没有发生变化

好,变化检测到此结束,不会再进入到 MovieComponent 中了。

这正是OnPush与Default之间的差别:当检测到与子组件输入绑定的值没有发生改变时,变化检测就不会深入到子组件中去。

那么当我们点击MainComponent中的第二个按钮时,由于改变了actor本身而不是它的属性值,那么就会检测到actor的变化,进而继续进入到MovieComponent 进行变化检测。

所以,当你使用了OnPush检测机制时,在修改一个绑定值的属性时,要确保同时修改到了绑定值本身的引用。

但是每次需要改变属性值的时候去new一个新的对象会使得代码很难看,并且有时候你难以保证你一定记得这么做,恩,immutable.js 你值得拥有!

另外一个问题就是,当我们使用OnPush,并且输入绑定的是一个Observable对象时,该怎么让检测到当订阅的事件发生时引起的绑定的值的发生的改变呢?

比如下面这个组件:

输入绑定 addItemStream 是一个Observable对象,Observable对象本身是不会变化的,只有当订阅的事件到达时,才会去修改count的值。

如果使用OnPush 那么检测就不会进入到CountComponent。

解决的办法很简单,只需在修改count的值后做一个标记(markForCheck),那么变化检测就会沿着CountComponent所在的树枝进行变化检测。

总结

总结来说,Angular中变化检测器是树型结构的组织,与组件树结构相对应,默认情况下,当一个组件引发了变化检测时,检测是从树根开始一直检测到树节点。当你设置某个组件的检测策略是 OnPush 时,如果该组件的输入绑定没有发生变化时,那么检测就不会进入到该组件。当组件树变的很庞大时,常用这种办法来提高应用的性能。 

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容