Zone.js 源码分析
第一节:猴子补丁
猴子补丁 = 运行时替换原生 API,在调度时和执行时插入自定义逻辑。
核心代码
const _setTimeout = window.setTimeout;
window.setTimeout = function (callback, delay, ...args) {
// === 调度时:插入的代码 ===
const zone = Zone.current; // 保存当前 Zone
const wrapped = function () {
// === 执行时:插入的代码 ===
return zone.run(callback, this, args);
};
return _setTimeout(wrapped, delay, ...args);
};
作用
- 调度时:把
Zone.current存进闭包 - 执行时:用
zone.run()恢复 Zone,再执行真正的 callback
第二节:作用域
作用域 = 让异步回调能访问「调度时」的上下文。
Zone 类
class Zone {
constructor(parent, spec) {
this._parent = parent;
this._properties = spec.properties || {};
}
// 在 Zone 中执行代码
run(callback) {
const prev = _currentZoneFrame;
_currentZoneFrame = { zone: this, parent: prev };
try {
return callback();
} finally {
_currentZoneFrame = prev; // 恢复
}
}
// 创建子 Zone,继承父 Zone 的 properties
fork(spec) {
return new Zone(this, {
...spec,
properties: { ...this._properties, ...spec.properties }
});
}
// 获取 Zone 上挂载的数据
get(key) {
if (this._properties.hasOwnProperty(key)) {
return this._properties[key];
}
return this._parent ? this._parent.get(key) : undefined;
}
}
// 全局 getter
Object.defineProperty(Zone, 'current', {
get() { return _currentZoneFrame.zone; }
});
使用
const zone = Zone.current.fork({
properties: { requestId: 'req-123' }
});
zone.run(() => {
Zone.current.get('requestId'); // 同步能拿到
setTimeout(() => {
Zone.current.get('requestId'); // 异步也能拿到
}, 1000);
});
第三节:判断异步是否全部完成
原理:猴子补丁给 setTimeout 前后增加代码,每次调度 +1,每次执行完 -1,当从 1→0 时表示全部完成。
核心代码
class Zone {
constructor() {
this._macroTaskCount = 0;
this._onHasTask = null;
}
_changeTaskCount(delta) {
const wasEmpty = this._macroTaskCount === 0;
this._macroTaskCount += delta;
const isEmpty = this._macroTaskCount === 0;
if (this._onHasTask) {
if (wasEmpty && !isEmpty) {
this._onHasTask({ macroTask: true }); // 有任务进入
} else if (!wasEmpty && isEmpty) {
this._onHasTask({ macroTask: false }); // 全部完成 ✓
}
}
}
}
// 猴子补丁:前后增加计数代码
window.setTimeout = function (callback, delay, ...args) {
const zone = Zone.current;
// 调度时 +1
zone._changeTaskCount(1);
const wrapped = function () {
try {
return zone.run(callback, this, args);
} finally {
// 执行完 -1(finally 确保无论成功/抛错都执行)
zone._changeTaskCount(-1);
}
};
return _setTimeout(wrapped, delay, ...args);
};
流程
调用 setTimeout → count: 0 → 1 → 触发 onHasTask({ macroTask: true })
↓
事件循环等待
↓
回调执行完 → count: 1 → 0 → 触发 onHasTask({ macroTask: false }) 全部完成
使用
const zone = Zone.current.fork({
onHasTask(state) {
if (!state.macroTask) {
console.log('全部异步执行完了');
}
}
});
zone.run(() => {
setTimeout(() => {}, 500);
setTimeout(() => {}, 800);
});