🅰️ Angular Zone.js 异步追踪原理

Zone/ZoneDelegate/ZoneSpec/Task 四大核心,完整拆解 Angular 变更检测的异步追踪机制

源码级别解析 · zone.ts · zone-impl.ts · promise.ts · timers.ts
2026-03-19 | 每日技术深度解读

什么是 Zone.js?

Zone.js 是 Angular 团队开发的异步执行上下文追踪库

核心职责:

  • 拦截所有异步操作(setTimeout、Promise、事件等)
  • 保持异步调用链中的 Zone 上下文
  • 追踪任务状态(microTask/macroTask/eventTask)
  • 提供统一的错误处理机制
  • 通知 Angular 何时触发变更检测

本质: Monkey Patch 所有浏览器异步 API,在调用前后注入钩子

核心架构概览

┌─────────────────────────────────────────────────────────────────┐ │ Zone.js 架构 │ ├─────────────────────────────────────────────────────────────────┤ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ Zone │───▶│ ZoneDelegate│───▶│ ZoneSpec │ │ │ │ (执行上下文) │ │ (委托模式) │ │ (配置规则) │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ │ │ │ ▼ ▼ │ │ ┌─────────────┐ ┌─────────────┐ │ │ │ ZoneTask │ │ NgZone │ │ │ │ (任务抽象) │ │ (Angular封装)│ │ │ └─────────────┘ └─────────────┘ │ └─────────────────────────────────────────────────────────────────┘

四大核心:Zone、ZoneDelegate、ZoneSpec、Task

四大核心概念

概念 职责 类比
Zone 执行上下文,管理异步调用链 线程本地存储 (ThreadLocal)
ZoneSpec Zone 的配置规则和钩子函数 拦截器配置
ZoneDelegate 委托模式,协调父子 Zone 事件冒泡机制
Task 异步任务抽象 VM 任务队列

Zone 接口定义

interface Zone {
  readonly parent: Zone | null;      // 父 Zone
  readonly name: string;             // Zone 名称(调试用)
  
  get(key: string): any;             // 获取属性
  getZoneWith(key: string): Zone;    // 查找定义 key 的 Zone
  
  fork(zoneSpec: ZoneSpec): Zone;    // 创建子 Zone
  wrap<F>(callback: F, source: string): F;  // 包装回调
  run<T>(callback, ...args): T;      // 在 Zone 中执行
  runGuarded<T>(callback, ...args): T;     // 执行 + 错误处理
  runTask(task, ...args): any;       // 执行任务
  
  scheduleMicroTask(...): MicroTask;
  scheduleMacroTask(...): MacroTask;
  scheduleEventTask(...): EventTask;
  scheduleTask<T>(task: T): T;
  cancelTask(task: Task): any;
}

ZoneImpl 核心实现

class ZoneImpl implements Zone {
  private _parent: ZoneImpl | null;
  private _name: string;
  private _properties: {[key: string]: any};
  private _zoneDelegate: _ZoneDelegate;

  constructor(parent: ZoneImpl | null, zoneSpec: ZoneSpec | null) {
    this._parent = parent;
    this._name = zoneSpec?.name || '<root>';
    this._properties = zoneSpec?.properties || {};
    this._zoneDelegate = new _ZoneDelegate(
      this,
      this._parent?._zoneDelegate,
      zoneSpec
    );
  }

  static get current(): Zone {
    return _currentZoneFrame.zone;  // 从栈帧获取当前 Zone
  }
}

关键:_currentZoneFrame 是全局变量,存储当前执行上下文的 Zone 栈帧

Zone.fork() 创建子 Zone

public fork(zoneSpec: ZoneSpec): Zone {
  if (!zoneSpec) throw new Error('ZoneSpec required!');
  return this._zoneDelegate.fork(this, zoneSpec);
}

// ZoneDelegate.fork 实现
fork(targetZone: Zone, zoneSpec: ZoneSpec): Zone {
  return this._forkZS
    ? this._forkZS.onFork!(this._forkDlgt!, this.zone, targetZone, zoneSpec)
    : new ZoneImpl(targetZone, zoneSpec);  // 默认行为
}

fork 流程:

  1. 调用 ZoneDelegate.fork
  2. 检查是否有 onFork 钩子
  3. 有钩子 → 执行钩子;无钩子 → 创建新 ZoneImpl
  4. 子 Zone 继承父 Zone 的属性和委托链

Zone.run() 执行上下文切换

public run<T>(
  callback: (...args: any[]) => T,
  applyThis?: any,
  applyArgs?: any[],
  source?: string
): T {
  // 1. 保存当前栈帧,切换到新 Zone
  _currentZoneFrame = { parent: _currentZoneFrame, zone: this };
  
  try {
    // 2. 通过委托执行回调
    return this._zoneDelegate.invoke(
      this, callback, applyThis, applyArgs, source
    );
  } finally {
    // 3. 恢复原栈帧(无论成功或异常)
    _currentZoneFrame = _currentZoneFrame.parent!;
  }
}

Zone 栈帧链: 类似调用栈,_currentZoneFrame 形成链表结构

Zone.runGuarded() 错误处理

public runGuarded<T>(
  callback: (...args: any[]) => T,
  applyThis: any = null,
  applyArgs?: any[],
  source?: string
): T {
  _currentZoneFrame = { parent: _currentZoneFrame, zone: this };
  try {
    try {
      return this._zoneDelegate.invoke(
        this, callback, applyThis, applyArgs, source
      );
    } catch (error) {
      // 捕获错误,交给 Zone 错误处理
      if (this._zoneDelegate.handleError(this, error)) {
        throw error;  // 如果未被处理,重新抛出
      }
    }
  } finally {
    _currentZoneFrame = _currentZoneFrame.parent!;
  }
}

Zone.wrap() 包装回调

public wrap<T extends Function>(callback: T, source: string): T {
  if (typeof callback !== 'function') {
    throw new Error('Expecting function got: ' + callback);
  }
  
  // 1. 通过委托拦截回调
  const _callback = this._zoneDelegate.intercept(this, callback, source);
  const zone: ZoneImpl = this;
  
  // 2. 返回包装函数,保证在原 Zone 中执行
  return function(this: unknown) {
    return zone.runGuarded(_callback, this, arguments, source);
  } as any as T;
}

用途: 将回调绑定到特定 Zone,跨异步操作保持上下文

例如: setTimeout 的回调被 wrap 后,执行时恢复原 Zone

ZoneSpec 配置接口

interface ZoneSpec {
  name: string;                          // Zone 名称(必须)
  properties?: {[key: string]: any};     // 自定义属性
  
  // 钩子函数(全部可选)
  onFork?: (parentDelegate, current, target, spec) => Zone;
  onIntercept?: (parentDelegate, current, target, delegate, source) => Function;
  onInvoke?: (parentDelegate, current, target, delegate, this, args, source) => any;
  onHandleError?: (parentDelegate, current, target, error) => boolean;
  onScheduleTask?: (parentDelegate, current, target, task) => Task;
  onInvokeTask?: (parentDelegate, current, target, task, this, args) => any;
  onCancelTask?: (parentDelegate, current, target, task) => any;
  onHasTask?: (parentDelegate, current, target, hasTaskState) => void;
}

ZoneSpec 钩子函数详解

钩子 触发时机 典型用途
onFork fork() 调用时 自定义子 Zone 创建逻辑
onIntercept wrap() 调用时 修改或记录回调
onInvoke run()/runGuarded() 性能监控、日志
onHandleError 异常发生时 全局错误处理
onScheduleTask 调度任务时 任务拦截、Mock
onHasTask 任务队列状态变化 检测稳定性(Angular 核心用途)

ZoneDelegate 委托模式

为什么需要 ZoneDelegate?

子 Zone 不能直接调用父 Zone 的方法,否则回调会被绑定到父 Zone

interface ZoneDelegate {
  zone: Zone;
  
  fork(targetZone: Zone, zoneSpec: ZoneSpec): Zone;
  intercept(targetZone: Zone, callback: Function, source: string): Function;
  invoke(targetZone: Zone, callback: Function, ...): any;
  handleError(targetZone: Zone, error: any): boolean;
  scheduleTask(targetZone: Zone, task: Task): Task;
  invokeTask(targetZone: Zone, task: Task, ...): any;
  cancelTask(targetZone: Zone, task: Task): any;
  hasTask(targetZone: Zone, isEmpty: HasTaskState): void;
}

ZoneDelegate 实现原理

class _ZoneDelegate implements ZoneDelegate {
  private _taskCounts: {[key in TaskType]: number} = {
    'microTask': 0, 'macroTask': 0, 'eventTask': 0
  };
  
  // 存储各钩子的 ZoneSpec 和委托链
  private _forkZS: ZoneSpec | null;
  private _forkDlgt: _ZoneDelegate | null;
  private _forkCurrZone: Zone | null;
  // ... 其他钩子的存储
  
  constructor(zone: Zone, parentDelegate: _ZoneDelegate | null, zoneSpec: ZoneSpec | null) {
    // 决定使用当前 ZoneSpec 还是委托给父级
    this._forkZS = zoneSpec?.onFork ? zoneSpec : parentDelegate?._forkZS;
    this._forkDlgt = zoneSpec?.onFork ? parentDelegate : parentDelegate?._forkDlgt;
    // ...
  }
}

