在 Angular 應用程序中,包含了咱們經過 Angular 提供的 API 實現的自定義指令。這些自定義指令對瀏覽器來講,都是沒法識別的,所以每一個 Angular 應用程序在運行前,都須要經歷一個編譯的階段。javascript
在 Angular 2 中有兩種編譯模式:html
JIT - Just-In-Timejava
AOT - Ahead-Of-Timenode
Just-in-Time 編譯模式開發流程webpack
使用 TypeScript 開發 Angular 應用git
運行 tsc 編譯 TypeScript 代碼angularjs
使用 Webpack 或 Gulp 等其餘工具構建項目,如代碼壓縮、合併等github
部署應用web
應用部署後,當用戶經過瀏覽器訪問咱們應用的時候,她將經歷如下步驟(非嚴格 CSP):typescript
下載應用相關的資源,如 JavaScript 文件、圖片、樣式資源
Angular 啓動
Angular 進入 JiT 編譯模式,開始編譯咱們應用中的指令或組件,生成相應的 JavaScript 代碼
應用完成渲染
Ahead-Of-Time 編譯模式開發流程
使用 TypeScript 開發 Angular 應用
運行 ngc 編譯應用程序
使用 Angular Compiler 編譯模板,通常輸出 TypeScript 代碼
運行 tsc 編譯 TypeScript 代碼
使用 Webpack 或 Gulp 等其餘工具構建項目,如代碼壓縮、合併等
部署應用
應用部署後,相比於 JIT 編譯模式,在 AOT 模式下用戶訪問咱們的應用,只需經歷如下步驟:
下載應用相關的資源,如 JavaScript 文件、圖片、樣式資源
Angular 啓動
應用完成渲染
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 資源的佔比。
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 (變化檢測策略) 來進一步提供應用的性能。
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') }) ] };
示例項目: