详解Angular中的变更检测(四)- NgZone 1

83 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第2天,点击查看活动详情

Zone 是跨异步任务而持久存在的执行上下文。

简单来说,在 Angular APP 中,每个 Task 都会在 Angular 的 Zone 中运行,这个 Zone 被称为 NgZone。一个 Angular APP 中只存在一个 Angular Zone,而变更检测只会由运行于这个 NgZone 中的异步操作触发。

我们可以fork出来一个zone进行进一步封装和隔离异步行为

总结:
Angular 通过 Zone.js 创建了一个自己的区域并称之为 NgZone,Angular 应用中所有的异步操作都运行在这个区域中。

Zone和执行上下文

Zone 提供了在异步任务之间的执行上下文。执行上下文是一个抽象概念,用于在当前执行的代码中保存有关环境的信息。

const callback = function() {
  console.log('setTimeout callback context is', this);
}

const ctx1 = { name: 'ctx1' };
const ctx2 = { name: 'ctx2' };

const func = function() {
  console.log('caller context is', this);
  setTimeout(callback);
}

func.apply(ctx1);
func.apply(ctx2);

setTimeout() 回调中的 this 值可能会有所不同,具体取决于 setTimeout() 的调用时机。因此,你可能会在异步操作中丢失上下文。

Zone 提供了不同于 this 的新的 Zone 上下文,该 Zone 上下文在异步操作中保持不变。在下例中,新的 Zone 上下文称为 zoneThis。

zone.run(() => {
  // now you are in a zone
  expect(zoneThis).toBe(zone);
  setTimeout(function() {
    // the zoneThis context will be the same zone
    // when the setTimeout is scheduled
    expect(zoneThis).toBe(zone);
  });
});

新的上下文 zoneThis 可以从 setTimeout() 的回调函数中检索出来,这个上下文和调用 setTimeout() 时的上下文是一样的。要获取此上下文,可以调用 Zone.current

Zone和异步生命周期钩子

zone.js 可以创建在异步操作中持久存在的上下文,并为异步操作提供生命周期钩子。

const zone = Zone.current.fork({
  name: 'zone',
  onScheduleTask: function(delegate, curr, target, task) {
    console.log('new task is scheduled:', task.type, task.source);
    return delegate.scheduleTask(target, task);
  },
  onInvokeTask: function(delegate, curr, target, task, applyThis, applyArgs) {
    console.log('task will be invoked:', task.type, task.source);
    return delegate.invokeTask(target, task, applyThis, applyArgs);
  },
  onHasTask: function(delegate, curr, target, hasTaskState) {
    console.log('task state changed in the zone:', hasTaskState);
    return delegate.hasTask(target, hasTaskState);
  },
  onInvoke: function(delegate, curr, target, callback, applyThis, applyArgs) {
    console.log('the callback will be invoked:', callback);
    return delegate.invoke(target, callback, applyThis, applyArgs);
  }
});
zone.run(() => {
  setTimeout(() => {
    console.log('timeout callback is invoked.');
  });
});

上面的示例创建了一个具有多个钩子的 Zone

当任务状态更改时,就会触发 onXXXTask 钩子。Zone 任务的概念与 JavaScript VM 中任务的概念非常相似:

  • macroTask:比如 setTimeout()
  • microTask:比如 Promise.then()
  • eventTask:比如 element.addEventListener()

这些钩子在以下情况下触发:

钩子详情
onScheduleTask在计划新的异步任务时触发,比如调用 setTimeout() 时。
onInvokeTask在异步任务即将执行时触发,比如 setTimeout() 的回调即将执行时。
onHasTask当 Zone 内的一种任务的状态从稳定变为不稳定或从不稳定变为稳定时触发。状态“稳定”表示该 Zone 内没有任务,而“不稳定”表示在该 Zone 中计划了新任务。
onInvoke将在 Zone 中执行同步函数时触发。

使用这些钩子,Zone 可以监视 Zone 内所有同步和异步操作的状态。

上面的示例返回以下输出:

the callback will be invoked: () => {
  setTimeout(() => {
    console.log('timeout callback is invoked.');
  });
}
new task is scheduled: macroTask setTimeout
task state changed in the zone: { microTask: false,
  macroTask: true,
  eventTask: false,
  change: 'macroTask' }
task will be invoked macroTask: setTimeout
timeout callback is invoked.
  task state changed in the zone: { microTask: false,
  macroTask: false,
  eventTask: false,
  change: 'macroTask' }

Zone 的所有功能均由名为 Zone.js 的库提供。该库通过猴子补丁拦截异步 API 来实现这些功能。猴子补丁是一种在运行时添加或修改函数默认行为而无需更改源代码的技术。

是不是对Zone的概念和生命周期有了基本了解了呢,下一篇文章会继续分享NgZone。