Angular 2 JIT vs AOT

在 Angular 应用程序中,包含了我们通过 Angular 提供的 API 实现的自定义指令。这些自定义指令对浏览器来说,都是无法识别的,因此每个 Angular 应用程序在运行前,都需要经历一个编译的阶段。

在 Angular 2 中有两种编译模式:

  • JIT - Just-In-Time

  • AOT - Ahead-Of-Time

JIT - Just-In-Time

图片描述

Just-in-Time 编译模式开发流程

  • 使用 TypeScript 开发 Angular 应用

  • 运行 tsc 编译 TypeScript 代码

  • 使用 Webpack 或 Gulp 等其他工具构建项目,如代码压缩、合并等

  • 部署应用

应用部署后,当用户通过浏览器访问我们应用的时候,她将经历以下步骤(非严格 CSP):

  • 下载应用相关的资源,如 JavaScript 文件、图片、样式资源

  • Angular 启动

  • Angular 进入 JiT 编译模式,开始编译我们应用中的指令或组件,生成相应的 JavaScript 代码

  • 应用完成渲染

AOT - Ahead-Of-Time

图片描述

Ahead-Of-Time 编译模式开发流程

  • 使用 TypeScript 开发 Angular 应用

  • 运行 ngc 编译应用程序

    • 使用 Angular Compiler 编译模板,一般输出 TypeScript 代码

    • 运行 tsc 编译 TypeScript 代码

  • 使用 Webpack 或 Gulp 等其他工具构建项目,如代码压缩、合并等

  • 部署应用

应用部署后,相比于 JIT 编译模式,在 AOT 模式下用户访问我们的应用,只需经历以下步骤:

  • 下载应用相关的资源,如 JavaScript 文件、图片、样式资源

  • Angular 启动

  • 应用完成渲染

JIT vs AOT

Just-In-Time (JIT) compilation

图片描述

Ahead-Of-Time (AOT) compilation

图片描述

特性 JIT AOT
编译平台 (Browser) 浏览器 (Server) 服务器
编译时机 Runtime (运行时) Build (构建阶段)
包大小 较大 较小
执行性能 - 更好
启动时间 - 更短

除此之外 AOT 还有以下优点:

  • 在客户端我们不需要导入体积庞大的 angular 编译器,这样可以减少我们 JS 脚本库的大小

  • 使用 AOT 编译后的应用,不再包含任何 HTML 片段,取而代之的是编译生成的 TypeScript 代码,这样的话 TypeScript 编译器就能提前发现错误。总而言之,采用 AOT 编译模式,我们的模板是类型安全的。

另外感兴趣的读者,可以使用 source-map-explorer 工具查看不同模式下生成的 bundle JS 文件中各种 JS 资源的占比。

AOT详解

app.component.html

<button (click)="toggleHeading()">Toggle Heading</button>
<h1 *ngIf="showHeading">Hello {{name}}</h1>

<h3>List of Heroes</h3>
<div *ngFor="let hero of heroes">{{hero}}</div>

app.component.ts

import { Component } from '@angular/core';

@Component({
  moduleId: module.id,
  selector: 'my-app',
  templateUrl: './app.component.html'
})
export class AppComponent {
  name: string = 'Angular';
  showHeading = true;
  heroes = ['Magneta', 'Bombasto', 'Magma', 'Tornado'];

  toggleHeading() {
    this.showHeading = !this.showHeading;
  }
}

安装 npm 依赖:

npm install @angular/compiler-cli @angular/platform-server --save

在项目根目录新增 tsconfig-aot.json 配置文件,内容如下:

{
  "compilerOptions": {
    "target": "es5",
    "module": "es2015",
    "moduleResolution": "node",
    "sourceMap": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "lib": ["es2015", "dom"],
    "noImplicitAny": true,
    "suppressImplicitAnyIndexErrors": true
  },

  "files": [
    "src/app/app.module.ts",
    "src/main.ts"
  ],

  "angularCompilerOptions": {
   "genDir": "aot",
   "skipMetadataEmit" : true
 }
}

执行 AoT 编译:

  • node_modules/.bin/ngc -p tsconfig-aot.json

  • "node_modules/.bin/ngc" -p tsconfig-aot.json # Windows 用户

命令成功运行后,在根目录下会自动生成 aot 目录,接下来我们来研究一下目录中生成的文件:

*.component.ngfactory.ts

此类文件内包含以下定义:

  • View_{COMPONENT}_Host{COUNTER} - 内部宿主组件

  • View_{COMPONENT}{COUNTER} - 内部组件

上面的 {COMPONENT} 表示组件关联的类名称,{COUNTER} 是一个无符号整数。

它们都继承于 AppView 并实现以下方法:

  • createInternal - 用于渲染组件

  • destroyInternal - 用于执行清理操作,如移除事件监听、销毁内嵌视图

  • detectChangesInternal - 用于执行变化检测

其中 detectChangesInternal 方法中包含了 JavaScript VM Friendly 的代码,现在我们来看一下具体示例:

<h1 *ngIf="showHeading">Hello {{name}}</h1>

