在 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') }) ] };
示例项目: