Angular 2 JIT vs AOT

在 Angular 應用程序中,包含了咱們經過 Angular 提供的 API 實現的自定義指令。這些自定義指令對瀏覽器來講,都是沒法識別的,所以每一個 Angular 應用程序在運行前,都須要經歷一個編譯的階段。javascript

在 Angular 2 中有兩種編譯模式:html

  • JIT - Just-In-Timejava

  • AOT - Ahead-Of-Timenode

JIT - Just-In-Time

圖片描述

Just-in-Time 編譯模式開發流程webpack

  • 使用 TypeScript 開發 Angular 應用git

  • 運行 tsc 編譯 TypeScript 代碼angularjs

  • 使用 Webpack 或 Gulp 等其餘工具構建項目,如代碼壓縮、合併等github

  • 部署應用web

應用部署後,當用戶經過瀏覽器訪問咱們應用的時候,她將經歷如下步驟(非嚴格 CSP):typescript

  • 下載應用相關的資源,如 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')
    })
  ]
};

示例項目:

參考資源

相關文章
相關標籤/搜索