翻译说明: 中英对照, 意译。
本文出自: AngularInDepth
原文地址
I was once asked if change detection in Angular is depth or breadth first.
我曾被问道: Angular 中的变更检测是深度优先还是广度优先。
This basically means whether Angular first checks siblings of the current component (breadth-first) or its children (depth-first).
这主要是指 Angular 首先检查当前组件的兄弟组件(广度优先)还是子组件(深度优先)。
I hadn’t given any prior thought to this question so I just went with my gut and the knowledge of internals.
我之前没有考虑过这个问题,所以我只是在用我的直觉和内在的知识。
I declared that it was depth-first. Later, to check my assertion, I created a tree of components and put some logging logic inside the ngDoCheck
hook:
我说它是深度优先。稍后,为了检测我的断言,我创建了一个组件树,并在 ngDoCheck
钩子中放了些日志逻辑:
@Component({
selector: 'r-comp',
template: `{{addRender()}}`
})
export class RComponent {
ngDoCheck() {
// holds all calls in order and is logged to console
calls.ngDoCheck.push('R');
}
And to my surprise, it turned out that some siblings were checked first as depicted on the diagram below:
令我惊讶的是,结果显示,一些兄弟组件首先被检查,如下图所示:
So here you see that Angular checks K
and then V
, L
and then C
and so on.
这里你可以看到, Angular 检测了 K
然后是 V
, L
然后是 C
,等等。
So was I wrong and it’s really a breadth-first algorithm?
那么, 难道是我错了, 这真的是广度优先算法?
Well, not exactly. First thing to notice in the above representation is that it’s not a proper breadth-first algorithm.
嗯, 不完全是。首先要注意的是,这不是个完全地广度优先算法。
The conventional implementation of the algorithm checks all siblings on the same level, whereas in the diagram above as you can see the algorithm indeed checks L
and C
sibling components, but instead of checking X and F it goes down to J and O.
广度优先算法的常规实现是检查 同级的所有兄弟组件,而在上图中,你可以看到该算法确实检查了同级组件 L
和 C
,但是没有检查 X
和 F
,就往下到了 J
和 O
。
Also, the implementation of the breadth-first graph traversal algorithm is well defined but I couldn’t find it in the sources.
同样,广度优先的图遍历算法的实现是很明确的,但我在源代码中找不到。
So I decided to run another experiment and put a logging logic in a custom function called when change detection evaluates expressions:
因此,我决定进行另一个实验,并将日志逻辑放在自定义函数中,当变更检测对表达式求值时调用该函数:
@Component({
selector: 'r-comp',
template: `{{addRender()}}`
})
export class RComponent {
addRender() {
calls.render.push('R');
}
}
And this time I got different results:
这次我获得了不同的结果:
It’s a proper depth-first graph traversal algorithm.
这是一个完全的深度优先图遍历算法。
So what’s going on here? It’s actually pretty simple, let’s see.
那么, 这里发生了什么? 事实上很简单, 我们来看一下.
Change detection operations 变更检测操作
To understand the difference in behavior we need to take a look at the operations performed by change detection mechanism when checking a component.
要理解行为上的差异,我们需要查看变更检测机制在检查组件时执行的操作。
If you’ve read my other articles on change detection you probably know that the key operations performed by change detection are the following:
如果你已经阅读了 关于变更检测的我的其他文章 , 你可能知道下面的变更检测执行的关键操作:
update child components properties
更新子组件的属性call
NgDoCheck
andNgOnChanges
lifecycle hooks on child components
调用 子组件 的NgDoCheck
和NgOnChanges
生命周期钩子update DOM on the current component
更新 当前组件 的 DOMrun change detection for child components
对子组件执行变更检测
I highlighted one interesting specifics above — when Angular checks the current component it calls lifecycle hooks on child components, but renders DOM for the current component.
我在上面强调了一个有趣的细节——当 Angular 检查当前组件时,调用了子组件的生命周期钩子,但是为当前组件渲染 DOM。
And that’s a very important distinction.
这是个很重要的区别。
This is precisely the reason that makes it seem as if the algorithm runs breadth-first if we put logging into NgDoCheck
hook.
如果我们把日志放到 NgDoCheck
钩子里,这恰恰是使它看起来就像是算法执行深度优先的原因。
When Angular checks a current component it calls lifecycle hooks for all its child components which are siblings.
当 Angular 检查当前组件时,它会调用所有同级的子组件的生命周期钩子。
Suppose Angular checks K
component now and calls NgDoCheck
lifecycle hook on L
and C
. So, we get the following:
假设 Angular 现在检查 K
组件, 调用了 L
和 C
组件的 NgDoCheck
生命周期钩子。 那么, 我们会得到下面的图:
Looks like breadth-first algorithm.
看起来像是 广度优先 算法。
However, remember that Angular still in the process of checking K
component.
然而, 记住: Angular 仍然在检测 K
组件的过程中。
So after completing all operations for the K
component it doesn’t proceed to checking the sibling V component, as it would with the breadth-first implementation.
因此,在完成了 K
组件的所有操作之后,并没有继续检查同级的 V
组件,若检查的话就是广度优先的实现。
Instead, it goes on to check L
component, which is a child of K
.
相反,它继续检查 L
组件,L
组件是 K
的子组件。
This is the depth-first implementation of change detection algorithm.
这是变更检测算法的深度优先实现。
And as we now know it will call ngDoCheck
on J
and O
components and this is exactly what happens:
就像我们现在知道的,它会调用 J
and O
组件的 ngDoCheck
,这正是所发生的事情:
So, after all, my gut didn’t let me down.
因此,终究我的直觉并没有让我失望。
Change detection mechanism is implemented as depth-first internally, but involves calling ngDoCheck
lifecycle hooks on sibling components first.
变更检测机制在内部的实现为深度优先, 但是包含首先在兄弟组件上调用ngDoCheck
生命周期钩子。
By the way, I already described this logic in depth in the If you think ngDoCheck
means your component is being checked — read this article.
顺便说一下, 我已经在 If you think ngDoCheck
means your component is being checked — read this article 中深入地描述了这部分逻辑。(译者注: 这里提到的这篇文章我也翻译了, 请查阅本文所在文集 <<技术博客翻译>>)
Stackblitz demo
Here you can see the demo with logging logic in different places.
这里 你可以在不同地方看到带有日志逻辑的演示