Task 三种类型

MicroTask

当前栈清空后立即执行

  • Promise.then
  • queueMicrotask
  • MutationObserver

特点:不可取消,保证执行一次

MacroTask

在下一个 VM 轮次执行

  • setTimeout
  • setInterval
  • requestAnimationFrame
  • XHR

特点:可取消,有延迟

EventTask

事件监听器,可能执行零次或多次

  • addEventListener
  • WebSocket.onmessage

特点:不可预测的执行时机

Task 状态机

┌──────────────────────────────────────────────┐ │ Task 状态转换 │ └──────────────────────────────────────────────┘ │ ▼ ┌─────────────┐ scheduleTask() ┌─────────────┐ runTask() ┌─────────────┐ │notScheduled │ ─────────────────▶ │ scheduling │ ─────────────▶│ scheduled │ └─────────────┘ └─────────────┘ └─────────────┘ ▲ │ │ │ │ │ │ error │ runTask() │ ▼ │ │ ┌─────────────┐ ▼ │ │ unknown │ ┌─────────────┐ │ └─────────────┘ │ running │ │ └─────────────┘ │ │ │ ┌─────────────────────────────────────────┤ │ │ │ │ MacroTask/ EventTask/ │ one-shot periodic │ │ │ └────────────────────┴───────────────────────┐ ┌───────────┘ │ │ ▼ ▼ ┌─────────────┐ │ scheduled │ └─────────────┘

ZoneTask 实现

class ZoneTask<T extends TaskType> implements Task {
  public type: T;
  public source: string;
  public callback: Function;
  public invoke: Function;
  public data?: TaskData;
  public scheduleFn?: (task: Task) => void;
  public cancelFn?: (task: Task) => void;
  public runCount: number = 0;
  
  _zone: ZoneImpl | null = null;
  _zoneDelegates: _ZoneDelegate[] | null = null;
  _state: TaskState = 'notScheduled';

  constructor(type, source, callback, options, scheduleFn, cancelFn) {
    this.invoke = function() {
      return ZoneTask.invokeTask.call(global, self, this, arguments);
    };
  }
}

scheduleMicroTask() 微任务调度

scheduleMicroTask(
  source: string,
  callback: Function,
  data?: TaskData,
  customSchedule?: (task: Task) => void
): MicroTask {
  return this.scheduleTask(
    new ZoneTask(microTask, source, callback, data, customSchedule, undefined)
  );
}

// 内部 scheduleMicroTask 实现(zone-impl.ts)
function scheduleMicroTask(task?: MicroTask) {
  // 使用原生 Promise 或 queueMicrotask 调度
  if (nativeMicroTask) {
    nativeMicroTask(task.invoke);
  } else {
    nativePromise.resolve().then(task.invoke);
  }
}

scheduleMacroTask() 宏任务调度

scheduleMacroTask(
  source: string,
  callback: Function,
  data?: TaskData,
  customSchedule?: (task: Task) => void,
  customCancel?: (task: Task) => void
): MacroTask {
  return this.scheduleTask(
    new ZoneTask(macroTask, source, callback, data, customSchedule, customCancel)
  );
}

// 使用示例:setTimeout 内部
const task = scheduleMacroTaskWithCurrentZone(
  'setTimeout',
  callback,
  { delay, isPeriodic: false },
  scheduleTask,  // 调用原生 setTimeout
  clearTask      // 调用原生 clearTimeout
);

scheduleEventTask() 事件任务调度

scheduleEventTask(
  source: string,
  callback: Function,
  data?: TaskData,
  customSchedule?: (task: Task) => void,
  customCancel?: (task: Task) => void
): EventTask {
  return this.scheduleTask(
    new ZoneTask(eventTask, source, callback, data, customSchedule, customCancel)
  );
}

// 使用示例:addEventListener 内部
const task = zone.scheduleEventTask(
  'addEventListener',
  listener,
  { target, eventName },
  () => nativeAddEventListener.call(target, eventName, task.invoke),
  () => nativeRemoveEventListener.call(target, eventName, task.invoke)
);

runTask() 任务执行

runTask(task: Task, applyThis?: any, applyArgs?: any): any {
  if (task.zone != this) {
    throw new Error('A task can only run in its creation zone!');
  }

  const {type, data: {isPeriodic, isRefreshable} = {}} = task;
  
  // 状态转换: scheduled -> running
  zoneTask._transitionTo(running, scheduled);
  _currentTask = zoneTask;
  _currentZoneFrame = { parent: _currentZoneFrame, zone: this };

  try {
    return this._zoneDelegate.invokeTask(this, zoneTask, applyThis, applyArgs);
  } finally {
    // 状态恢复: running -> scheduled (EventTask) 或 notScheduled (一次性 MacroTask)
    if (type == eventTask || isPeriodic) {
      zoneTask._transitionTo(scheduled, running);
    } else {
      this._updateTaskCount(zoneTask, -1);
      zoneTask._transitionTo(notScheduled, running);
    }
    _currentZoneFrame = _currentZoneFrame.parent!;
    _currentTask = previousTask;
  }
}

cancelTask() 任务取消

cancelTask(task: Task): any {
  if (task.zone != this) {
    throw new Error('A task can only be cancelled in its creation zone!');
  }
  
  if (task.state !== scheduled && task.state !== running) {
    return;  // 已完成或已取消的任务不能再次取消
  }

  // 状态转换: scheduled/running -> canceling
  zoneTask._transitionTo(canceling, scheduled, running);
  
  try {
    this._zoneDelegate.cancelTask(this, task);  // 调用 cancelFn
  } catch (err) {
    zoneTask._transitionTo(unknown, canceling);  // 错误时状态为 unknown
    throw err;
  }
  
  this._updateTaskCount(zoneTask, -1);  // 减少任务计数
  zoneTask._transitionTo(notScheduled, canceling);
  task.runCount = -1;  // 标记为已取消
}

onHasTask() 状态通知

type HasTaskState = {
  microTask: boolean;
  macroTask: boolean;
  eventTask: boolean;
  change: TaskType;  // 哪种类型触发了变化
};

// ZoneDelegate._updateTaskCount
_updateTaskCount(type: TaskType, count: number) {
  const counts = this._taskCounts;
  const prev = counts[type];
  const next = counts[type] = prev + count;
  
  // 当从 0->1 或 1->0 时触发 onHasTask
  if (prev == 0 || next == 0) {
    const isEmpty: HasTaskState = {
      microTask: counts['microTask'] > 0,
      macroTask: counts['macroTask'] > 0,
      eventTask: counts['eventTask'] > 0,
      change: type
    };
    this.hasTask(this._zone, isEmpty);
  }
}

Angular 用途: 检测应用稳定性(所有任务队列为空时触发变更检测)

Promise 补丁原理

核心思路: 替换全局 Promise 为 ZoneAwarePromise

// 保存原生 Promise
const NativePromise = global['Promise'];

// 替换为 Zone 感知的 Promise
global['Promise'] = ZoneAwarePromise;

// 同时 patch 原生 Promise 的 then
patchThen(NativePromise);

// patchThen 实现
function patchThen(Ctor: Function) {
  const proto = Ctor.prototype;
  const originalThen = proto.then;
  
  proto.then = function(onResolve, onReject) {
    const wrapped = new ZoneAwarePromise((resolve, reject) => {
      originalThen.call(this, resolve, reject);
    });
    return wrapped.then(onResolve, onReject);  // 走 ZoneAwarePromise.then
  };
}

ZoneAwarePromise 类实现

class ZoneAwarePromise<R> implements Promise<R> {
  // 使用 Symbol 存储私有状态,避免冲突
  [symbolState] = UNRESOLVED;  // null = pending, true = resolved, false = rejected
  [symbolValue] = [];          // 回调队列

  constructor(executor) {
    try {
      executor(
        once(makeResolver(this, RESOLVED)),
        once(makeResolver(this, REJECTED))
      );
    } catch (error) {
      resolvePromise(this, false, error);
    }
  }

  then(onFulfilled?, onRejected?) {
    const chainPromise = new ZoneAwarePromise(noop);
    const zone = Zone.current;  // 捕获当前 Zone!
    
    if (this[symbolState] == UNRESOLVED) {
      this[symbolValue].push(zone, chainPromise, onFulfilled, onRejected);
    } else {
      scheduleResolveOrReject(this, zone, chainPromise, onFulfilled, onRejected);
    }
    return chainPromise;
  }
}

Promise.then 拦截机制

function scheduleResolveOrReject(
  promise, zone, chainPromise, onFulfilled, onRejected
) {
  const promiseState = promise[symbolState];
  const delegate = promiseState
    ? typeof onFulfilled === 'function' ? onFulfilled : forwardResolution
    : typeof onRejected === 'function' ? onRejected : forwardRejection;

  // 关键:在捕获的 Zone 中调度微任务
  zone.scheduleMicroTask(
    'Promise.then',
    () => {
      try {
        const value = zone.run(delegate, undefined, [promise[symbolValue]]);
        resolvePromise(chainPromise, true, value);
      } catch (error) {
        resolvePromise(chainPromise, false, error);
      }
    },
    chainPromise as TaskData
  );
}

核心: Promise.then 回调通过 scheduleMicroTask 在原 Zone 中执行

Promise 状态管理

function resolvePromise(promise, state, value) {
  if (promise === value) {
    throw new TypeError('Promise resolved with itself');
  }
  
  if (promise[symbolState] === UNRESOLVED) {
    promise[symbolState] = state;
    const queue = promise[symbolValue];
    promise[symbolValue] = value;

    // 处理已排队的回调
    for (let i = 0; i < queue.length; ) {
      scheduleResolveOrReject(
        promise, queue[i++], queue[i++], queue[i++], queue[i++]
      );
    }
    
    // 未处理的 rejection
    if (queue.length == 0 && state == REJECTED) {
      promise[symbolState] = REJECTED_NO_CATCH;
      const error = new Error('Uncaught (in promise): ' + value);
      error.rejection = value;
      error.promise = promise;
      error.zone = Zone.current;
      error.task = Zone.currentTask;
      _uncaughtPromiseErrors.push(error);
      api.scheduleMicroTask();  // 触发错误处理
    }
  }
}

Timer 补丁原理

┌─────────────────────────────────────────────────────────────────┐ │ setTimeout 拦截流程 │ ├─────────────────────────────────────────────────────────────────┤ │ 用户代码 │ │ setTimeout(callback, 100) │ │ │ │ │ ▼ │ │ patchMethod(window, 'setTimeout') │ │ │ │ │ │ 1. 包装 callback 为 Zone 感知版本 │ │ │ 2. 创建 MacroTask │ │ │ 3. 调用原生 setTimeout(task.invoke, delay) │ │ ▼ │ │ 原生 setTimeout │ │ │ │ │ │ 100ms 后 │ │ ▼ │ │ task.invoke() → Zone.runTask() → callback() │ │ │ │ │ ▼ │ │ 触发 onHasTask(任务完成) │ └─────────────────────────────────────────────────────────────────┘

patchTimer 实现

export function patchTimer(window, setName, cancelName, nameSuffix) {
  let setNative = patchMethod(window, setName, (delegate) =>
    function(self, args) {
      if (typeof args[0] === 'function') {
        const options = {
          isPeriodic: nameSuffix === 'Interval',
          delay: args[1] || 0,
          args: args
        };
        
        // 包装回调
        const callback = args[0];
        args[0] = function() {
          try {
            return callback.apply(this, arguments);
          } finally {
            // 非周期任务完成后清理
            if (!options.isPeriodic) {
              delete tasksByHandleId[options.handleId];
            }
          }
        };
        
        // 创建宏任务
        const task = scheduleMacroTaskWithCurrentZone(
          setName, args[0], options, scheduleTask, clearTask
        );
        return task.data.handleId;
      }
      return delegate.apply(window, args);
    }
  );
}

setTimeout 包装细节

function scheduleTask(task: Task) {
  const data = task.data;
  
  // 将回调替换为 task.invoke
  data.args[0] = function() {
    return task.invoke.apply(this, arguments);
  };

  // 调用原生 setTimeout
  const handleId = setNative.apply(window, data.args);
  data.handleId = handleId;
  
  // 保存 task 映射,用于 clearTimeout
  tasksByHandleId[handleId] = task;
  
  return task;
}

// task.invoke 内部
this.invoke = function() {
  return ZoneTask.invokeTask.call(global, self, this, arguments);
};
// 最终调用 zone.runTask(task)

setInterval 特殊处理

// setInterval 的 isPeriodic = true
const options = {
  isPeriodic: nameSuffix === 'Interval',  // true
  delay: args[1] || 0,
  args: args
};

// runTask 中的处理
if (type == eventTask || isPeriodic) {
  // 周期任务执行后状态回到 scheduled
  zoneTask._transitionTo(scheduled, running);
} else {
  // 一次性任务执行后状态变为 notScheduled
  this._updateTaskCount(zoneTask, -1);
  zoneTask._transitionTo(notScheduled, running);
}

// finally 块中的清理
if (!isPeriodic && !isRefreshable) {
  delete tasksByHandleId[handleId];  // 只有非周期任务才清理
}

EventTarget 补丁

Zone.__load_patch('EventTarget', (global, Zone, api) => {
  patchEvent(global, api);
  eventTargetPatch(global, api);
});

// eventTargetPatch 实现
export function eventTargetPatch(_global, api) {
  const EVENT_TARGET = _global['EventTarget'];
  
  api.patchEventTarget(_global, api, [EVENT_TARGET.prototype]);
}

