摘自
understanding-zones-and-change-detection-in-ionic-2-angular-2
zones-in-angular-2
每一个异步任务,都会先加入event queue, 每当stack空的时候,都会把一个异步任务从queue推入到event,这时 a turn happens:
ngZone
fork Parent Zone,实现了observable streams
:onTurnStart()
, onTurnDone()
等
一旦这个异步任务完成,
ObservableWrapper.subscribe(this.zone.onTurnDone, () => {
this.zone.run(() => {
this.tick();
});
});
tick() {
// perform change detection
this.changeDetectorRefs.forEach((detector) => {
detector.detectChanges();
});
}
这个tick()
会trigger 每一个component
的change detection,angular中每一个component
有自己的change detection,每次change detection, 整个component tree不需要完全更新。
run()
和runOutSizeAngular()
区别就在于 后者 does not emit an onTurnDone
event
An asynchronous task is a task that runs outside of the normal flow of the program – the program will go on executing without waiting for the asynchronous tasks to finish. When the async task does finish the application can handle it through the use of something like a callback or a promise. All asynchronous tasks in JavaScript are added to an event queue, and tasks in the event queue are executed by the event loop when there is time (i.e. when the stack is empty, the event loop will push the task onto the stack):
异步任务是运行在program normal流程以外的task - program可以继续执行,而不需要等待异步任务完成。当异步任务完成之后,application可以通过诸如callback/promise的用法来handle it。在Javascript当中,所有的异步任务都会被加入到事件队列当中,根据事件循环机制,当stack为空时,事件队列会把task push到stack当中执行。
image.png
The image above gives a visualisation of this. Normal (synchronous) function calls in a program are added to the “stack”, which are then executed from the top down. Any asynchronous function calls are added to the “event queue”. Once the stack is empty, the tasks in the queue can begin being processed.
上图显示的就是Javascript的事件循环机制。Normal function(同步任务)直接被push到stack当中,按照后进先出的规则执行。异步任务会被push到事件队列当中,当stack为空时,事件队列会popup相应的task到stack进行处理。
Angular 2 has its own zone – its own execution context – and it can detect when any asynchronous task starts or finishes within that zone. So any task that is executed within Angular 2’s zone will trigger a change. That’s an important concept to understand, because anything executed outside of Angular 2’s zone won’t trigger a change.
Angular2的zone - 它的执行上下文,在执行上下文中,它可以检测到所有的异步任务,并且监测它是否开始/结束。任何在zone中执行的task都会触发一次变化检测。而outside zone的所有task都不会触发。
When we first run our application our code will start executing. The root component is created and bootstrapped, our components are created, all of our constructor functions in our components will execute their code and so on. Eventually, once everything settles down (the stack is empty), our application reaches a nice resting state. Everything has been determined, and our views can reflect that.
我们首次run application,代码就开始执行了,Root组件被创建并且bootstrapped之后,其余组件会根据构造函数依次被创建,最终,一旦所有的任务执行完成之后(此时stack为空),application将处于'resting state',视图层绘制完成。
An application that doesn’t change is pretty boring though, in fact, one could argue it’s not an application at all! So once that initial state of the application is determined, there are a few ways that the state can change – all of which are caused by asynchronous tasks, which can be:
如果一个application一直保持不变的话,那其实是一件很boring(simple)的事情。一旦application的初始状态确定之后,有一些方法可以触发状态的更新 - 所有的这些方法都是由异步任务导致的:
Events like (click)
Http Requests like http.post
Timers like setTimeout
A user clicking a button can happen at any time, and that button click may cause a change to a view. A HTTP request could be triggered immediately by our application, or it could be triggered by a user clicking on a button, either way it could take anywhere from 10 milliseconds to 10 seconds (to never) to complete, and it could also cause a change to a view.
用户点击button事件可以发生在任何时候,button的点击也许会触发view层的某处变化。一个HTTP请求可以被application触发,也可以被用户点击button事件触发等等,这也会导致view层的变化。
Let’s take a look at the following example:
private myTitle: string = "Hello";
constructor(){
setTimeout(() => {
this.myTitle = "Goodbye!";
}, 5000);
}
<ion-title>{{myTitle}}</ion-title>
In this example we initially set the member variable myTitle
to "Hello"
. So once our application has finished loading and reaches that first 'resting state', myTitle
will be “Hello”
and the template will know to display it as the <ion-title>
.
在这个例子当中,初始化设置myTitle
的值为"Hello"
,一旦application完成初始化,达到'resting state',myTitle
的值为"Hello"
,模板将它显示在<ion-title>
中。
We have a timer here though, and after 5 seconds it is going to alter the state of the application by changing myTitle
to “Goodbye!”
. The problem is: how is Angular supposed to know about this? Well, we already know it uses zones to figure this out, but how?
在这里,还有一个定时器,5s之后将myTitle
的值改为"Goodbye!"
。问题是: Angular是如何利用zones
知道这些的?
As I mentioned, NgZone
is a special type of zone
created by Angular 2. It forks its parent zone
, which allows it to set up its own functionality on that zone. This extra functionality includes adding an onTurnDone
event which triggers when Angular’s zone finishes processing the current “turn”
– a “turn”
is what happens when the event loop pushes a task from the event queue (an asynchronous task) to the stack.
NgZone
是Angular创建的一种特殊类型的zone
。它forks父类zone
,也拥有自身的functionality,包括添加了onTurnDone
event - 当Angular zone
finish当前“turn”
的时候触发(“turn”
是指 当事件循环机制从事件队列当中拿出一条异步任务并push到stack。)
ObservableWrapper.subscribe(this.zone.onTurnDone, () => {
this.zone.run(() => {
this.tick();
});
});
tick() {
// perform change detection
this.changeDetectorRefs.forEach((detector) => {
detector.detectChanges();
});
}
we can see that every time a “turn”
completes in Angular’s zone it triggers the tick
function. If there were some asynchronous tasks, once the stack is emptied they would be added to the stack and executed – once those have finished executing Angular would be notified that another turn has completed. This tick
function then triggers change detection for each component every time a “turn”
completes. Each component in Angular 2 has its own change detection, so the entire component tree doesn’t need to be updated for every change. In effect, every time an asynchronous function finishes executing, Angular triggers tick
which checks for changes.
从代码中可以看出,每一次“turn”
完成,都会触发tick
function。如果有一些异步任务,一旦stack空时,它们就会被添加到stack当中然后被执行 - 一旦执行完成,Angular将被通知“turn”
完成。tick
function就会为每一个组件触发变化检测,整个组件树不需要每次变化检测都被更新。
The main thing to remember is that in order for something to trigger change detection it needs to be executed within Angular’s zone
:
只有run在zone
内部的逻辑才会触发变化检测:
this.zone.run(() => {
// some code
});
Similarly, we could also run something outside or Angular 2’s zone:
类似地,也可以在zone
外部run something:
this.zone.runOutsideAngular(() => {
// do something
.......
// reenter Angular zone
this.zone.run(() => {
// back in Angular town
});
});
This would instead run the code in Angular 2’s parent zone, the zone that Angular 2’s zone is forked from, so that it won’t trigger change detection.
此时不会触发变化检测。
NgZone
is basically a forked zone
that extends its API and adds some additional functionality to its execution context. One of the things it adds to the API is the following set of custom events we can subscribe to, as they are observable streams:
NgZone
是zones的子类,有一些是“Observable”
的 custom events,可以订阅它们:
onTurnStart() -
Notifies subscribers just before Angular’s event turn starts. Emits an event once per browser task that is handled by Angular.
onTurnDone() -
Notifies subscribers immediately after Angular’s zone is done processing the current turn and any micro tasks scheduled from that turn.
onEventDone() -
Notifies subscribers immediately after the final onTurnDone() callback before ending VM event.
Useful for testing to validate application state.
If “Observables”
and “Streams”
are super new to you, you might want to read our article on Taking advantage of Observables in Angular.
The main reason Angular adds its own event emitters instead of relying on beforeTask
and afterTask
callbacks, is that it has to keep track of timers and other micro tasks. It’s also nice that Observables
are used as an API to handle these events.
Since NgZone
is really just a fork of the global zone
, Angular has full control over when to run something inside its zone to perform change detection and when not. Why is that useful? Well, it turns out that we don’t always want Angular to magically perform change detection.
NgZone
是fork global zone
,因此Angular可以控制哪些运行可以触发变化检测,哪些可以不用。
As mentioned a couple of times, Zones monkey-patches pretty much any global asynchronous operations by the browser. And since NgZone
is just a fork of that zone which notifies the framework to perform change detection when an asynchronous operation has happened, it would also trigger change detection when things like mousemove
events fire.
We probably don’t want to perform change detection every time mousemove
is fired as it would slow down our application and results in very bad user experience.
对于mousemove
event,它不该每次被fired都要触发变化检测,不然application的性能会下降,也有很差的用户体验。
That’s why NgZone
comes with an API runOutsideAngular()
which performs a given task in NgZone’s parent zone, which does not emit an onTurnDone
event, hence no change detection is performed. To demonstrate this useful feature, let’s take look at the following code:
这也是为什么会有runOutsideAngular()
API的原因:
@Component({
selector: 'progress-bar',
template: `
<h3>Progress: {{progress}}</h3>
<button (click)="processWithinAngularZone()">Process within Angular zone</button>
`})
class ProgressBar {
progress: number = 0;
constructor(private zone: NgZone) {}
processWithinAngularZone() {
this.progress = 0;
this.increaseProgress(() => console.log('Done!'));
}}
Nothing special going on here. We have component that calls processWithinAngularZone()
when the button in the template is clicked. However, that method calls increaseProgress()
. Let’s take a closer look at this one:
increaseProgress(doneCallback: () => void) {
this.progress += 1;
console.log(`Current progress: ${this.progress}%`);
if (this.progress < 100) {
window.setTimeout(() => {
this.increaseProgress(doneCallback);
}, 10);
} else {
doneCallback();
}}
increaseProgress()
calls itself every 10 milliseconds until progress equals 100. Once it’s done, the given doneCallback
will execute. Notice how we use setTimeout()
to increase the progress.
increaseProgress()
每10ms就会call自身,一直到progress===100
,一旦progress===100
,将会执行doneCallback
。
Running this code in the browser, basically demonstrates what we already know. After each setTimeout()
call, Angular performs change detection and updates the view, which allows us to see how progress is increased every 10 milliseconds. It gets more interesting when we run this code outside Angular’s zone
. Let’s add a method that does exactly that.
在浏览器当中运行这些代码,每一次 setTimeout()
call之后都会触发变化检测,然后更新视图。如果将整个过程放在Angular’s zone
外:
processOutsideAngularZone() {
this.progress = 0;
this.zone.runOutsideAngular(() => {
this.increaseProgress(() => {
this.zone.run(() => {
console.log('Outside Done!');
});
});
});}
processOutsideAngularZone()
also calls increaseProgress()
but this time using runOutsideAngularZone()
which causes Angular not to be notified after each timeout. We access Angular’s zone by injecting it into our component using the NgZone
token.
The UI is not updated as progress increases. However, once increaseProgress()
is done, we run another task inside Angular’s zone
again using zone.run()
which in turn causes Angular to perform change detection which will update the view. In other words, instead of seeing progress increasing, all we see is the final value once it’s done.
这时,UI不会随着progress的增加而更新。一旦increaseProgress()
done,通过zone.run()
在Angular’s zone
内run console.log
, 触发变化检测,从而更新视图。
换言之,UI上只会显示progress的初始值和done之后的最终值,而不是progress从初始值一直递增到done的最终值。
网友评论