Zone.js是angular团队参照NodeJS的Domain,Dart的Zone,为angular 2开发的核心组件。javascript
一开始,我对Zone.js是拒绝的。咱们知道相似的 Domain 模块,主要是为了解决异步错误跟踪问题。因此,当我没有太强烈的错误跟踪需求的时候,Zone.js有啥用?java
然而execution context
不单单能够用来跟踪异步错误,还能够作一些猥琐而实用的事情。jquery
Zone.current.fork({}).run(function () { Zone.current.inTheZone = true; setTimeout(function () { console.log('in the zone: ' + !!Zone.current.inTheZone); // 'in the zone: true' }, 0); }); console.log('in the zone: ' + !!Zone.current.inTheZone); // 'in the zone: false'
execution context,咱们能够理解成只与当前 fork 出来的
Zone
实例相关的上下文。git
上面的例子很明显,由于只有在 fork 中 Zone 的实例咱们才设置了 Zone.current.inTheZone 为 true,因此在外面打印出来的结果是 false。github
咱们想像上面那个过程是同步的,那么发生了什么呢?异步
const defaultZone = Zone.current // 生成一个新的Zone const zone = new Zone() // 设置当前zone Zone.current = zone // 对当前zone设值 Zone.current.inTheZone = true console.log('in the zone: ' + !!Zone.current.inTheZone) // 退出当前zone Zone.current = defaultZone console.log('in the zone: ' + !!Zone.current.inTheZone)
很好,同步没有什么问题,那么异步怎么办呢?其实很简单,就是在每个异步入口加一个看门人,就能够了。async
const defaultZone = Zone.current // 生成一个新的Zone const zone = new Zone() // 设置当前zone Zone.current = zone // 对当前zone设值 Zone.current.inTheZone = true const anonymousA = function () { console.log('in the zone: ' + !!Zone.current.inTheZone); // 'in the zone: true' } // 给进入异步的函数配发令牌映射到对应zone anonymousA._zone = zone // 退出当前zone Zone.current = defaultZone setTimeout(() => { // 函数从新回来,设置当前 zone Zone.current = anonymousA._zone anonymousA.call(this) // 退出当前zone Zone.current = defaultZone }, 0) console.log('in the zone: ' + !!Zone.current.inTheZone)
固然Zone.js
实现比上面复杂得多,有兴趣的同窗能够看看源代码。函数
从上面的例子看,咱们能够看到,咱们能够在 zone 实例上保存只有该 zone 使用的属性。那么咱们在利用Object.defineProperty就能够达成咱们的目标了。oop
// 写的巨简单,不要吐槽 !function (win, Zone) { var map = {}; var noop = {}; var dependence = {}; var alias = {}; var hasSet = {}; // 由于懒,仅支持 define(name, factory),反正只是 demo function define(name, factory) { if (typeof factory === 'function') { map[name] = { factory: factory, exports: noop }; } else { map[name] = { exports: factory }; } } function require(name) { var module = map[name] if (module.exports !== noop) return module.exports; if (dependence[name]) { var properties = {}; // 利用Object.defineProperty 组装 window.xxx -> require('xxx') 的映射 Object.keys(dependence[name]).forEach(function (key) { var res; if (alias[key]) res = alias[key]; else res = key; properties[res] = require(key + '@' + dependence[name][key]); if (!hasSet[res]) { hasSet[res] = true; Object.defineProperty(window, res, { get: function () { return Zone.current.get(res) } }); } }); // 对每一个模块,fork 一个 Zone 实例进行执行 Zone.current.fork({ properties: properties }).run(function () { module.exports = module.factory() }); } else { module.exports = module.factory(); return module.exports; } } function config(opt) { Object.assign(dependence, opt.dep); Object.assign(alias, opt.alias); } require.config = config; window.define = define; window.require = require; }(window, Zone)
// 模拟两个jQuery define('jquery@1.4', { version: '1.4', bind: function () { console.log('call bind'); } }) define('jquery@1.8', { version: '1.8', on: function () { console.log('call on'); } }) // 仅仅打印版本,不作任何事情 function logVersion() { console.log('version === ', $.version) } // 要运行的第一段代码 define('module1', function module1() { // 使用1.8版本 $.on(); // 证实即便异步调用,这里面的 $ 依然指向正确 setTimeout(logVersion, 100) }) // 要运行的第二段代码 define('module2', function module2() { // 使用1.4版本 $.bind(); // 证实即便异步调用,这里面的 $ 依然指向正确 setTimeout(logVersion, 300) }) // 载入依赖 require.config({ dep: { module1: { 'jquery': '1.8' }, module2: { 'jquery': '1.4' } }, alias: { 'jquery': '$' } }) require('module1') require('module2')
具体实现参见:(two-different-jquery)[https://github.com/miniflycn/async-technique-you-may-do-not-know/tree/master/two-different-jquery]ui
其实咱们能够基于 Zone.js 作一个 Sandbox,则在大型重历史包袱的应用中,能够很好地将多个技术体系共存而不产生恶心的冲突问题。
或者作一个对任意模块依赖注入的方案,对模块之间作彻底解耦。