// patchEventTarget 内部
function patchEventTarget(global, api, apis) {
  for (const obj of apis) {
    // 拦截 addEventListener/removeEventListener
    patchMethod(obj, 'addEventListener', (delegate) =>
      function(self, args) {
        const [eventName, callback, options] = args;
        // 创建 EventTask 替代原回调
        const task = zone.scheduleEventTask(eventName, callback, ...);
        args[1] = task.invoke;  // 替换回调
        return delegate.apply(self, args);
      }
    );
  }
}

addEventListener 拦截机制

// 简化的 addEventListener 包装
const nativeAdd = target.addEventListener;
target.addEventListener = function(eventName, callback, options) {
  if (typeof callback !== 'function') {
    return nativeAdd.call(this, eventName, callback, options);
  }
  
  const zone = Zone.current;
  
  // 创建 EventTask
  const task = zone.scheduleEventTask(
    'addEventListener:' + eventName,
    callback,
    { target: this, eventName, options },
    // scheduleFn: 调用原生 addEventListener
    () => nativeAdd.call(this, eventName, task.invoke, options),
    // cancelFn: 调用原生 removeEventListener
    () => nativeRemove.call(this, eventName, task.invoke, options)
  );
  
  // 保存 task 引用用于移除
  callback[zoneSymbol('listener')] = task;
};

// removeEventListener
target.removeEventListener = function(eventName, callback) {
  const task = callback[zoneSymbol('listener')];
  if (task) {
    task.zone.cancelTask(task);
  }
};

XHR 补丁原理

XHR 作为 MacroTask 处理

XMLHttpRequest.send() 被包装为宏任务,在请求完成时触发

Zone.__load_patch('XHR', (global, Zone) => {
  const XHR_TASK = zoneSymbol('xhrTask');
  const XHR_SYNC = zoneSymbol('xhrSync');
  
  // patch open 方法,记录 URL 和同步/异步
  patchMethod(XMLHttpRequestPrototype, 'open', () =>
    function(self, args) {
      self[XHR_SYNC] = args[2] === false;  // 第三个参数是否同步
      self[XHR_URL] = args[1];
      return openNative.apply(self, args);
    }
  );
  
  // patch send 方法
  patchMethod(XMLHttpRequestPrototype, 'send', () =>
    function(self, args) {
      if (self[XHR_SYNC]) {
        return sendNative.apply(self, args);  // 同步请求不创建任务
      }
      // 异步请求创建宏任务
      const task = scheduleMacroTaskWithCurrentZone('XMLHttpRequest.send', ...);
    }
  );
});

XHR 任务调度细节

function scheduleTask(task: Task) {
  const data = task.data;
  const target = data.target;
  
  // 添加 readystatechange 监听器
  const listener = () => {
    if (target.readyState === target.DONE) {
      if (task.state === 'scheduled') {
        task.invoke();  // 触发 Zone.runTask
      }
    }
  };
  oriAddListener.call(target, 'readystatechange', listener);
  
  // 调用原生 send
  sendNative.apply(target, data.args);
  target[XHR_SCHEDULED] = true;
  return task;
}

// 任务完成后的处理
// task.invoke() 会触发 onHasTask,通知 Angular 变更检测

__load_patch() 机制

static __load_patch(name: string, fn: PatchFn, ignoreDuplicate = false): void {
  if (patches.hasOwnProperty(name)) {
    const checkDuplicate = global[__symbol__('forceDuplicateZoneCheck')] === true;
    if (!ignoreDuplicate && checkDuplicate) {
      throw Error('Already loaded patch: ' + name);
    }
  } else if (!global['__Zone_disable_' + name]) {
    const perfName = 'Zone:' + name;
    mark(perfName);
    patches[name] = fn(global, ZoneImpl, _api);  // 执行补丁函数
    performanceMeasure(perfName, perfName);
  }
}

// 使用示例
Zone.__load_patch('timers', (global, Zone, api) => {
  patchTimer(global, 'set', 'clear', 'Timeout');
  patchTimer(global, 'set', 'clear', 'Interval');
});

浏览器补丁加载顺序

export function patchBrowser(Zone: ZoneType): void {
  // 1. Timer 补丁
  Zone.__load_patch('timers', ...);
  Zone.__load_patch('requestAnimationFrame', ...);
  
  // 2. 阻塞方法
  Zone.__load_patch('blocking', ...);  // alert, prompt, confirm
  
  // 3. EventTarget
  Zone.__load_patch('EventTarget', ...);
  
  // 4. Observer 类
  Zone.__load_patch('MutationObserver', ...);
  Zone.__load_patch('IntersectionObserver', ...);
  Zone.__load_patch('FileReader', ...);
  
  // 5. 属性补丁
  Zone.__load_patch('on_property', ...);  // onclick 等
  
  // 6. 自定义元素
  Zone.__load_patch('customElements', ...);
  
  // 7. XHR
  Zone.__load_patch('XHR', ...);
  
  // 8. 其他
  Zone.__load_patch('geolocation', ...);
  Zone.__load_patch('PromiseRejectionEvent', ...);
  Zone.__load_patch('queueMicrotask', ...);
}

