理解Zone

原文地址:http://blog.thoughtram.io/angular/2016/01/22/understanding-zones.html

在NG-Conf 2014年,Brian介绍了Zone,以及它们如何改变我们处理异步代码的方式。如果你还没看过这个演讲,试一试,只需要15分钟,api现在可能有所不同,但语义和底层概念都是相同的。在本文中,我们想深入地探讨zone如何的运作。

解决的问题

让我们快速来概括下什么是Zone,正如Brian所说,它们基本上是一个异步操作的执行上下文,证明了它们在错误处理和分析非常有用,但这到底意味着什么呢?为了理解执行上下文这一部分,我们需要更好地了解Zone用来解决什么问题,我们先看看以下JavaScript代码

foo();
bar();
baz();

function foo() {...}
function bar() {...}
function baz() {...}

在这里没什么特别的代码,我们有三个连续执行的函数foo,bar,baz,比如,我们要测量这个代码的执行时间,我们很容易的扩展一些用来分析的代码段。

var start,time = 0;
    timer = performance ? performance.Now || Date.Now;

// start timer start = timer();
foo();
bar();
baz();
// stop timer
time = timer() - start;
// log time in ms
console.log(Math.floor(time*100) / 100 + 'ms');

然而,我们常常有异步操作要做。可以是AJAX请求从远程服务器获取一些数据,或者也许我们只是为下一帧执行一些操作,无论发生哪种异步操作,因为是异步,基本上,这些操作无法被我们的分析代码给计算到,看看这个代码

function doSomething() {
  console.log('Async task');
}

// start timer
start = timer();
foo();
setTimeout(doSomething,2000);
bar();
baz();
// stop timer
time = timer() - start;

我们在代码添加一个异步操作,这对我们的分析有什么影响?我们会发现分析结果没有什么大的差别。

事实上多了一个操作,所以需要更长的时间执行这段代码,然而实际执行的时间没有计算到setTimeout()操作,这是因为异步操作会被添加到浏览器的事件队列,在下一次事件循环(event loops)中才会被执行。

如果你还不了解这块内容,你可以看看这个视频浏览器事件循环是如何工作的

那么,我们如何解决这个问题,我们需要的一些hook,允许我们在这样的异步任务发生时执行一些分析代码,当然,我们可以手动的为每个异步创建并启动一个计时器,但这会使我们的代码变得非常混乱。

这就是Zone可以发挥作用的地方,Zone可以执行一些操作 - 如在每次代码进入或退出一个区域,启动、停止计时器,或保存堆栈跟踪,他们可以在我们的代码中重写方法,甚至关联起各个区域的数据。

创建(Creating),分叉(forking),扩展(extending) Zone

Zone实际上Dart语言的特性,然后,由于Dart也只是编译成JavaScript,所以我们在Javascript中也能实现相同功能,Brian做到了这点,他为Javascript Zone 创建了 Zone.js,也是一个Angular 2的依赖。在使用Zone为我们的示例代码创建分析代码之前,先让我们讨论如何创建zone。

一旦我们嵌入zone.js到我们的网站,我们可以获得全局zone对象。zone配备了一个run()方法,它接受一个函数用来在这个zone区域中执行,也就是说,我们想要在一个zone中运行代码,我们可以这样做:

function main() {
  foo();
  setTimeout(doSomething,2000);
  bar();
  baz();
}

zone.run(main);

酷。但这有什么意义?好吧……目前的结果没有什么区别,除了我们不得不写下更多的代码。但是,在此时,我们的代码运行在一个zone中(另一个执行上下文),正如我们前面了解到的,当我们的代码进入或退出某个zone时,zone可以对其进行操作。

为了建立这些hook,我们需要fork当前的zone,fork一个zone会返回一个新的zone,它基本上是从“父”zone继承的,当然,fork一个zone也允许我们扩展返回的那个zone的行为,我们可以在zone对象上使用.fork()来fork一个zone,这里的代码看上去可能是这样的:

