0%

NgZone

利用Angular本身ChangeDetectionStrategyAPI改变检查策略脱离变化检测器,即减少不必要的检测来提高应用的性能

zone是什么

对浏览器的异步api做了封装,并对外发出通知何时开始何时结束,angular在得到异步事件结束的通知后,执行变化检查。

zone性能优化的重点在哪

精确的控制哪些异步事件是应该在angular的zone以内运行的,哪些是应该在angular的zone之外运行的。显然在angular的zone之外运行的事件是不会进行变化检测的,减少不必要的变化检测则实现了性能上的优化

1
2
3
4
5
6
7
import { NgZone } from '@angular/core';
constructor(private zone: NgZone) {}
mouseDown(event) {
this.zone.runOutsideAngular(() => {
window.document.addEventListener('mouseover', this.mouseMove.bind(this)); // 此处应该尽量避免直接操纵dom,应该利用底层封装api(renderer, renderer2)
})
}

NgZone可以让代码继续回到zone里运行,会再次触发anuglar的变化检测,调用NgZone.run();

1
2
3
4
5
6
mouseUp(event) {
this.zone.run(() => {
.....
})
window.docuemnt.removeEventListener('mousemove', this.mouseMove); // 移除mousemove绑定的回调
}

补充

zone.js为javascript提供执行上下文,可以在异步任务之间进行持久性传递。采用了猴子补丁将javascript中的异步任务包裹了一层。使得异步任务运行在zone的上下文中。每一个异步任务都被当作一个task,并在task基础上提供钩子函数。

  • onZoneCreated: 产生一个新的zone对象时的钩子函数,zone.fork也会产生一个继承基类zone的新zone,形成一个独立的zone上下文

  • beforeTask: zone Task 执行前的钩子函数

  • afterTask

  • onError: zone运行Task时候的异常钩子函数

并且对大多数异步事件进行了包裹封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const log = (phase) => {
return () => {
console.log('i am in zone.js' + phase + '!');
}
}
zone.fork({
onZoneCreated: log('onZoneCreated'),
beforeTask: log('beforeTask'),
afterTask: log('afterTask')
}).run(() => {
const methodLog = (func) => {
return () => {
console.log('i am from' + func + 'function')
}
},
foo = methodLog('foo'),
bar = methodLog('bar'),
baz = () => {
setTimeout( methodLog('baz in setTimeout'), 0)
};
foo();
baz();
bar();
})
1
2
3
4
5
6
7
8
9
10
输出结果:
i am in zone.js beforeTask;
i am from foo function;
i am from bar function;
i am in zone.js afterTask;

i am in zone.js onZoneCreated;
i am in zone.js beforeTask;
i am from baz in setTimeout function;
i am in zone.js afterTask;

上述例子中将run方法分为了两个task,分别为同步task和异步task。fork方法会产生一个继承根zone的子类,并在fork函数中配置特定的钩子函数,形成独立的zone上下文,而run方法则是启动执行业务代码的对外接口。

使用Observable优化脏检查

使用OnPush的检查策略,如果修改了对象内部的值,此时不会进行脏检查,不会进行视图更新。此时可以选择Observable对象,通过手动调用markForCheck()方法进行优化,当前组件到根组件的路径上的所有组件都会进行变化检测。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Component({
...,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class TodosComponent implements OnInit{
@Input() todos: Observable<Todo[]>;
constructor(private cd: ChangeDetectorRef) {}
ngOnInit() {
this.todos.subscribe(todos => {
业务代码;
this.cd.markForCheck();
})
}
}