当你使用Angular 2的时候,你会惊喜地发现你不再需要$rootScope.$apply了,即使你直接调用addEventListener挂载一个事件处理器偷偷修改了模型,也不需要通知Angular。Angular似乎能够“未卜先知”地知道你的所有小动作。这一切的功劳都归功于zone.js。

zone.js的github地址见 这里。原代码库提供了一个 ng-conf上的video(需翻墙)来解释这个库的背景。注意 zone.js是一个独立的库,并不依赖于Angular,所以你无需担心被“某些别有用心的大户”挟持了。

简单地说,zone.js允许你为一段代码及其衍生出的代码提供了一个统一的上下文(zone.js称之为zone)。这个概念类似Node中的domain或者Java中的thread locals。
能够在源于同一段代码的有着不同stack trace的程序间共享数据是一件很诱人的事情。比如说,你想统计程序的执行时间,然而你却有这样的一段奇葩程序:它自己要执行1秒,并且触发一个异步的操作;这个异步的操作又执行了3秒。如果你能够在这两段程序间共享数据,这个问题就简单多了。
当然,你完全可以自己通过给每个入口的地方都加入一段代码的方式来解决这个问题,但是你会更希望能够使用一种类似zone.js提供的这种“面向切面”的优雅解决方案。

首先,如果用这样的方式来运行你的程序:

1
zone.run(yourCode);

这样你的这段代码以及其衍生的代码,包括:

  1. 它注册的浏览器事件处理器
  2. 它通过setTimeout和setInterval在未来执行的代码

以及衍生代码所衍生的代码就共享一个zone了。

那么,怎么使用这个zone呢?你可以通过类似下面这段代码这样通过zone.fork来插入一些监听器:

1
2
3
4
5
6
7
8
zone.fork({
beforeTask: function() {
// 在每段代码执行前都会执行的代码
},
afterTask: function() {
// 在每段代码执行后都会执行的代码
}
}).run(youCode);

比如,在beforeTask记录下开始时间,afterTask时记录下结束时间、与开始时间比较算出执行时间并且累加,你就可以解决前文说过的那个问题了。本文开头的Angular 2的$rootScope.$apply的迷雾也就解开了,因为Angular 2使用了zone并且在afterTask时帮你执行这个了。

zone.js的实现原理很简单,就是mock掉你的每个入口位置。多么地简单粗暴啊!但是非常有效。

说了这么多,来看一个最最简单地例子吧:我们想在每次执行代码(主函数、事件监听函数和setTimeout加入的macrotask)的前后各打印一行log。

首先,我们新创建一个空文件夹来装例子所需的文件。
然后,因为zone支持bower,我们通过在该文件夹中运行下面的命令(有关bower使用,请见 这里)下载zone:

1
bower install zone

然后,在目录下创建index.html文件。那么默认情况下,相对于html文件,zone.js文件的位置应该是”bower_components/zone/dist/zone.js”。html文件内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Zone.js Basic Demo</title>
<script src="./bower_components/zone/dist/zone.js"></script>
</head>
<body>
<h1>Basic Example</h1>
<button id="trigger">Run async code</button>
<script>
/*
* This is a simple example of async stack traces with zones
*/
function main() {
console.log('Run main');
trigger.addEventListener('click', clickHandler);
}
function clickHandler() {
console.log('Run click handler');
setTimeout(function () {
console.log('Run macro task');
});
}
/*
* Bootstrap the app
*/
zone.fork({
beforeTask: function () {
console.log('Enter zone');
},
afterTask: function () {
console.log('Leave zone');
}
}).run(main);
</script>
</body>
</html>

然后你可以用类似 http-server的方法来部署这个文件并用浏览器访问。
在页面加载后,你会在控制台看到如下信息

1
2
3
Enter zone
Run main
Leave zone

当点击页面中的按钮时,控制台会打印如下信息

1
2
3
4
5
6
Enter zone
Run click handler
Leave zone
Enter zone
Run macro task
Leave zone

值得指出的是,zone.js那个文件本身并不支持microtask(有关macrotask和microtask,日后我会专门写一篇文章来解释)。如果想支持microtask,你需要使用库里面的zone-microtask.js。这个文件比zone.js大很多,主要的原因是它用es6-promise完全替换掉了浏览器内置的Promise。