Angular2 组件变化检测总结 (Change Detection)

看了很多有关的文章,也认真观看了Angular2 团队放在youtube的相关演讲视频。(你懂的)

Change Detection In Angular 2


我也想用自己的理解总结一下Angular2/4组件变化的原理。内容分这么几个小节:

  1. 什么操作会引发组件绑定属性变化
  2. 组件绑定属性变化如何通知Angular2
  3. Angular2如何进行脏检查(与Angular1简单对比)
  4. 小结:为什么Angular2比Angular1性能好很多

什么操作会引发组件绑定属性变化

其实不光是Angular,对于所有的MVVM框架,引发绑定属性变化的方式无非下面四种:
Events: click, submit etc..
XHR: 从后台服务器获取数据更新视图
Timers: setTimeout(), setInterval()
Promise: ES6 新出

以上四种方式的一个共同点就是:它们全部都是异步命令。这就引出了下面的问题:

组件绑定属性变化如何通知Angular2——关键字 Zone.js

问题:为什么异步指令难以跟踪捕捉:

function doSomething() {
  console.log('Async task');
}

// start timer
start = timer();
foo();
setTimeout(doSomething, 2000);
bar();
baz();
// stop timer
time = timer() - start;

我们用试图得到代码的运行时间,但是很显然包裹在setTimeout中的doSomething的执行时间无法计算,foo, bar, baz都在执行栈中而doSomething由于setTimeout的原因被放入了事件队列。

Solution: Zone.js的引入

Zone最著名的一个标签就是猴子补丁(Monkey-patched Hooks)
那么什么是猴子补丁呢,用一句话概括就是将所有javascript的原生方法特别是关于异步的重新实现一遍,既然是自己重建那当然可以夹杂一些私货在里面了(比如监控异步函数的执行)。

这些重写的方法包括但不仅限于以下,大家可以感受一下:
window.addEventListener = Zone.addEventListener,
window.removeEventListener = Zone.removeEventListener,
window.setTimeout = Zone.setTimeout,
window.setInterval = Zone.setInterval

除了重写了一些异步方法,Zone还定义了自己的event,当Angular的绑定属性改变,将会触发这些event,其中最关键的event 叫 onTurnDone。而这个event 的 handler 就是做 Change Detection. 直接上源码可能更清晰一点:

ObservableWrapper.subscribe(this.zone.onTurnDone, () => {
  this.zone.run(() => {
    this.tick();
  });
});

tick() {
  // perform change detection
  this.changeDetectorRefs.forEach((detector) => {
    detector.detectChanges();
  });
}

Angular2如何进行脏检查(与Angular1简单对比)

下面是一个关于监测数据改变次数的公式(来自Angular2 团队的演讲视频)

这里写图片描述

C - 检查一个绑定所花的的时间
N - 绑定的数目

由上图我们可以看到,提高效率的方法无非两种:减少 C 或者 减少 N.

减少 C

与Angular1相比,Angular2 在源码层面做了很多优化,号称几毫秒可以做数万次监测,毕竟是换车了,所以 C 大大的降低了。所以在 C 上,angular2 完胜!

减少 N。这是我想要着重来说的。

这里有两个重点:

  1. 有向树结构。
  2. Immutable Object

Angular2 的有向树结构:

Angular1:
这里写图片描述

Angular2:
这里写图片描述

从这张angular1 和 angular2 的对比图可以看出:在 binding 数目一样的情况下,环结构可能对同一个 binding 检查多遍,而有向树至多检查一遍。从这个角度来说,Angular2 的 binding 数目比 Angular1 要少很多 (特别是大型项目)

Angular2 的 Immutable Object:

Mutable(可变) VS Immutable(不可变)

Mutable:

Javascript 默认所有的对象都是可变的,Angular1 并没有引入 Immutable,所以 Angular1 中所有的对象也都是可变的。那么什么是可变呢:

var employee = {
    salary: 1000,
    age: 30
};
var anotherEmployee = employee;
anotherEmployee.salary = 10000000;
console.log(employee === anotherEmployee) // true

很明显,employee 和 anotherEmployee 的引用相同,改变 employee 中的属性当然也就相当于改变了 anotherEmployee。这就是对象可变。

Immutable:

var employee = {
    salary: 1000,
    age: 30
};
var anotherEmployee = Object.assign({}, employee, {salary: 10000000});;
console.log(employee === anotherEmployee); // false

在修改 employee 对象中的 salary 时,我们并没有直接修改原有对象,而是用 Object.assign 方法创建一个新的对象anotherEmployee。这就是对象不可变( Immutable Object )。

可以推导出一个小结论:在 javascript 中,Object 类型都是 mutable,其他诸如 number,string,boolean 都是 immutable

我们再回头想想 angular1 和 angular2 中的绑定语法:

<app [employee] = 'employee'> </app>

对于 mutable 的情况,当绑定的值是一个对象,那么当该对象中的某个属性发生变化时,对于 Angular (不管1还是2) 来说,该对象是没有变化的,但是业务要求这个变化必须要检测到,怎么办呢,没有办法,框架只好检查每一个 binding 的每一个属性来和前值做对比。

Change Detect

既然 Angular1 和 Angular2 中的对象都是 mutable,体现不出 Angular2 的优越性, 那还扯半天所为何来? 呵呵不好意思,我少加了一个形容词,在 Angular2 中对象默认是 mutable。默认的意思就是我们可以改变让它不默认。

这里写图片描述

我们可以在定义Component的时候将 Change Detect 的检查策略改为 OnPush

@Component({
  template: `
    <h2>{{employee.salary}}</h2>
    <span>{{employee.age}}</span>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
class EmployeeCmp {
  @Input() employee;
}

当绑定对象变成 immutable 的时候,改变绑定对象的属性就意味着对象本身的改变,那么 Angular2 就很容易检测从而实行“定点清除”,如下图所示:
OnPush

这样当然又有效的减少了 N 次数。

以上就是 Angular2 的组件绑定变化总结,希望对花时间来阅读它的人有所帮助。通过自己写一写博客总结一下,对这块掌握的也更牢固了。

最后,今天是 2017 年的最后一个工作日,希望自己在接下来的一年中能继续前行,心想事成:)

猜你喜欢

转载自blog.csdn.net/Napoleonxxx/article/details/78928200