译文 | Angular中的AoT编译

Posted RDK快速开发套件

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了译文 | Angular中的AoT编译相关的知识,希望对你有一定的参考价值。

译者:陈旭@中兴RDK

前两天,Jigsaw七巧板上来了个issue https://github.com/rdkmaster/jigsaw/issues/113,@jackjoy 在issue中提到了一篇介绍Angular AoT的文章,我看了一下,觉得讲的非常好,还涉及到一些Angular编译原理的内容。于是打算翻译一下,让大伙都能够读一读,多了解一点AoT知识。

文中的第一人称“我”均指代作者本人(http://blog.mgechev.com)

最近我给angular-seed增加了对Ahead-of-Time(AoT)编译的支持,这引来了不少关于这个新特性的问题。我们从下面这些话题开始来回答这些问题:

  • 为什么Angular需要编译?

  • 什么东西会被编译?

  • 他们是如何被编译的?

  • 编译发生在什么时候?JiT vs AoT

  • 我们从AoT中获得了什么?

  • AoT编译是如何工作的?

  • 我们使用AoT和JiT的代价是什么?

为什么Angular需要编译?

这个问题的简短回答是:编译可以让Angular应用达到更高层度的运行效率,我所说的效率,主要是指的性能提升,但也包括电池节能和节省流量。

AngularJs1.x 有一个实现渲染和变化检测的很动态的方式,比如AngularJs1.x的编译器非常通用,它被设计为任何模板实现一系列的动态计算,虽然它在通常情况下运行的很好,但是JS虚拟机的动态特性让一些低层次的计算优化变得很困难。由于js虚拟机无法理解那些我们作为脏检查的上下文对象(术语为scope)的形态,虚拟机的内联缓存常常不精确,这导致了运行效率的下降。

译者:scope是AngularJs1.x中的一个重要对象,他是AngularJs1.x用于计算模板的上下文。

Angular2+采用了一个不同的方式。在给每个组件做渲染和变化检测的时候,它不再使用同一套逻辑,框架在运行时或者编译时会生成对js虚拟机友好的代码。这些友好的代码可以让js虚拟机在属性访问的缓存,执行变化检查,进行渲染的逻辑执行的快的多。

举个例子,看看下面的代码:

// ...
Scope.prototype.$digest = function () {
  'use strict';
  var dirty, watcher, current, i;
  do {
    dirty = false;
    for (i = 0; i < this.$$watchers.length; i += 1) {
      watcher = this.$$watchers[i];
      current = this.$eval(watcher.exp);
      if (!Utils.equals(watcher.last, current)) {
        watcher.last = Utils.clone(current);
        dirty = true;
        watcher.fn(current);
      }
    }
  } while (dirty);
  for (i = 0; i < this.$$children.length; i += 1) {
    this.$$children[i].$digest();
  }
};
// ...

这个代码片段来自《轻量级angularJs1.x实现》一文。这些代码实现了对scope树做深度优先搜索,目的是为了寻找绑定数据的变化,这个方法对任何指令都生效。这些代码显然比下面这些直接指定检查的代码慢:

// ...
var currVal_6 = this.context.newName;
if (import4.checkBinding(throwOnChange, this._expr_6, currVal_6)) {
    this._NgModel_5_5.model = currVal_6;
    if ((changes === null)) {
        (changes = {});
    }
    changes['model'] = new import7.SimpleChange(this._expr_6, currVal_6);
    this._expr_6 = currVal_6;
}
this.detectContentChildrenChanges(throwOnChange);
// ...

译者:

这里一下子提及了angularJs1.x的好几个概念,包括scope,数据绑定,指令。不熟悉angularJs1.x的同学理解起来费劲,想弄懂的话,自行搜索吧。个人认为可以无视,毕竟这个文章的重点不是在这里。你就认为Angular2+的处理方式比angularJs1.x牛逼很多就好了,哈哈。

上面代码包含了一个来自angular-seed的某个编译后的组件的代码,这些代码是由编译器生成的,包含了一个 detectChangesInternal 方法的实现。Angular框架通过直接属性访问的方式读取了数据绑定中的某些值,并且采用了最高效的方式与新的值做比较。一旦Angular框架发现这些值发生了变化,它就立即更新只受这些数据波及的DOM元素。

在回答了“为什么Angular需要编译”这个问题的同时,我们同时也回答了“什么东西会被编译”这个问题。我们希望把组件的模板编译成一个JS类,这些类包含了在绑定的数据中检测变化和渲染UI的逻辑。通过这个方式,我们和潜在的平台解耦了。换句话说,通过对渲染器采取了不同的实现,我们在不对代码做任何的修改的前提下,就可以对同一个以AoT方式编译的组件做不同的渲染。比如,上述代码中的组件还可以直接用在NativeScript中,这是由于这些不同的渲染器都能够理解编译后的组件。

编译发生在什么时候?JiT 还是 AoT

Angular编译器最cool的一点是它可以在页面运行时(例如在用户的浏览器内)启动,也可以作为构建的一个步骤在页面的编译时启动。这主要得益于Angular的可移植性:我们可以在任何的平台的JS虚拟机上运行Angular,所以我们完全可以在浏览器和NodeJs中运行它。

JiT编译模式的流程

一个典型的非AoT应用的开发流程大概是:

  • 使用TypeScript开发Angular应用

  • 使用tsc来编译这个应用的ts代码

  • 打包

  • 压缩

  • 部署

以上是关于译文 | Angular中的AoT编译的主要内容,如果未能解决你的问题,请参考以下文章

在 Angular 5 和 AOT-Build 中使用 @angular 编译器时出错

为啥 Angular AoT 不支持装饰器中的函数表达式?

Angular AOT 组件中的相对路径

Angular2 AOT 编译与静态外部 js 文件

为啥即使模板访问私有属性,Angular AOT 也会编译?

在运行时为 AOT 编译的 Angular 应用程序获取 --locale 的值