HTML5技术

zone.js - 暴力之美 - 破狼

字号+ 作者:H5之家 来源:H5之家 2016-02-14 12:00 我要评论( )

在ng2的开发过程中,Angular团队为我们带来了一个新的库 zone.js。zone.js的设计灵感来源于Dart语言,它描述JavaScript执行过程的上下文,可以在异步任务之间进行持久性传递,它类似于Java中的TLS(thread-local storage: 线程本地存储)技术,zone.js则是将

在ng2的开发过程中,Angular团队为我们带来了一个新的库 – zone.js。zone.js的设计灵感来源于Dart语言,它描述JavaScript执行过程的上下文,可以在异步任务之间进行持久性传递,它类似于Java中的TLS(thread-local storage: 线程本地存储)技术,zone.js则是将TLS引入到JavaScript语言中的实现框架。

那么zone.js能为我们解决什么问题呢?在回答这个问题之前,博主更希望回顾下在JavaScript开发中,我们究竟遇见了什么难题?

问题引入

我们先来看一段常规的同步JavaScript代码:

var foo = function(){ ... }, bar = function(){ ... }, baz = function(){ ... }; foo(); bar(); baz();

这段代码并没有什么特殊之处,它的执行顺序也并无什么特殊之处,完全在我们的预知之内:foo –> bar –> baz。对它做性能监测也很容易,我们只需要在执行上下文前后记录执行时间即可。

var start, timer = performance ? performance.now.bind(performance) : Date.now.bind(Date); start = timer(); foo(); bar(); baz(); console.log(Math.floor((timer() - start) * 100) / 100 + 'ms');

但在JavaScript的世界并不全是这么简单,众所周知的JavaScript单线程执行的。因此为了不阻塞UI界面的用户体验,在JavaScript执行的很多耗时操作都被封装为了异步操作,如:setTimeout、XMLHttpRequest、DOM事件等。由于浏览器的寄宿限制,JavaScript中异步操作是与生俱来的特性,被深深的印在了骨髓之中。这也是Ryan Dahl博士选择JavaScript开发Node.js平台的原因之一。关于JavaScript单线程执行可以参考博主的另一篇博文:。

那么对于下面这段异步代码,我们又如何做性能监测呢?

var foo = function(){ setTimeout(..., 2000); }, bar = function(){ $.get(...).success(...); }, baz = function(){ ... }; foo(); bar(); baz();

在这段代码中,引入了setTimeout和AJAX异步调用。其中AJAX回调和setTimeout回调时间顺序很难确定,因此给这段代码引入性能检测代码并不像上面的顺序执行代码一样那么简单了。如果我们需要强行加入性能的检测,则会在setTimeout和$.get回调中插入相关的hook代码并并记录执行时间,这样我们的业务代码也会变得非常混乱,就像一团“意大利拉面”一样(What the fuck!)。

zone.js简介

在本文开篇提到zone.js为JavaScript提供了执行上下文,可以在异步任务之间进行持久性传递。该是zone.js上场的时候了。zone.js采用猴子补丁(Monkey-patched)的暴力方式将JavaScript中的异步任务都包裹了一层,使得这些异步任务都将运行在zone的上下文中。每一个异步的任务在zone.js都被当做为一个Task,并在Task的基础上zone.js为开发者提供了执行前后的钩子函数(hook)。这些钩子函数包括:

并且zone.js对JavaScript中的大多数异步事件都做了包裹封装,它们包括:

以及对promise、geolocation定位信息、websocket等也进行了包裹封装,你可以在这里找到它们https://github.com/angular/zone.js/tree/master/lib/patch。

下面我们先来看一个简单的zone.js示例:

