新聞中心
預(yù)先(AOT)編譯器
Angular 應(yīng)用主要由組件及其 HTML 模板組成。由于瀏覽器無法直接理解 Angular 所提供的組件和模板,因此 Angular 應(yīng)用程序需要先進行編譯才能在瀏覽器中運行。

創(chuàng)新互聯(lián)建站秉承實現(xiàn)全網(wǎng)價值營銷的理念,以專業(yè)定制企業(yè)官網(wǎng),網(wǎng)站建設(shè)、成都做網(wǎng)站,小程序開發(fā),網(wǎng)頁設(shè)計制作,手機網(wǎng)站制作設(shè)計,成都全網(wǎng)營銷推廣幫助傳統(tǒng)企業(yè)實現(xiàn)“互聯(lián)網(wǎng)+”轉(zhuǎn)型升級專業(yè)定制企業(yè)官網(wǎng),公司注重人才、技術(shù)和管理,匯聚了一批優(yōu)秀的互聯(lián)網(wǎng)技術(shù)人才,對客戶都以感恩的心態(tài)奉獻自己的專業(yè)和所長。
在瀏覽器下載和運行代碼之前的編譯階段,Angular 預(yù)先(AOT)編譯器會先把你的 Angular HTML 和 TypeScript 代碼轉(zhuǎn)換成高效的 JavaScript 代碼。在構(gòu)建期間編譯應(yīng)用可以讓瀏覽器中的渲染更快速。
本指南中解釋了如何指定元數(shù)據(jù),并使用一些編譯器選項以借助 AOT 編譯器來更有效的編譯應(yīng)用。
觀看 Alex Rickabaugh 在 AngularConnect 2019 解釋 Angular 編譯器的演講。
下面是你可能要使用 AOT 的部分原因。
|
原因 |
詳情 |
|---|---|
|
更快的渲染方式 |
使用 AOT,瀏覽器會下載應(yīng)用程序的預(yù)編譯版本。瀏覽器加載可執(zhí)行代碼,以便立即渲染應(yīng)用程序,而無需等待先編譯應(yīng)用程序。 |
|
更少的異步請求 |
編譯器在應(yīng)用程序 JavaScript 中內(nèi)聯(lián)外部 HTML 模板和 CSS 樣式表,消除對這些源文件的單個 ajax 請求。 |
|
更小的 Angular 框架下載大小 |
如果應(yīng)用程序已被編譯,則無需下載 Angular 編譯器。編譯器大約是 Angular 本身的一半,因此省略它會大大減少應(yīng)用程序的體積。 |
|
及早檢測模板錯誤 |
AOT 編譯器會在用戶看到之前在構(gòu)建步驟中檢測并報告模板綁定錯誤。 |
|
更好的安全性 |
AOT 會在 HTML 模板和組件提供給客戶端之前就將它們編譯為 JavaScript 文件。由于沒有要讀取的模板,也沒有危險的客戶端 HTML 或 JavaScript 求值,因此注入攻擊的機會更少。 |
選擇編譯器
Angular 提供了兩種方式來編譯你的應(yīng)用:
|
ANGULAR 編譯方式 |
詳情 |
|---|---|
|
即時 (JIT) |
當(dāng)運行時在瀏覽器中編譯你的應(yīng)用程序。在 Angular 8 之前,這是默認(rèn)值。 |
|
預(yù)先 (AOT) |
在構(gòu)建時編譯你的應(yīng)用程序和庫。這是從 Angular 9 開始的默認(rèn)值。 |
當(dāng)運行 CLI 命令 ?ng build? (只構(gòu)建) 或 ?ng serve? (構(gòu)建并啟動本地服務(wù)器) 時,編譯類型(JIT 或 AOT)取決于你在 ?angular.json? 中的構(gòu)建配置所指定的 ?aot ?屬性。默認(rèn)情況下,對于新的 CLI 應(yīng)用,其 ?aot ?為 ?true?。
AOT 工作原理
Angular AOT 編譯器會提取元數(shù)據(jù)來解釋應(yīng)由 Angular 管理的應(yīng)用程序部分。你可以在裝飾器(比如 ?@Component()? 和 ?@Input()?)中顯式指定元數(shù)據(jù),也可以在被裝飾的類的構(gòu)造函數(shù)聲明中隱式指定元數(shù)據(jù)。元數(shù)據(jù)告訴 Angular 要如何構(gòu)造應(yīng)用程序類的實例并在運行時與它們進行交互。
在下列范例中,?@Component()? 元數(shù)據(jù)對象和類的構(gòu)造函數(shù)會告訴 Angular 如何創(chuàng)建和顯示 ?TypicalComponent ?的實例。
@Component({
selector: 'app-typical',
template: 'A typical component for {{data.name}}'
})
export class TypicalComponent {
@Input() data: TypicalData;
constructor(private someService: SomeService) { … }
}Angular 編譯器只提取一次元數(shù)據(jù),并且為 ?TypicalComponent ?生成一個工廠。當(dāng)它需要創(chuàng)建 ?TypicalComponent ?的實例時,Angular 調(diào)用這個工廠,工廠會生成一個新的可視元素,并且把它(及其依賴)綁定到組件類的一個新實例上。
編譯的各個階段
AOT 編譯分為三個階段。
|
階段 |
詳情 |
|
|---|---|---|
| 1 |
代碼分析 |
在此階段,TypeScript 編譯器和AOT 收集器會創(chuàng)建源代碼的表示。收集器不會嘗試解釋它收集的元數(shù)據(jù)。它會盡可能地表示元數(shù)據(jù),并在檢測到元數(shù)據(jù)語法違規(guī)時記錄錯誤。 |
| 2 |
代碼生成 |
在此階段,編譯器的 |
| 3 |
模板類型檢查 |
在此可選階段,Angular 模板編譯器使用 TypeScript 編譯器來驗證模板中的綁定表達式。你可以通過設(shè)置 |
元數(shù)據(jù)的限制
你只能使用 TypeScript 的一個子集書寫元數(shù)據(jù),它必須滿足下列限制:
- 表達式語法只支持 JavaScript 的一個有限的子集
- 只能引用代碼收縮后導(dǎo)出的符號
- 只能調(diào)用編譯器支持的函數(shù)
- 被裝飾和用于數(shù)據(jù)綁定的類成員必須是公共(public)的
關(guān)于準(zhǔn)備 AOT 編譯應(yīng)用程序的其它準(zhǔn)則和說明,參閱 Angular:編寫 AOT 友好的應(yīng)用程序。
AOT 編譯中的錯誤通常是由于元數(shù)據(jù)不符合編譯器的要求而發(fā)生的(下面將更全面地介紹)。
配置 AOT 編譯
你可以在 ?tsconfig.json? TypeScript 配置文件中提供控制編譯過程的選項。
階段 1:分析
TypeScript 編譯器會做一些初步的分析工作,它會生成類型定義文件?.d.ts?,其中帶有類型信息,Angular 編譯器需要借助它們來生成代碼。 同時,AOT 收集器(collector) 會記錄 Angular 裝飾器中的元數(shù)據(jù),并把它們輸出到?.metadata.json?文件中,和每個 ?.d.ts? 文件相對應(yīng)。
你可以把 ?.metadata.json? 文件看做一個包括全部裝飾器的元數(shù)據(jù)的全景圖,就像抽象語法樹 (AST)一樣。
Angular 的 ?
schema.ts? 會將 JSON 格式描述為 TypeScript 接口的集合。
表達式語法限制
AOT 收集器只能理解 JavaScript 的一個子集。定義元數(shù)據(jù)對象時要遵循下列語法限制:
|
語法 |
范例 |
|---|---|
|
對象字面量 |
{cherry: true, apple: true, mincemeat: false} |
|
數(shù)組字面量 |
['cherries', 'flour', 'sugar'] |
|
展開數(shù)組字面量 |
['apples', 'flour', ...] |
|
函數(shù)調(diào)用 |
bake(ingredients) |
|
新建對象 |
new Oven() |
|
屬性訪問 |
pie.slice |
|
數(shù)組索引訪問 |
ingredients[0] |
|
引用標(biāo)識符 |
Component |
|
模板字符串 |
`pie is ${multiplier} times better than cake` |
|
字符串字面量 |
'pi' |
|
數(shù)字字面量 |
3.14153265 |
|
邏輯字面量 |
true |
|
null 字面量 |
null |
|
受支持的前綴運算符 |
!cake |
|
受支持的二元運算符 |
a+b |
|
條件運算符 |
a ? b : c |
|
括號 |
(a+b) |
如果表達式使用了不支持的語法,收集器就會往 ?.metadata.json? 文件中寫入一個錯誤節(jié)點。稍后,如果編譯器用到元數(shù)據(jù)中的這部分內(nèi)容來生成應(yīng)用代碼,它就會報告這個錯誤。
如果你希望 ?
ngc?立即匯報這些語法錯誤,而不要生成帶有錯誤信息的 ?.metadata.json? 文件,可以到 TypeScript 的配置文件中設(shè)置 ?strictMetadataEmit?選項。"angularCompilerOptions": { … "strictMetadataEmit" : true }Angular 庫通過這個選項來確保所有的 Angular ?
.metadata.json? 文件都是干凈的。當(dāng)你要構(gòu)建自己的代碼庫時,這也同樣是一項最佳實踐。
不要有箭頭函數(shù)
AOT 編譯器不支持函數(shù)表達式和箭頭函數(shù),也叫 lambda 函數(shù)。
考慮如下組件裝飾器:
@Component({
…
providers: [{provide: server, useFactory: () => new Server()}]
})AOT 的收集器不支持在元數(shù)據(jù)表達式中出現(xiàn)箭頭函數(shù) ?() => new Server()?。它會在該函數(shù)中就地生成一個錯誤節(jié)點。稍后,當(dāng)編譯器解釋該節(jié)點時,它就會報告一個錯誤,讓你把這個箭頭函數(shù)轉(zhuǎn)換成一個導(dǎo)出的函數(shù)。
你可以把它改寫成這樣來修復(fù)這個錯誤:
export function serverFactory() {
return new Server();
}
@Component({
…
providers: [{provide: server, useFactory: serverFactory}]
})在版本 5 和更高版本中,編譯器會在發(fā)出 ?.js? 文件時自動執(zhí)行此重寫。
代碼折疊
編譯器只會解析到已導(dǎo)出符號的引用。收集器可以在收集期間執(zhí)行表達式,并用其結(jié)果記錄到 ?.metadata.json? 中(而不是原始表達式中)。這樣可以讓你把非導(dǎo)出符號的使用限制在表達式中。
比如,收集器可以估算表達式 ?1 + 2 + 3 + 4? 并將其替換為結(jié)果 ?10?。這個過程稱為?折疊???梢杂眠@種方式簡化的表達式是可折疊的。
收集器可以計算對模塊局部變量的 ?const ?聲明和初始化過的 ?var ?和 ?let ?聲明,并從 ?.metadata.json? 文件中移除它們。
考慮下列組件定義:
const template = '{{hero.name}}';
@Component({
selector: 'app-hero',
template: template
})
export class HeroComponent {
@Input() hero: Hero;
}編譯器不能引用 ?template ?常量,因為它是未導(dǎo)出的。但是收集器可以通過內(nèi)聯(lián) ?template ?常量的方式把它折疊進元數(shù)據(jù)定義中。最終的結(jié)果和你以前的寫法是一樣的:
@Component({
selector: 'app-hero',
template: '{{hero.name}}'
})
export class HeroComponent {
@Input() hero: Hero;
}這里沒有對 ?template ?的引用,因此,當(dāng)編譯器稍后對位于 ?.metadata.json? 中的收集器輸出進行解釋時,不會再出問題。
你還可以通過把 ?template ?常量包含在其它表達式中來讓這個例子深入一點:
const template = '{{hero.name}}';
@Component({
selector: 'app-hero',
template: template + '{{hero.title}}'
})
export class HeroComponent {
@Input() hero: Hero;
}收集器把該表達式縮減成其等價的已折疊字符串:
'{{hero.name}}{{hero.title}}'可折疊的語法
下表中描述了收集器可以折疊以及不能折疊哪些表達式:
|
語法 |
可折疊? |
|---|---|
|
對象字面量 |
是 |
|
數(shù)組字面量 |
是 |
|
展開數(shù)組字面量 |
否 |
|
函數(shù)調(diào)用 |
否 |
|
新建對象 |
否 |
|
屬性訪問 |
如果目標(biāo)對象也是可折疊的,則是 |
|
數(shù)組索引訪問 |
如果目標(biāo)數(shù)組和索引都是可折疊的,則是 |
|
引用標(biāo)識符 |
如果引用的是局部標(biāo)識符,則是 |
|
沒有替換表達式的模板字符串 |
是 |
|
有替換表達式的模板字符串 |
如果替換表達式是可折疊的,則是 |
|
字符串字面量 |
是 |
|
數(shù)字字面量 |
是 |
|
邏輯字面量 |
是 |
|
null 字面量 |
是 |
|
受支持的前綴運算符 |
如果操作數(shù)是可折疊的,則是 |
|
受支持的二元運算符 |
如果左操作數(shù)和右操作數(shù)都是可折疊的,則是 |
|
條件運算符 |
如果條件是可折疊的,則是 |
|
括號 |
如果表達式是可折疊的,則是 |
如果表達式是不可折疊的,那么收集器就會把它作為一個 AST(抽象語法樹)寫入 ?.metadata.json? 中,留給編譯器去解析。
階段 2:代碼生成
收集器不會試圖理解它收集并輸出到 ?.metadata.json? 中的元數(shù)據(jù),它所能做的只是盡可能準(zhǔn)確的表述這些元數(shù)據(jù),并在檢測到元數(shù)據(jù)中的語法違規(guī)時記錄這些錯誤。解釋這些 ?.metadata.json? 是編譯器在代碼生成階段要承擔(dān)的工作。
編譯器理解收集器支持的所有語法形式,但是它也可能拒絕那些雖然語法正確但語義違反了編譯器規(guī)則的元數(shù)據(jù)。
公共符號
編譯器只能引用已導(dǎo)出的符號。
- 帶有裝飾器的類成員必須是公開的。你不可能把一個私有或內(nèi)部使用的屬性做成 ?
@Input()?。 - 數(shù)據(jù)綁定的屬性同樣必須是公開的
支持的類和函數(shù)
只要語法有效,收集器就可以用 ?new ?來表示函數(shù)調(diào)用或?qū)ο髣?chuàng)建。但是,編譯器在后面可以拒絕生成對特定函數(shù)的調(diào)用或?qū)?b>特定對象的創(chuàng)建。
編譯器只能創(chuàng)建某些類的實例,僅支持核心裝飾器,并且僅支持對返回表達式的宏(函數(shù)或靜態(tài)方法)的調(diào)用。
|
編譯器動作 |
詳情 |
|---|---|
|
新建實例 |
編譯器只允許創(chuàng)建來自 |
|
支持的裝飾器 |
編譯器只支持來自 ? |
|
函數(shù)調(diào)用 |
工廠函數(shù)必須導(dǎo)出為命名函數(shù)。AOT 編譯器不支持用 Lambda 表達式(箭頭函數(shù))充當(dāng)工廠函數(shù)。 |
函數(shù)和靜態(tài)方法調(diào)用
收集器接受任何只包含一個 ?return ?語句的函數(shù)或靜態(tài)方法。編譯器也支持在返回表達式的函數(shù)或靜態(tài)函數(shù)中使用宏。
考慮下面的函數(shù):
export function wrapInArray(value: T): T[] {
return [value];
} 你可以在元數(shù)據(jù)定義中調(diào)用 ?wrapInArray?,因為它所返回的表達式的值滿足編譯器支持的 JavaScript 受限子集。
你還可以這樣使用 ?wrapInArray()?:
@NgModule({
declarations: wrapInArray(TypicalComponent)
})
export class TypicalModule {}編譯器會把這種用法處理成你以前的寫法:
@NgModule({
declarations: [TypicalComponent]
})
export class TypicalModule {}Angular 的 ?RouterModule ?導(dǎo)出了兩個靜態(tài)宏函數(shù) ?forRoot ?和 ?forChild?,以幫助聲明根路由和子路由。 查看這些方法的源碼,以了解宏函數(shù)是如何簡化復(fù)雜的 ?NgModule ?配置的。
元數(shù)據(jù)重寫
編譯器會對含有 ?useClass?、?useValue?、?useFactory ?和 ?data ?的對象字面量進行特殊處理,把用這些字段之一初始化的表達式轉(zhuǎn)換成一個導(dǎo)出的變量,并用它替換該表達式。這個重寫表達式的過程,會消除它們受到的所有限制,因為編譯器并不需要知道該表達式的值,它只要能生成對該值的引用就行了。
你可以這樣寫:
class TypicalServer {
}
@NgModule({
providers: [{provide: SERVER, useFactory: () => TypicalServer}]
})
export class TypicalModule {}如果不重寫,這就是無效的,因為這里不支持 Lambda 表達式,而且 ?TypicalServer ?也沒有被導(dǎo)出。為了支持這種寫法,編譯器自動把它重寫成了這樣:
class TypicalServer {
}
export const θ0 = () => new TypicalServer();
@NgModule({
providers: [{provide: SERVER, useFactory: θ0}]
})
export class TypicalModule {}這就讓編譯器能在工廠中生成一個對 ?θ0? 的引用,而不用知道 ?θ0? 中包含的值到底是什么。
編譯器會在生成 ?.js? 文件期間進行這種重寫。它不會重寫 ?.d.ts? 文件,所以 TypeScript 也不會把這個變量當(dāng)做一項導(dǎo)出,因此也就不會污染 ES 模塊中導(dǎo)出的 API。
階段 3:模板類型檢查
Angular 編譯器最有用的功能之一就是能夠?qū)δ0逯械谋磉_式進行類型檢查,在由于出錯而導(dǎo)致運行時崩潰之前就捕獲任何錯誤。在模板類型檢查階段,Angular 模板編譯器會使用 TypeScript 編譯器來驗證模板中的綁定表達式。
通過在該項目的 TypeScript 配置文件中的 ?"angularCompilerOptions"? 中添加編譯器選項 ?"fullTemplateTypeCheck"?,可以顯式啟用本階段。
當(dāng)模板綁定表達式中檢測到類型錯誤時,進行模板驗證時就會生成錯誤。這和 TypeScript 編譯器在處理 ?.ts? 文件中的代碼時報告錯誤很相似。
比如,考慮下列組件:
@Component({
selector: 'my-component',
template: '{{person.addresss.street}}'
})
class MyComponent {
person?: Person;
}這會生成如下錯誤:
my.component.ts.MyComponent.html(1,1): : Property 'addresss' does not exist on type 'Person'. Did you mean 'address'?錯誤信息中匯報的文件名 ?my.component.ts.MyComponent.html? 是一個由模板編譯器生成出的合成文件,用于保存 ?MyComponent ?類的模板內(nèi)容。編譯器永遠(yuǎn)不會把這個文件寫入磁盤。這個例子中,這里的行號和列號都是相對于 ?MyComponent ?的 ?@Component? 注解中的模板字符串的。如果組件使用 ?templateUrl ?來代替 ?template?,這些錯誤就會在 ?templateUrl ?引用的 HTML 文件中匯報,而不是這個合成文件中。
錯誤的位置是從包含出錯的插值表達式的那個文本節(jié)點開始的。如果錯誤是一個屬性綁定,比如 ?[value]="person.address.street"?,錯誤的位置就是那個包含錯誤的屬性的位置。
驗證使用 TypeScript 類型檢查器和提供給 TypeScript 編譯器的選項來控制類型驗證的詳細(xì)程度。比如,如果指定了 ?strictTypeChecks?,則會報告錯誤以及下述錯誤消息。
my.component.ts.MyComponent.html(1,1): : Object is possibly 'undefined'類型窄化
在 ?ngIf ?指令中使用的表達式用來在 Angular 模板編譯器中窄化聯(lián)合類型,就像 TypeScript 中的 ?if ?表達式一樣。比如,要在上述模板中消除 ?Object is possibly 'undefined'? 錯誤,可以把它改成只在 ?person ?的值初始化過的時候才生成這個插值。
@Component({
selector: 'my-component',
template: ' {{person.address.street}} '
})
class MyComponent {
person?: Person;
}使用 ?*ngIf? 能讓 TypeScript 編譯器推斷出這個綁定表達式中使用的 ?person ?永遠(yuǎn)不會是 ?undefined?。
非空類型斷言操作符
使用 非空類型斷言操作符 可以在不方便使用 ?*ngIf? 或 當(dāng)組件中的某些約束可以確保這個綁定表達式在求值時永遠(yuǎn)不會為空時,防止出現(xiàn) ?Object is possibly 'undefined'? 錯誤。
在下面的例子中,?person ?和 ?address ?屬性總是一起出現(xiàn)的,如果 ?person ?非空,則 ?address ?也一定非空。沒有一種簡便的寫法可以向 TypeScript 和模板編譯器描述這種約束。但是這個例子中使用 ?address!.street? 避免了報錯。
@Component({
selector: 'my-component',
template: ' {{person.name}} lives on {{address!.street}} '
})
class MyComponent {
person?: Person;
address?: Address;
setData(person: Person, address: Address) {
this.person = person;
this.address = address;
}
}應(yīng)該保守點使用非空斷言操作符,因為將來對組件的重構(gòu)可能會破壞這個約束。
這個例子中,更建議在 ?*ngIf? 中包含對 ?address ?的檢查,代碼如下:
@Component({
selector: 'my-component',
template: ' {{person.name}} lives on {{address.street}} '
})
class MyComponent {
person?: Person;
address?: Address;
setData(person: Person, address: Address) {
this.person = person;
this.address = address;
}
} 名稱欄目:創(chuàng)新互聯(lián)Angular教程:Angular預(yù)先編譯
網(wǎng)頁路徑:http://fisionsoft.com.cn/article/dhdjjie.html


咨詢
建站咨詢