该模板编译后,detectChangesInternal 方法中的代码如下:

 detectChangesInternal(throwOnChange:boolean):void {
   // 计算h1标签中文本元素的内容
    const currVal_2:any = import3.inlineInterpolate(1,'Hello ' 
      ,this.parentView.context.name,'');
   // 判断新值与旧值是否相等,若不相等则更新文本的内容,同时设置旧值为当前值
    if (import3.checkBinding(throwOnChange,this._expr_2,currVal_2)) {
      this.renderer.setText(this._text_1,currVal_2);
      this._expr_2 = currVal_2;
    }
  }

接下来我们来看一下 Angular 1.x 中简易版 $digest

// $scope.$watch('name', function(newValue, oldValue) {})
Scope.prototype.$watch = function (exp, fn) {
  'use strict';
  this.$$watchers.push({
    exp: exp,
    fn: fn,
    last: Utils.clone(this.$eval(exp))
  });
};

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); // 在Angular1.x的源码中会有TTL值控制最大的检测次数,避免出现死循环
  for (i = 0; i < this.$$children.length; i += 1) {
    this.$$children[i].$digest();
  }
};

从上面的代码可以看出,Angular 1.x 中变化检测涉及循环遍历比 Angular 2 的变化检测逻辑复杂很多。此外 Angular 2 的变化检测是单向的,从根组件开始执行,具体如下图:

图片描述

更令人兴奋的是,我们还可以灵活地设置 ChangeDetectionStrategy (变化检测策略) 来进一步提供应用的性能。

AOT实战

1.使用 ngc 命令行工具

示例项目:

2.使用 @ngtools/webpack Webpack 2 插件

webpack.config.js 配置:

'use strict';
let path = require('path');
let AotPlugin = require('@ngtools/webpack').AotPlugin;

module.exports = {
  module: {
    rules: [
      { test: /\.ts/, use: '@ngtools/webpack' }
    ]
  },
  
  plugins: [
    new AotPlugin({
      tsConfigPath: path.join(process.cwd(), 'tsconfig.json'),
      entryModule: path.join(process.cwd(), 'src/app/modules/main.module#MainModule')
    })
  ]
};

示例项目:

3.使用 @ultimate/aot-loader Webpack 2 插件

webpack.config.js 配置:

'use strict';
let path = require('path');
let AotPlugin = require('@ultimate/aot-loader').AotPlugin;

module.exports = {
  module: {
    rules: [
      { test: /\.ts/, use: '@ultimate/aot-loader' }
    ]
  },
  
  plugins: [
    new AotPlugin({
      tsConfig: path.join(process.cwd(), 'tsconfig.json'),
      entryModule: path.join(process.cwd(), 'src/app/modules/main.module#MainModule')
    })
  ]
};

示例项目:

参考资源


全栈修仙之路
聚焦全栈,专注分享 Angular、TypeScript、Node.js/Java 、Spring 技术栈等全栈干货。 欢迎小伙伴们关注...

[链接]

15.7k 声望
10.1k 粉丝
0 条评论
推荐阅读
8.9W 播放量的 TS 动画版进阶教程合集来了!
阿宝哥精心准备的 《轻松学 TypeScript》 视频教程来咯,强烈推荐!通过形象生动的动画,让你轻松搞懂 TypeScript 的难点和核心知识点!目前已更新到第 15 期,其中 1 期的观看量已破 1.9W,该合集的播放量目前已...

阿宝哥34阅读 2.4k评论 2

封面图
快速构建页面结构的 3D Visualization
可以通过 控制台 --&gt; 右边的三个小点 --&gt; More Tools --&gt; Layers 打开。即可以看到页面的一个 3D 层级关系,像是这样:

chokcoco6阅读 1.6k

前端性能优化到底该怎么做(上)— 开门见山
前端性能优化 又是个听起来很高大上的词,确实是的,因为它需要 高在性能,大在范围,所幸很多大佬都已经输出了很多高质量的内容供大家参考,作者最近也在学习和了解这方面的内容,对如下文中的一些理解若有不当...

熊的猫10阅读 2.1k

封面图
UI 提供的【progress-step】要🙉怎么🙊实现!!!
这天突然收到了 UI 修改设计稿的消息通知:"xxx 已修改 xxx 项目并 @ 了你,请及时查看变更内容",一条、两条、三条 ......,修改消息铺天盖地而来,然后就什么都看不到了(因为我选择开启消息免打扰)!,但没多...

熊的猫8阅读 1k

封面图
常见 JavaScript 设计模式 — 原来这么简单
设计模式总共有 23 种,但在前端领域其实没必要全部都去学习,毕竟大部分的设计模式是在 JavaScript 中占的比重并不是那么大,本文会列举出一些 JavaScript 常见的、容易被忽视的设计模式,不过还是有必要先简单...

熊的猫14阅读 1.6k

封面图
2022 最受欢迎的 CSS 伪类、伪元素分别是什么
本文首发于微信公众号:大迁世界, 我的微信:qq449245884,我会第一时间和你分享前端行业趋势,学习途径等等。更多开源作品请看 GitHub [链接] ,包含一线大厂面试完整考点、资料以及我的系列文章。

前端小智5阅读 836

8个酷炫的GitHub技巧,让你看起来像大佬一样!
微信搜索 【大迁世界】, 我会第一时间和你分享前端行业趋势,学习途径等等。本文 GitHub [链接] 已收录,有一线大厂面试完整考点、资料以及我的系列文章。

前端小智7阅读 533

[链接]

15.7k 声望
10.1k 粉丝
宣传栏