var myZone = zone.fork();

myZone.run(main);

这实际上只是给了我们一个新的zone,和原先的zone(我们还没有讨论过)相同功能。让我们来尝试这些我们之前提到的hook,并扩展我们的新zone,使用一个Zonespecification来定义hook,并传递给fork(),我们可以使用下面这些hook:

onZoneCreated - zone被fork时调用
beforeTask - 在zone.run执行的函数之前调用
afterTask - 在zone.run执行的函数之后调用
onError - 当函数传递给run或beforeTask抛出异常时被调用
下面是我们的示例代码,在每个任务执行之前和之后:

var myZonespec = {
  beforeTask: function () {
    console.log('Before task');
  },afterTask: function () {
    console.log('After task');
  }
};

var myZone = zone.fork(myZonespec);
myZone.run(main);

// Logs:
// Before task
// After task
// Before task
// Async task
// After task

等一下!发生了什么?这两个hook被执行了两次? 这是为什么?当然,我们已经了解到,zone.run显然被认为是一个“task”,这也就是为什么前两个消息被log,但似乎像setTimeout()调用也被视为一个task了。这怎么可能?

猴子补丁(Monkey-patched) hook

Monkey-patched是指给内置对象扩展的一种术语

原来还有一些其他的hook,实际上,这些都不只是简单的hook,还在全局作用域中monkey-patched一些方法,只要我们在网站上嵌入zone.js,几乎导致所有的异步操作方法被monkey-patched,并都运行在一个新的zone里。

例如,当我们调用setTimeout(),实际上我们调用的是Zone.setTimeout(),这又使用zone.fork()创建了一个新的zone,其给定的处理程序被执行。这就是为什么我们的hook被很好的执行了,因为这个被fork的zone从父zone继承了要执行的task。

认情况下zone.js重写了提供了如下的方法:

Zone.setInterval()
Zone.alert()
Zone.prompt()
Zone.requestAnimationFrame()
Zone.addEventListener()
Zone.removeEventListener()

可能有人会问,为什么像方法alert()和prompt()也被修补,如前所述,这些hook同时修补方法,我们可以已添加afterTask和afterTask完全相同的方式,改变和扩展它们fork的zone,这是非常强大的,当我们编写测试时,我们可以截获alert()和prompt(),并改变它们自己的行为。

zone.js配备了一个微型的DSL,让你可以加强zone hook,如果你对这个特别的东西感兴趣,你可以看看这个项目的readme

创建Zone性能分析

我们最初的问题是,我们能不能捕捉到我们代码中异步任务的执行时间,现在我们已经了解关于Zone和它提供的api,实际上我们需要创建一个zone,用来记录我们异步任务的cpu时间,幸运的是,一个zone性能分析的实现在zone.js资源库例子中已经实现,你可以在这里找到

在这看上去是这样的:

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 specification内,这个例子还增加了add()和reset()方法调用zone对象看上去是这样的:

zone
  .fork(profilingzone)
  .fork({
    '+afterTask': function () {
      console.log('Took: ' + zone.time());
    }
  })
  .run(main);

+语法是一个DSL,它允许扩展父zone的hook

我们还可以使用一个LongStackTraceZone,当然还有更多的例子

转自http://ng2.zai.io/topic/detail/news/7adc9551707f3c07

相关文章

ANGULAR.JS:NG-SELECTANDNG-OPTIONSPS:其实看英文文档比看中...
AngularJS中使用Chart.js制折线图与饼图实例  Chart.js 是...
IE浏览器兼容性后续前言 继续尝试解决IE浏览器兼容性问题,...
Angular实现下拉菜单多选写这篇文章时,引用文章地址如下:h...
在AngularJS应用中集成科大讯飞语音输入功能前言 根据项目...
Angular数据更新不及时问题探讨前言 在修复控制角标正确变...