理解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,当然还有更多的例子

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容

  • 概述 zone是异步任务中持续存在的执行上下文zone.js提供了一种机制来拦截异步任务以及追踪异步任务zone....
    CAaRrLl阅读 3,741评论 0 3
  • Angular组件 一:组件基础 1:什么是组件? 组件(Component)是构成Angular应用的基础和核心...
    真的稻城阅读 4,303评论 3 6
  • 进程 创建 创建进程用fork()函数。fork()为子进程创建新的地址空间并且拷贝页表。子进程的虚拟地址空间...
    梅花怒阅读 1,891评论 0 7
  • 我再没主动找过心月,每天站在教室门口,也没看见过她。 我不知道用什么方式面对她,我知道我没有做错什么,她也没什么做...
    7次遇见我阅读 189评论 0 2
  • 于千万人之中遇见你所要遇见的人,于千万年之中,时间的无涯的荒野里,没有早一步,也没有晚一步,刚巧赶上了,那也没有别...
    粉盒阅读 598评论 0 2