如果模板中有一些变量需要经常变动,那么频繁的变动将造成性能上的消耗,Angular为我们提供了NgZone服务,对于一些频繁的操作,可以不去触发变更检测。
使得Angular不跟踪变化。
重新跟踪变化 如果此时又想让Angular跟踪foo的变化,用其提供的run方法。
Angular 引入 Zone.js 以处理变更检测。Zone.js 使 angular 可以决定何时需要刷新UI。通常情况下,你不需要关注 Zone.js,因为其一直在正常工作。
简而言之:Zone.js 如何工作
Zone.js 对所有常见的异步 API(setTimeout, setInterval 和 Promise)打上了“补丁” 以追踪所有的异步操作。
Zone
Zone 是一种用于拦截和跟踪异步工作的机制。
Tasks
Zone.js 将会对每一个异步操作创建一个 task。一个 task 运行于一个 Zone 中。
NgZone
通常来说, 在 Angular APP 中,每个 task 都会在 “Angular” Zone 中运行,这个 Zone 被称为 NgZone。一个 Angular APP 中只存在一个 Angular Zone,而变更检测只会由 运行于这个 NgZone 中的异步操作触发。
Root Zone/Forks
在 Zone.js 中 zone 是存在层级关系的,你永远只会从顶层的 zone 进行操作 - 也就是 “root zone”。新的 zone 可以通过 fork root zone 创建。NgZone 也是由 root zone fork 而来。
ZoneSpecs
在 fork 一个 zone 时,一个新的 zone 将会由 ZoneSpec 创建。一个 ZoneSpec 可以只包含一个提供给新 child zone 的名字,也可以包含许多用于拦截特定 Zone/任务事件的钩子函数。
如果你想要了解更多有关于 Zone.js 的工作机制,浏览这个网页。
你并不一定要在 Angular 应用中使用 Zone.js (但是推荐你使用)
Zone.js 可以在 Angular 应用的启动阶段通过设置一个 noop 的 zone 参数以禁用。然而如果你选择在 Angular 中禁用 Zone,那你就必须自己手动控制 UI 刷新的时机(比如通过 ChangeDetectorRef.detectChanges() 函数)。
通常来说,不推荐放弃使用 Zone.js, 因为你放弃了自动化变更检测的便利,但是对于某些自定义元素(Angular elements),使用手动控制的方式就比较合理了。
Angular 对比 AngularJS 在变化检测上由原来的双向检测(父->子,子->父)变为了单向(父->子)。所以每一次变化检测都会确定性地收敛,一般页面即使有几千个组件,变化检测也可以做几毫秒内完成,所以大家开发时很少会遇到性能瓶颈。(即使是AngularJS也很少遇到瓶颈)
https://angular.io/api/core/NgZone#ngzone
class NgZone {
static isInAngularZone(): boolean
static assertInAngularZone(): void
static assertNotInAngularZone(): void
constructor(__0)
hasPendingMacrotasks: boolean
hasPendingMicrotasks: boolean
isStable: boolean
onUnstable: EventEmitter<any>
onMicrotaskEmpty: EventEmitter<any>
onStable: EventEmitter<any>
onError: EventEmitter<any>
run<T>(fn: (...args: any[]) => T, applyThis?: any, applyArgs?: any[]): T
runTask<T>(fn: (...args: any[]) => T, applyThis?: any, applyArgs?: any[], name?: string): T
runGuarded<T>(fn: (...args: any[]) => T, applyThis?: any, applyArgs?: any[]): T
runOutsideAngular<T>(fn: (...args: any[]) => T): T
}
’Example:
import {Component, NgZone} from '@angular/core';
import {NgIf} from '@angular/common';
@Component({
selector: 'ng-zone-demo',
template: `
<h2>Demo: NgZone</h2>
<p>Progress: {{progress}}%</p>
<p *ngIf="progress >= 100">Done processing {{label}} of Angular zone!</p>
<button (click)="processWithinAngularZone()">Process within Angular zone</button>
<button (click)="processOutsideOfAngularZone()">Process outside of Angular zone</button>
`,
})
export class NgZoneDemo {
progress: number = 0;
label: string;
constructor(private _ngZone: NgZone) {}
// Loop inside the Angular zone
// so the UI DOES refresh after each setTimeout cycle
processWithinAngularZone() {
this.label = 'inside';
this.progress = 0;
this._increaseProgress(() => console.log('Inside Done!'));
}
// Loop outside of the Angular zone
// so the UI DOES NOT refresh after each setTimeout cycle
processOutsideOfAngularZone() {
this.label = 'outside';
this.progress = 0;
this._ngZone.runOutsideAngular(() => {
this._increaseProgress(() => {
// reenter the Angular zone and display done
this._ngZone.run(() => { console.log('Outside Done!'); });
});
});
}
_increaseProgress(doneCallback: () => void) {
this.progress += 1;
console.log(`Current progress: ${this.progress}%`);
if (this.progress < 100) {
window.setTimeout(() => this._increaseProgress(doneCallback), 10);
} else {
doneCallback();
}
}
}