Zone.js 源码初探

声明

本文假设读者已经了解 Zone.js 的基本用法,明白 Zone.js 需要解决的问题。

需要学习或复习的读者可以参看我的另一篇文章 Zone.js 入门

初识 Zone.js

Angular 仓库下的那个 Zone.js 中的例子有点小小的问题。因此这边,我 fork

了一份原作者的仓库进行学习研究。Zone.js 本身是由 TypeScript 写就,通过

Gulp + Rollup 的方式进行构建,这块应用比较复杂,这里我们只需重点关注

dist文件夹下最终编译合并后的 zone.js 。这份代码对应了源码中多个不同的

TS 文件,我们可以参考一个简单的例子,来共同探寻 Zone.js 源代码的秘密。

测试用例

理解源代码最好的方式还是写出一个测试用例,然后跟踪它。我在 zon-lab 中

就建立了一个很好的例子。它的入口函数非常简单,只包含了 setTimeout,这

可以让我们集中精力研究 Zone 内部的具体实现。


探索之旅!

现在让我们根据测试用例的线索,结合 Zone.js 源码,弄清其中的原理吧!

Zone 类

首先,当我们导入 Zone.js 时,会在全局创建一个名为 Zone 的类。

Zone 构造函数

Zone 的构造函数非常特殊,是由代表父区域的 parent(一个 Zone 对象)和

当前区域的配置信息(ZoneSpec)所指定。这里 parent 非常重要,这个引用

可以让我们建立一个可嵌套的区域。子区域可以继承父区域设定的一系列钩子

函数。

Zone.current

Zone 的静态 current 成员变量可以让我们获取一个指向当前区域 Zone 的对

象。这个对象会在我们调用 Zone 对象的 run 方法时,被自动置为当前区域的

Zone 的对象,使得后续执行的任务能被不同的区域所管理。

Zone.fork

这是一个实例方法。当我们调用 Zone 类对象 fork 方法时,传入区域的配置信

息(由 ZoneSpec 指定)后,就交由成员变量 _zoneDelegate 进一步的处理

它是 ZoneDelegate 的一个实例对象。

Zone.run

Zone.run 是 Zone 类的一个实例方法。它的主要作用是切换当前的 Zone 的静

态成员变量 current 的值,使其指向当前的区域(Zone)对象。在交给代理

_zoneDelegate 通过 invoke 调用完 run 中的函数之后,在将 current 的值进

行恢复。这个方法非常巧妙地实现了不同区域下 Zone 实例对象的隔离。

Zone.__load_patch

Zone.__load_patch 是 Zone 中为全局对象打补丁的核心。它会把方法按照打

补丁方式的不同分别进行处理。

timer 就针对 set\clearTimeout,set\clearInterval,set\clearImmediate 三

个函数,利用 patchTimer 调用 patchMethod 方法进行替换。改造后的函数

会利用 scheduleMacroTaskWithCurrentZone 进行调用执行。

这个函数会将我们的回调函数封装在一个名为 ZoneTask 的结构体中。其中

ZoneTask 结构体绑定了当前区域中的 current 对象。这样在我们运行改造过

的方法时候,会先执行区域中配置好的钩子函数,当钩子完毕全部执行完毕后

再执行我们的回调函数。

总结

本文根据 Zone.js 的使用方法,研究了 Zone.js 替换 setTimeout 函数的原理。

更多替换的实现方式可以参看 Zone.js 源码。

发表评论

电子邮件地址不会被公开。 必填项已用*标注