var log = function(phase){ return function(){ console.log("I am in zone.js " + phase + "!"); }; }; zone.fork({ onZoneCreated: log("onZoneCreated"), beforeTask: log("beforeTask"), afterTask: log("afterTask"), }).run(function(){ var methodLog = function(func){ return function(){ console.log("I am from " + func + " function!"); }; }, foo = methodLog("foo"), bar = methodLog("bar"), baz = function(){ setTimeout(methodLog('baz in setTimeout'), 0); }; foo(); baz(); bar(); });

执行这段示例代码的输出是:

I am in zone.js beforeTask! I am from foo function! I am from bar function! I am in zone.js afterTask! I am in zone.js onZoneCreated! I am in zone.js beforeTask! I am from baz in setTimeout function! I am in zone.js afterTask!

从上面的输出结果,我们能够看出在zone.js中将run方法块分为了两个Task,它们分别是方法体运行时的Task和异步setTimeout的Task。并且我们能够在这些Task的创建,执行前后拦截并做一些有意义的事情。

在zone.js中fork方法会产生一个继承至zone的子类,并在fork函数中可以配置特定的钩子方法,形成独立的zone上下文。而run方法则是启动执行业务代码的对外接口。

同时zone也支持父子继承,以及它也定义了一套DSL语法,支持$、+、-的前缀。

更多的语法使用,请参考zone.js github首页文档https://github.com/angular/zone.js。

引入zone.js

有了上面的这些关于zone.js的基础知识,在本文开始的遗留问题我们就可以迎刃而解了。下面这段代码是来自zone.js项目的示例代码:https://github.com/angular/zone.js/blob/master/example/profiling.html

var profilingZone = (function () { var time = 0, timer = performance ? performance.now.bind(performance) : Date.now.bind(Date); return { beforeTask: function () { this.start = timer(); }, afterTask: function () { time += timer() - this.start; }, time: function () { return Math.floor(time*100) / 100 + 'ms'; }, reset: function () { time = 0; } }; }()); zone.fork(profilingZone).run(function(){ //业务逻辑代码 });

这里在beforeTask中启动了时间计算,并在afterTask中计算出当前累积的花费的时间。因此我们在业务代码的逻辑中就可以随时利用zone.time()来获取当前耗时了。

zone.js的实现

了解了zone.js的时候之后,或许你会像我一样感觉很神奇,它是如何实现的呢?

下面是zone.js中browser.ts的代码片段(https://github.com/angular/zone.js/blob/master/lib/patch/browser.ts):

export function apply() { fnPatch.patchSetClearFunction(global, global.Zone, [ ['setTimeout', 'clearTimeout', false, false], ['setInterval', 'clearInterval', true, false], ['setImmediate', 'clearImmediate', false, false], ['requestAnimationFrame', 'cancelAnimationFrame', false, true], ['mozRequestAnimationFrame', 'mozCancelAnimationFrame', false, true], ['webkitRequestAnimationFrame', 'webkitCancelAnimationFrame', false, true] ]); fnPatch.patchFunction(global, [ 'alert', 'prompt' ]); eventTargetPatch.apply(); propertyDescriptorPatch.apply(); promisePatch.apply(); mutationObserverPatch.patchClass('MutationObserver'); mutationObserverPatch.patchClass('WebKitMutationObserver'); definePropertyPatch.apply(); registerElementPatch.apply(); geolocationPatch.apply(); fileReaderPatch.apply(); }

 

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

相关文章
  • HTML5之美 - 周全264

    HTML5之美 - 周全264

    2017-04-05 17:00

  • Dropzone.js实现文件拖拽上传 - 小熊吉米

    Dropzone.js实现文件拖拽上传 - 小熊吉米

    2017-01-05 09:00

  • 【CSS进阶】伪元素的妙用--单标签之美 - ChokCoco

    【CSS进阶】伪元素的妙用--单标签之美 - ChokCoco

    2016-05-26 18:00

  • HTML 5的革新:结构之美 - jerrylsxu

    HTML 5的革新:结构之美 - jerrylsxu

    2016-03-29 17:00

网友点评
s