NgZone 集成

NgZone 是 Angular 对 Zone.js 的封装,提供更友好的 API

// 简化的 NgZone 实现
class NgZone {
  private _nesting = 0;
  private _outer: Zone;
  private _inner: Zone;
  
  readonly hasPendingMacrotasks: boolean;
  readonly hasPendingMicrotasks: boolean;
  readonly isStable: boolean;
  
  constructor() {
    this._outer = Zone.current;
    this._inner = this._outer.fork({
      name: 'angular',
      onHasTask: (delegate, current, target, hasTaskState) => {
        // 当任务队列状态变化时,触发变更检测
        if (!hasTaskState.microTask && !hasTaskState.macroTask) {
          this._onMicrotaskEmpty.emit();
          if (!this.hasPendingMacrotasks) {
            this._onStable.emit();
          }
        }
      }
    });
  }
}

NgZone 核心方法

class NgZone {
  // 在 Zone 外执行(不触发变更检测)
  runOutsideAngular<T>(fn: () => T): T {
    return this._outer.run(fn);
  }
  
  // 在 Zone 内执行(触发变更检测)
  run<T>(fn: () => T): T {
    return this._inner.run(fn);
  }
  
  // run + 错误处理
  runGuarded<T>(fn: () => T): T {
    return this._inner.runGuarded(fn);
  }
  
  // 调度任务
  scheduleMicroTask(fn: () => void) {
    this._inner.scheduleMicroTask('scheduleMicrotask', fn);
  }
}

runOutsideAngular: 用于性能敏感操作(如动画、高频事件)

ApplicationRef 稳定性检测

// ApplicationRef 内部
class ApplicationRef {
  private _runningTick = false;
  
  constructor(private _zone: NgZone) {
    // 监听 Zone 稳定事件
    this._zone.onStable.subscribe(() => {
      if (!this._zone.hasPendingMacrotasks && !this._zone.hasPendingMicrotasks) {
        this.tick();  // 触发变更检测
      }
    });
  }
  
  tick(): void {
    if (this._runningTick) {
      throw new Error('ApplicationRef.tick is called recursively');
    }
    this._runningTick = true;
    try {
      // 遍历所有组件,执行变更检测
      for (const view of this._views) {
        view.detectChanges();
      }
    } finally {
      this._runningTick = false;
    }
  }
}

变更检测触发时机

┌─────────────────────────────────────────────────────────────────┐ │ Angular 变更检测触发流程 │ ├─────────────────────────────────────────────────────────────────┤ │ 异步操作完成 │ │ (Promise.then / setTimeout / Event / XHR) │ │ │ │ │ ▼ │ │ Zone.js onHasTask 钩子 │ │ (任务队列状态: hasTask = false) │ │ │ │ │ ▼ │ │ NgZone.onMicrotaskEmpty │ │ │ │ │ ▼ │ │ ApplicationRef.tick() │ │ │ │ │ ▼ │ │ 遍历组件树,执行 ChangeDetectorRef.detectChanges() │ │ │ │ │ ▼ │ │ 更新 DOM │ └─────────────────────────────────────────────────────────────────┘

ZoneAwareStack 堆栈追踪

// Zone.js 提供增强的错误堆栈
interface Error {
  zoneAwareStack?: string;    // 带有 Zone 信息的堆栈
  originalStack?: string;     // 原始堆栈
}

// 在错误发生时记录 Zone 信息
if (state === REJECTED && value instanceof Error) {
  const trace = Zone.currentTask?.data?.['__creationTrace__'];
  if (trace) {
    ObjectDefineProperty(value, CURRENT_TASK_TRACE_SYMBOL, {
      value: trace
    });
  }
}

// 长堆栈追踪(需要启用 longStackTraceZone)
Zone.longStackTraceZone = Zone.current.fork({
  name: 'longStackTrace',
  onScheduleTask: (delegate, current, target, task) => {
    task.data['__creationTrace__'] = new Error().stack;
    return delegate.scheduleTask(target, task);
  }
});

全局错误处理流程

// ZoneSpec.onError 钩子
const angularZone = Zone.current.fork({
  name: 'angular',
  onHandleError: (delegate, current, target, error) => {
    // 1. 记录错误
    console.error('Error in Zone:', current.name, error);
    
    // 2. 通知 ErrorHandler
    errorHandler.handleError(error);
    
    // 3. 返回 false 表示已处理,不再抛出
    return false;
  }
});

