啸达同学刚写zone.js系列就说过,NgZone影响着Angular中的变更检测,历时一个多月的笔耕不辍,终于到了他初次下笔时的目的地~
zone.js系列
- zone.js由入门到放弃之一——通过一场游戏认识zone.js
- zone.js由入门到放弃之二——zone.js API大练兵
- zone.js由入门到放弃之三——zone.js 源码分析【setTimeout篇】
- zone.js由入门到放弃之四——Angular对zone.js的应用
初见NgZone
其实在上一篇文章中,大家已经初步窥探过NgZone的芳容了。而且我们也知道了,在NgZone中维护了OuterZone和InnerZone两个Zone。今天的这篇文章,我们主要分析一下InnerZone,并看一下InnerZone是如何跟Angular的变更检测联系到一起的。
InnerZone四方法
NgZone中InnerZone的创建是通过forkInnerZoneWithAngularBehavior
完成的,创建过程的简化版如下,其中又能看到很多熟悉的勾子函数。这里简单复习一下这几个勾子的意义:
onInvokeTask
:zone.js会在初始化的时候将异步方法都Pathc成ZoneTask,从而跟踪异步任务的执行情况的。onInvokeTask
就是其中的一个勾子函数,它会在异步任务执行回调的时候触发。onInvoke
:onInvoke
会在我们手动执行zone.run()的时候执行。onHasTask
:是针对整个任务队列状态改变的监听,当检测任务队列中有任务进入、或是有任务执行完出队列的时候会被执行。onHandleError
:当有异常抛出时被执行
InnerZone对异步任务的控制精华基本上就全部浓缩在这几个勾子函数中了,与此同时,为了更好地配合对异步任务的跟踪,NgZone中还定义了很多状态监控字段。只有理清这些字段的含义才能继续往下深入代码。
不熟悉zone.js原理的可以回看一下zone.js由入门到放弃之一和zone.js由入门到放弃之二(链接见文首)
function forkInnerZoneWithAngularBehavior(zone: NgZonePrivate) {
zone._inner = zone._inner.fork({
name: 'angular',
properties: <any>{'isAngularZone': true},
onInvokeTask: (...): any => {
...
},
onInvoke: (...): any => {
...
},
onHasTask: (...): any => {
...
},
onHandleError: (...): any => {
...
},
});
}
InnerZone五状态
接下来这几个状态属性会贯穿在后面的源码分析的全部过程中,我们也会通过对这几个状态的跟踪了解一下InnerZone事件跟踪的原理。
- hasPendingMacrotasks: boolean 队列中是否有待执行的宏任务
- hasPendingMicrotasks: boolean 队列中是否有待执行的微任务
- _nesting: number 队列中待执行任务的个数
- isStable: boolean 当任务队列中既没有待执行的宏任务,也没有待执行的微任务时,isStable为ture,表示当前是个稳定的状态。反之则代表非稳定状态。
- lastRequestAnimationFrameId: number 这个状态有些特别,它是一个延时器,后面会展开解释。
代码走读
前面在介绍zone.js的时候我们说过,zone.js把异步任务分为MacroTask、MicroTask和Event三种。今天我们就分别把这三种任务都按流程分析一遍。从难易程度上看,MacroTask最简单,Event相对最复杂。接下来,我们就按照这个顺序讲解。
MacroTask
之前在zone.js由入门到放弃之三中,详细介绍过zone.js对setTimeout的Patch过程,如果不了解具体过程的强烈建议先浏览一下那篇文章。
这一次,我们还是通过个setTimeout事件来跟踪NgZone的处理过程,测试代码很简单,如下所示。
export class AppComponent implements OnInit {
title = 'ngzone-process';
ngOnInit(): void {
setTimeout(() => {
console.log('[setTimeout] run in next 5s');
}, 5000);
}
ngDoCheck() {
console.log('rendering...');
}
}
因为zone.js可以感知到任务队列的变化情况,所以当setTimeout
执行时,它可以知道当前有一个宏任务来了,同时会触发onHasTask勾子。
onHasTask
当onHasTask
"检测"到有宏任务到来时,会把hasPendingMacrotasks
设置为true。
onHasTask:
(delegate: ZoneDelegate, current: Zone, target: Zone, hasTaskState: HasTaskState) => {
delegate.hasTask(target, hasTaskState);
if (current === target) {
// ...
} else if (hasTaskState.change == 'macroTask') {
zone.hasPendingMacrotasks = hasTaskState.macroTask;
}
}
},
此时,NgZone中的几个状态值大概是这个样子的,hasPendingMacrotasks变为true,表示当前有一个待执行的MacroTask。
接下来,zone.js会通过调用scheduleFn
,并把封装后的回调函数放在Timer队列中等待时钟到达。
hasPendingMacrotasks | hasPendingMicrotasks | _nesting | isStable | lastRequestAnimationFrameId |
true | false | 0 | true | -1 |
onInvokeTask
当时钟到达以后,事件循环会把封装后的回调函数放在任务队列中等待执行。当执行到回调时,回调会触发task.invoke
函数,接下来就会唤醒onInvokeTask勾子函数。
onInvokeTask:
(delegate: ZoneDelegate, current: Zone, target: Zone, task: Task, applyThis: any,
applyArgs: any): any => {
try {
onEnter(zone);
// 执行真正的回调
标签:ApplicationRef,zone,js,NgZone,任务,源码,组件,执行
From: https://blog.51cto.com/u_16152776/7528554