看了很多有关的文章,也认真观看了Angular2 团队放在youtube的相关演讲视频。(你懂的)
我也想用自己的理解总结一下Angular2/4组件变化的原理。内容分这么几个小节:
- 什么操作会引发组件绑定属性变化
- 组件绑定属性变化如何通知Angular2
- Angular2如何进行脏检查(与Angular1简单对比)
- 小结:为什么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。这是我想要着重来说的。
这里有两个重点:
- 有向树结构。
- 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 的每一个属性来和前值做对比。
既然 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 就很容易检测从而实行“定点清除”,如下图所示:
这样当然又有效的减少了 N 次数。
以上就是 Angular2 的组件绑定变化总结,希望对花时间来阅读它的人有所帮助。通过自己写一写博客总结一下,对这块掌握的也更牢固了。
最后,今天是 2017 年的最后一个工作日,希望自己在接下来的一年中能继续前行,心想事成:)