[翻译]Angular中的AoT(Ahead-of-Time C

2019-10-25  本文已影响0人  维仔_411d

此文是angular-seed的作者的文,发布于2016/08/14,原文地址
(angular-seed最后release时间为2017/03/25)


最近我在angular-seed中增加支持了 Ahead-of-Time(AoT) compilation ,收到了关于此特性的很多疑问。以下7点将能够解答绝大多数问题,下文将逐一展开:

为什么Angular中需要编译?(Why we need compilation in Angular?)

简而言之,编译能使Angular的应用有更高的效率。我所说的效率指的是性能的提高,但这也会加大能源、有时还有带宽的消耗。
AngulzarJS 1.x 在页面渲染和变化检测的实现采用了非常动态的方式。例如,AngulzarJS 1.x的编译器是非常通用的,它通过进行一系列动态计算 以期可以 编译任何模板文件。尽管大多数情况它能够正常工作,但因为他们的动态特性 使JavaScript Virtual Machines(VM)对这些计算 的优化程度较低(the JavaScript Virtual Machines (VM) struggles with optimizing the calculations on lower level)。
因为VM不知道scope对象的结构,scope对象提供脏检查逻辑的上下文环境 ,VM的内联缓存会出现很多misses ,这降低执行速度(Since the VM doesn’t know the shapes of the objects which provide context for the dirty-checking logic (i.e. the so called scope), it’s inline caches get a lot of misses which slows the execution down.)
Angulzar 2及以上的版本,执行变化检测和渲染方式与1不同。不再 对每个独立的component的渲染和和变化检测 使用同样的逻辑,而是在执行时或编译时 框架会生成VM友好的代码 ,这能使JVM 使用属性访问的方式访问缓存、更快地执行变化检测/渲染逻辑(This allows the JavaScript virtual machine to perform property access caching and execute the change detection/rendering logic much faster.)。
例如:以下代码截取自我的AngularJs1.x的轻量级实现,代码中我们使用深度优先遍历了整个scope树,查找我们绑定的数据的变化。这种实现方式对任何directive都适用。然而,与代码二 directive特定的代码相比 此实现明显更慢一些。

// ... 代码一
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();
  }
};
// ... 代码二
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);

代码二截取了 angular-seed项目中,编译后的组件生成的 detectChangesInternal 方法的部分实现(The snippet above contains a piece of the implementation of the generated detectChangesInternal method of a compiled component from the angular-seed project.)。它直接使用属性访问的方式获取绑定数据,并使用最高效的方式比较新值和原值。一旦发现二者不同,它只更新绑定值影响的DOM元素。

什么内容需要进行编译?(What needs to be compiled?)

我们在回答为何需要编译这个问题的同时,也回答了什么内容需要编译。 我们希望将模板文件编译成JavaScript的类。这些类中的方法包含检测绑定数据变化和渲染用户界面的逻辑 。这样我们就不需要底层平台耦合(除了markup的格式)。换言之,通过实现各种不同的渲染器, 我们可以 使用相同的 AoT编译的组件并渲染它,而无需改变代码。例如,只要渲染器能够理解传参,该组件就可以在NativeScript中被渲染(In other words, by having a different implementation of the renderer we can use the same AoT compiled component and render it without any changes in the code. So the component above could be rendered in NativeScript, for instance, as soon as the renderer understands the passed arguments.)

编译如何进行(How it gets compiled?)

Just-in-Time (JiT) 和 Ahead-of-Time (AoT) 分别在何时进行?(When the compilation takes place? Just-in-Time (JiT) vs Ahead-of-Time (AoT).)

Angular的编译器优点是在runtime(例如用户的浏览器)或build-time(作为build流程中的一步)都可以调用它。这是由于Angular的可移植性,我们可以在任何 有javascript vm的平台运行它,所以让Angular在浏览器和node中都可以被编译。
Just-in-Time编译过程的事件流Flow of events with Just-in-Time Compilation

Ahead-of-Time编译过程的事件流Flow of events with Just-in-Time Compilation