// 未处理的 Promise rejection
api.onUnhandledError = (e) => {
  if (api.showUncaughtError()) {
    console.error(
      'Unhandled Promise rejection:',
      e.rejection,
      '; Zone:', e.zone.name,
      '; Task:', e.task?.source
    );
  }
};

性能优化策略

减少 Zone 操作

  • 使用 runOutsideAngular
  • 避免在循环中触发 Zone
  • 使用 onPush 策略

禁用不必要的补丁

  • __Zone_disable_XHR
  • __Zone_disable_timers
  • __Zone_disable_requestAnimationFrame
// 禁用特定补丁
window.__Zone_disable_XHR = true;  // 在加载 zone.js 之前

// 或使用 zone.js/testing 排除
import 'zone.js/testing/none';  // 完全禁用

Zoneless 模式(Angular 18+)

Zoneless 是 Angular 的未来方向,移除 Zone.js 依赖

// 启用 Zoneless
bootstrapApplication(AppComponent, {
  providers: [
    provideExperimentalZonelessChangeDetection()
  ]
});

// 使用 Signal 触发变更检测
@Component({...})
class MyComponent {
  count = signal(0);
  
  increment() {
    this.count.update(v => v + 1);  // 自动触发变更检测
  }
}

优势: 更小的包体积、更快的启动、更好的 SSR 支持

最佳实践

✅ 推荐做法

  • 使用 NgZone.run() 执行需要变更检测的代码
  • 高频事件用 runOutsideAngular
  • 使用 async pipe 自动订阅
  • 启用 OnPush 策略
  • 使用 ChangeDetectorRef.detach() 手动控制

❌ 避免做法

  • 在 runOutsideAngular 中修改组件状态
  • 频繁调用 ApplicationRef.tick()
  • 在 Zone 内执行大量计算
  • 混用 Zone.js 和原生 API
  • 忘记处理订阅清理

调试技巧

// 1. 打印当前 Zone
console.log(Zone.current.name);

// 2. 追踪任务调度
Zone.current.fork({
  name: 'debug',
  onScheduleTask: (delegate, current, target, task) => {
    console.log('Task scheduled:', task.source, task.type);
    return delegate.scheduleTask(target, task);
  },
  onInvokeTask: (delegate, current, target, task) => {
    console.log('Task invoked:', task.source);
    return delegate.invokeTask(target, task);
  }
}).run(() => { /* your code */ });

// 3. 检测任务泄漏
setInterval(() => {
  console.log('Pending tasks:', 
    Zone.current._zoneDelegate._taskCounts
  );
}, 5000);

常见问题

问题 原因 解决方案
变更检测不触发 代码在 Zone 外执行 使用 NgZone.run()
变更检测过于频繁 高频事件触发 runOutsideAngular + 手动触发
内存泄漏 未取消的 EventTask 确保 removeEventListener
第三方库不工作 使用了原生 Promise 在 Zone 外加载,或 patch
测试超时 Zone 等待任务完成 使用 fakeAsync/flush

与其他方案对比

方案 原理 优点 缺点
Zone.js Monkey Patch 自动追踪,无需修改代码 性能开销,包体积
React Fiber 手动 yield 细粒度控制 需要开发者配合
Vue 3 Proxy + Scheduler 精确追踪 需要响应式 API
Signal 依赖图 + 自动传播 零 Zone 开销 需要学习新 API

总结

Zone.js 核心价值

  • 自动追踪异步操作,无需开发者干预
  • 为 Angular 变更检测提供稳定的基础
  • 统一的错误处理和堆栈追踪

关键源码文件

  • zone.ts - 入口和类型定义
  • zone-impl.ts - Zone/ZoneDelegate/ZoneTask 核心实现
  • promise.ts - ZoneAwarePromise 实现
  • timers.ts - setTimeout/setInterval 补丁
  • browser.ts - 浏览器补丁集合

未来趋势

Signal + Zoneless 是 Angular 的演进方向

参考资料

  • 源码仓库: github.com/angular/angular/tree/main/packages/zone.js
  • 官方文档: angular.dev/guide/change-detection-zone
  • Zone.js 设计文档: docs.google.com/document/d/1F5Ug0jhg082AJVS8MI-S8gK1noQ-D6Yp4_zJM0GVnDI
  • NgZone 源码: github.com/angular/angular/blob/main/packages/core/src/zone/ng_zone.ts
  • Zoneless RFC: github.com/angular/angular/discussions/53734

感谢阅读!
访问 https://atcfu.com/ai-articles/angular-zonejs-async/ 回顾本文