我们可以看到,JiT流程中,用户在浏览器端打开页面的步骤第三步,在AoT流程中不见了。这会带来更好更快的用户体验,在诸如angular-seed和angular-cli这种工具,能够显著地自动化构建(on top of that tools like angular-seed and angular-cli will automate the build process dramatically.)

总结,对Angular来说 JiT和AoT主要的不同点在于

  1. 编译发生的时间不同
  2. JiT生成JavaScript(由于代码是在浏览器端被编译为JavaScript,生成TypeScript没什么意义 (TypeScript doesn’t make a lot of sense since the code needs to be compiled to JavaScript in the browser)),而AoT通常生成TypeScript

AoT后会生成什么?(What we get from AoT?)

AoT编译如何工作的?(How the AoT compilation works?)

深入理解Ahead-of-Time Compilation (Ahead-of-Time Compilation in Depth)

这个小节回答了以下三个问题:

编译的过程我们快速过一下即可,没必要详细解释完整的@angular/compiler的代码,如果你对分析、解析、代码生成有兴趣,可以看一下关于“The Angular 2 Compiler” by Tobias Bosch的讨论或者slide deck.

Angular模板编译器获取 一个组件和和上下文作为输入(The Angular template compiler receives as an input a component and a context (we can think of the context as a position in the component tree)),生成如下文件:

*.ngfactory.ts
包含以下定义:

AoT vs JiT 开发经验
在此小节中我们会讨论使用AoT和 JiT开发体验的另一个不同点。
最大的不同点应该是Jit 模式时,internal component 和 the internal host component会被定义为JavaScript。这表示组件的controller中的字段始终是public访问权限,我们能访问任何private的字段而得不到任何编译时错误。
在JiT一旦我们开始了应用的引导程序,我们就已经能够在root componnet中访问到 root injector和所有指令 (他们包含在BrowserModule和我们在root module中引进的所有其他的module)。元数据将会在root component的视图文件的编译过程中被传给编译器,一旦编译器在JiT模式下生成了root component的代码,编译器就有了所有的元数据,这些数据也会用于生成所有子组件的代码(This metadata will be passed to the compiler for the process of compilation of the template of the root component. Once the compiler generates the code with JiT, it has all the metadata which should be used for the generation of the code for all child components)。编译器不仅已经知道在组件树的级别可访问哪些provider还知道使用了哪些指令,所以编译器能够给所有的组件生成代码(It can generate the code for all of them since it already knows not only which providers are available at this level of the component tree but also which directives are visible there)。
当 在模板中访问某元素时 ,编译器能够知道要做什么。例如组件<bar-baz></bar-baz>

AoT 和第三方模块
既然编译器需要组件的元数据以编译他们 的模板文件,我们可以摄像 在我们的应用中我们使用了第三方组件库。Angular AoT编译器如何能知道这些组件定义的元数据,因为这些组件是普通的JavaScript?编译器不能知道!所以引用了Angular library之外的第三方库,要用AoT模式编译应用,第三方库需要分布在 编译器生成的 *.metadata.json

AoT有什么优点?(What we get from AoT?)

你可能会想,AoT带来良好的性能。初始化渲染的性能AoT模式比JiT模式快得多,因为AoT大大降低了JVM要做的计算量。我们只需要在开发过程中将模板文件编译为JavaScript文件 一次。

JiT与AoT相比,有什么缺点?(Do we loose anything from using AoT vs JiT?)

总结

Angular编译器利用JVM的行内缓存机制,显著地改善了我们的应用的性能。另外,我们在构建过程中进行编译, 解决了禁用eval的问题,能够让我们进行更高效的tree-shaking,降低初始渲染时间。

不在运行时进行编译是否有缺点?在个别情况下,我们 可能会继续生产组件的模板文件,这就需要我们载入未编译的组件,然后在浏览器执行编译过程,在这种情况下我们需要在应用中引入@angular/compiler模块

AoT还有一个潜在的缺点是,在中大型应用中,AoT打包后包的大小会增加。因为组件的模板文件生成JavaScript代码量比模板文件本身要大,这很有可能导致最终打包的大体积。

总之,AoT编译是一种很好的技术,它已经被angular-seed和angular-cli集成了。

上一篇 下一篇

猜你喜欢

热点阅读