如何評價 angular 2 中的 AoT?

  1. 相比於 JIT 有何優缺點,對於打包文件大小和渲染性能到底提升有多大 ?

  2. 內部如何實現的,貌似現在只支持 ts ?
  3. 其他陣營的 React 和 Vue 是否有希望實現該功能 ?


1. 相比於 JIT 有何優缺點,對於打包文件大小和渲染性能到底提升有多大 ?

Angular 的 AOT/JIT 和一般說的編程語言的 AOT/JIT 是兩回事。。通常所說的 JIT 的優勢是 Profile-Based Optimization,也就是邊跑邊優化,根據運行時信息然後隨著時間的推移得到儘可能最優的代碼。。

而 Angular 的 JIT 只是把模版的編譯過程帶到運行時,但並沒有參考任何運行時信息(僅僅依賴於一些 Metadata),也只是一次性全量(NgModule 級別)編譯,沒有動態優化的過程。。

所以 Angular 的 JIT 相比 AOT 可以說沒有任何優勢。。反而由於增加運行時間(編譯過程)和運行時依賴大小(編譯器自身)。。而且還會影響安全性(要 eval)。。帶來了大量劣勢。。

PS:因為採用 Code Generation 的方式,生成的 ts/js 代碼肯定是會比原來的 html 的文件大小要大的(不理解為什麼更大的話可以參考附錄三),所以在應用足夠大(模版足夠多)的情況下 AOT 的大小是可以反超 JIT 的大小的。。不過這個時候不用 AOT 恐怕啟動時間早就無法容忍了。。

除非確實有動態組件的需求,否則唯一的優勢就是能用來做在線 Demo 吧。。

性能和大小可以自己試試唄。。僅僅編譯器模塊 Minify + Gzip 之後都超過 100 KB。。(遠超其他模塊大小)

2. 內部如何實現的,貌似現在只支持 ts ?

內部就是一個真真切切的編譯過程。。(詳見附錄三)

Angular 模版編譯是直接生成控制項操作的代碼(但不是直接操作 DOM,有抽象層,Angular 本身是跨平台的),包括初始化時的和變化時的,簡單的說就是直接在編譯時確實各種情況下需要的最小控制項操作部分,相比於 JSX 簡單的語法轉換要複雜得多。。

AOT 下會生成 ts(也不支持使用 js 的方式),然後你需要和業務代碼一起把 ts 處理成 js,怎麼處理是項目構建自己的事情。。JIT 下是直接生成 js 然後直接 eval。。

Angular 中 AOT 和 JIT 所執行的工作本質上並沒有什麼區別。。所以其實 JIT 怎麼實現 AOT 也基本就是怎麼實現。。並不是說使用 JIT 就會把模版交給瀏覽器處理了,仍然一樣是模版預編譯成邏輯代碼的方式。。

3. 其他陣營的 React 和 Vue 是否有希望實現該功能 ?

基於 Code Generation 的方式(從理論上)可以做到最高程度的優化。。但實際上也會留很多坑。。因為抽象層次最高,幾乎可以做到實現方式無關性(哪怕某一天換成 Virtual-DOM 也不需要在 API 層面發生變化)。。

React 這樣每次重新 render 的方式已經是寫死的只能用 Virtual-DOM 來 Diff 了,所以也不需要(按現有 API 也幾乎不能夠)使用更高級別的抽象。。

Vue 2.0 的話有兩種方式,render 函數的方式和 React 沒什麼區別,而模版的方式其實也只是一個簡單翻譯到 render 函數的過程,仍然受限於 Virtual-DOM 的方式,但鑒於目前 Virtual-DOM 的方式還沒有出現性能瓶頸,所以目前來說也並沒有什麼問題。。

兩者目前都沒有放棄 Virtual-DOM 的必要。。所以當然也沒有必要實現該 「功能」(語義級別的模版編譯)。。當然 JSX 到 JS 的編譯(語法級別)也還是可以(也幾乎一定都會)在編譯時來做。。然後前面也說到 Vue 2.0 也是有一個小小的模版編譯的(還是語法級別,額外開銷很小)。。&從可行性來說當然也可以放到編譯時來做,沒有調研過不太確定當前的構建工具支持和主流趨勢情況。。&感謝尤老師的說明,Vue 2.0 的模版編譯過程在 vue-loader 或者 vueify 下默認就是 AOT 了 -_-||。。

所以如果只是說構建時的代碼預處理的話,React 和 Vue 都有,並不是 Angular 的特例。

附錄一:React JSX 的編譯

編譯前:

const element = (
&

Hello, world!
& );

編譯後:

const element = React.createElement(
"h1",
{className: "greeting"},
"Hello, world!"
);

附錄二:Vue 2 Template 的編譯

編譯前:

&
&I"m a template!& &

{{ message }}
& &

No message.
& &

編譯後:

function anonymous() {
with(this) {
return _h(
"div",
[
_h("h1",["I"m a template!"]),
(message) ?
_h("p",[_s(message)]) :
_h("p",["No message."])
]
)
}
}

附錄三:Angular 2 Template 的編譯

編譯前:

&{{ title }}& & &

  • Hello {{ name }}& &

    編譯後(做好心理準備):

    /**
    * @fileoverview This file is generated by the Angular 2 template compiler.
    * Do not edit.
    * @suppress {suspiciousCode,uselessCode,missingProperties}
    */
    /* tslint:disable */

    import * as import0 from "./app.component";
    import * as import1 from "@angular/core/src/linker/view";
    import * as import2 from "@angular/core/src/render/api";
    import * as import3 from "@angular/core/src/linker/view_utils";
    import * as import4 from "@angular/core/src/metadata/view";
    import * as import5 from "@angular/core/src/linker/view_type";
    import * as import6 from "@angular/core/src/change_detection/change_detection";
    import * as import7 from "@angular/core/src/linker/component_factory";
    import * as import8 from "./app.component.css.shim";
    import * as import9 from "@angular/core/src/linker/query_list";
    import * as import10 from "@angular/core/src/linker/view_container";
    import * as import11 from "../../node_modules/@angular/common/src/directives/ng_for.ngfactory";
    import * as import12 from "@angular/core/src/linker/template_ref";
    import * as import13 from "@angular/core/src/change_detection/differs/iterable_differs";
    import * as import14 from "@angular/common/src/directives/ng_for";
    export class Wrapper_AppComponent {
    /*private*/ _eventHandler:Function;
    context:import0.AppComponent;
    /*private*/ _changed:boolean;
    constructor() {
    this._changed = false;
    this.context = new import0.AppComponent();
    }
    ngOnDetach(view:import1.AppView&,componentView:import1.AppView&,el:any):void {
    }
    ngOnDestroy():void {
    }
    ngDoCheck(view:import1.AppView&,el:any,throwOnChange:boolean):boolean {
    var changed:any = this._changed;
    this._changed = false;
    return changed;
    }
    checkHost(view:import1.AppView&,componentView:import1.AppView&,el:any,throwOnChange:boolean):void {
    }
    handleEvent(eventName:string,$event:any):boolean {
    var result:boolean = true;
    return result;
    }
    subscribe(view:import1.AppView&,_eventHandler:any):void {
    this._eventHandler = _eventHandler;
    }
    }
    var renderType_AppComponent_Host:import2.RenderComponentType = import3.createRenderComponentType("",0,import4.ViewEncapsulation.None,([] as any[]),{});
    class View_AppComponent_Host0 extends import1.AppView& {
    _el_0:any;
    compView_0:import1.AppView&;
    _AppComponent_0_3:Wrapper_AppComponent;
    constructor(viewUtils:import3.ViewUtils,parentView:import1.AppView&,parentIndex:number,parentElement:any) {
    super(View_AppComponent_Host0,renderType_AppComponent_Host,import5.ViewType.HOST,viewUtils,parentView,parentIndex,parentElement,import6.ChangeDetectorStatus.CheckAlways);
    }
    createInternal(rootSelector:string):import7.ComponentRef& {
    this._el_0 = import3.selectOrCreateRenderHostElement(this.renderer,"app-root",import3.EMPTY_INLINE_ARRAY,rootSelector,(null as any));
    this.compView_0 = new View_AppComponent0(this.viewUtils,this,0,this._el_0);
    this._AppComponent_0_3 = new Wrapper_AppComponent();
    this.compView_0.create(this._AppComponent_0_3.context);
    this.init(this._el_0,((&this.renderer).directRenderer? (null as any): [this._el_0]),(null as any));
    return new import7.ComponentRef_&(0,this,this._el_0,this._AppComponent_0_3.context);
    }
    injectorGetInternal(token:any,requestNodeIndex:number,notFoundResult:any):any {
    if (((token === import0.AppComponent) (0 === requestNodeIndex))) { return this._AppComponent_0_3.context; }
    return notFoundResult;
    }
    detectChangesInternal(throwOnChange:boolean):void {
    this._AppComponent_0_3.ngDoCheck(this,this._el_0,throwOnChange);
    this.compView_0.detectChanges(throwOnChange);
    }
    destroyInternal():void {
    this.compView_0.destroy();
    }
    visitRootNodesInternal(cb:any,ctx:any):void {
    cb(this._el_0,ctx);
    }
    }
    export const AppComponentNgFactory:import7.ComponentFactory& = new import7.ComponentFactory&("app-root",View_AppComponent_Host0,import0.AppComponent);
    const styles_AppComponent:any[] = [import8.styles];
    var renderType_AppComponent:import2.RenderComponentType = import3.createRenderComponentType("",0,import4.ViewEncapsulation.Emulated,styles_AppComponent,{});
    export class View_AppComponent0 extends import1.AppView& {
    _viewQuery_someThing_0:import9.QueryList&;
    _el_0:any;
    _text_1:any;
    _text_2:any;
    _el_3:any;
    _text_4:any;
    _anchor_5:any;
    /*private*/ _vc_5:import10.ViewContainer;
    _TemplateRef_5_5:any;
    _NgFor_5_6:import11.Wrapper_NgFor;
    _text_6:any;
    _text_7:any;
    /*private*/ _expr_12:any;
    constructor(viewUtils:import3.ViewUtils,parentView:import1.AppView&,parentIndex:number,parentElement:any) {
    super(View_AppComponent0,renderType_AppComponent,import5.ViewType.COMPONENT,viewUtils,parentView,parentIndex,parentElement,import6.ChangeDetectorStatus.CheckAlways);
    this._expr_12 = import6.UNINITIALIZED;
    }
    createInternal(rootSelector:string):import7.ComponentRef& {
    const parentRenderNode:any = this.renderer.createViewRoot(this.parentElement);
    this._viewQuery_someThing_0 = new import9.QueryList&();
    this._el_0 = import3.createRenderElement(this.renderer,parentRenderNode,"h1",import3.EMPTY_INLINE_ARRAY,(null as any));
    this._text_1 = this.renderer.createText(this._el_0,"",(null as any));
    this._text_2 = this.renderer.createText(parentRenderNode,"
    ",(null as any));
    this._el_3 = import3.createRenderElement(this.renderer,parentRenderNode,"ul",import3.EMPTY_INLINE_ARRAY,(null as any));
    this._text_4 = this.renderer.createText(this._el_3,"
    ",(null as any));
    this._anchor_5 = this.renderer.createTemplateAnchor(this._el_3,(null as any));
    this._vc_5 = new import10.ViewContainer(5,3,this,this._anchor_5);
    this._TemplateRef_5_5 = new import12.TemplateRef_(this,5,this._anchor_5);
    this._NgFor_5_6 = new import11.Wrapper_NgFor(this._vc_5.vcRef,this._TemplateRef_5_5,this.parentView.injectorGet(import13.IterableDiffers,this.parentIndex),this.ref);
    this._text_6 = this.renderer.createText(this._el_3,"
    ",(null as any));
    this._text_7 = this.renderer.createText(parentRenderNode,"
    ",(null as any));
    this._viewQuery_someThing_0.reset(([] as any[]));
    this.context.someElement = this._viewQuery_someThing_0.first;
    this.init((null as any),((&this.renderer).directRenderer? (null as any): [
    this._el_0,
    this._text_1,
    this._text_2,
    this._el_3,
    this._text_4,
    this._anchor_5,
    this._text_6,
    this._text_7
    ]
    ),(null as any));
    return (null as any);
    }
    injectorGetInternal(token:any,requestNodeIndex:number,notFoundResult:any):any {
    if (((token === import12.TemplateRef) (5 === requestNodeIndex))) { return this._TemplateRef_5_5; }
    if (((token === import14.NgFor) (5 === requestNodeIndex))) { return this._NgFor_5_6.context; }
    return notFoundResult;
    }
    detectChangesInternal(throwOnChange:boolean):void {
    const currVal_5_0_0:any = this.context.names;
    this._NgFor_5_6.check_ngForOf(currVal_5_0_0,throwOnChange,false);
    this._NgFor_5_6.ngDoCheck(this,this._anchor_5,throwOnChange);
    this._vc_5.detectChangesInNestedViews(throwOnChange);
    const currVal_12:any = import3.inlineInterpolate(1,"",this.context.title,"");
    if (import3.checkBinding(throwOnChange,this._expr_12,currVal_12)) {
    this.renderer.setText(this._text_1,currVal_12);
    this._expr_12 = currVal_12;
    }
    }
    destroyInternal():void {
    this._vc_5.destroyNestedViews();
    }
    createEmbeddedViewInternal(nodeIndex:number):import1.AppView& {
    if ((nodeIndex == 5)) { return new View_AppComponent1(this.viewUtils,this,5,this._anchor_5,this._vc_5); }
    return (null as any);
    }
    }
    class View_AppComponent1 extends import1.AppView& {
    _el_0:any;
    _text_1:any;
    /*private*/ _expr_2:any;
    constructor(viewUtils:import3.ViewUtils,parentView:import1.AppView&,parentIndex:number,parentElement:any,declaredViewContainer:import10.ViewContainer) {
    super(View_AppComponent1,renderType_AppComponent,import5.ViewType.EMBEDDED,viewUtils,parentView,parentIndex,parentElement,import6.ChangeDetectorStatus.CheckAlways,declaredViewContainer);
    this._expr_2 = import6.UNINITIALIZED;
    }
    createInternal(rootSelector:string):import7.ComponentRef& {
    this._el_0 = import3.createRenderElement(this.renderer,(null as any),"li",import3.EMPTY_INLINE_ARRAY,(null as any));
    this._text_1 = this.renderer.createText(this._el_0,"",(null as any));
    this.init(this._el_0,((&this.renderer).directRenderer? (null as any): [
    this._el_0,
    this._text_1
    ]
    ),(null as any));
    return (null as any);
    }
    detectChangesInternal(throwOnChange:boolean):void {
    const currVal_2:any = import3.inlineInterpolate(1,"Hello ",this.context.$implicit,"");
    if (import3.checkBinding(throwOnChange,this._expr_2,currVal_2)) {
    this.renderer.setText(this._text_1,currVal_2);
    this._expr_2 = currVal_2;
    }
    }
    visitRootNodesInternal(cb:any,ctx:any):void {
    cb(this._el_0,ctx);
    }
    }

    這就是為什麼只有 Angular 才需要專門提 AOT 這個概念。。O.O


    ng 的細節 @Trotyl Yu 說得挺清楚了。至於 Vue 這裡,有些細節有小區別,比如 Vue 是編譯模板到 render function,而 ng2 可能是編譯到針對虛擬渲染層的命令式操作,但這裡面的區別不是關鍵。關鍵是在前端的語境下 AoT 的意義是 1. 避免運行時編譯模板的代價;2. 避免需要把編譯器發送到瀏覽器里。

    從這個定義出發的話,Vue 2 的模板 -&> render function 編譯從一開始就一直支持所謂的 AoT... 當你用 vue-loader 或者 vueify 的時候默認就是了,也不需要額外配置。因為感覺根本不是個事兒所以從來沒特意宣傳過 -.-


    謝邀。

    【相比於 JIT 有何優缺點,對於打包文件大小和渲染性能到底提升有多大 ?】

    JiT是下載完整個模板之後在瀏覽器中解析模板並動態生成用來渲染的代碼(編譯),因此用到哪個模板就會編譯哪個模板,這就是JIT的含義,只是一個更大粒度上的JIT,本質上這裡用這個概念並無不妥,只是未必每個人都能抽象到這一層而已。而AoT是把這個過程改在編譯期完成,瀏覽器下載到的直接就是編譯好的渲染代碼,下載完就會立刻開始創建DOM節點。

    因此,從這種差異就可以推斷出:

    1. 首屏渲染速度會明顯快於JiT,文件越大越多的項目差異越明顯。

    2. 它不需要自帶compiler模塊,因此節省了空間。

    3. 它編譯生成的代碼里有大量諸如createElement之類的函數調用,因此普通的js minify工具是無法壓縮它們的,生成的代碼量可能比JiT還要大。不過gzip可以完美的處理這種情況,因此如果要部署AoT的成果,請務必開啟gzip。

    不過,要注意提高首屏載入速度並不是AoT的首要目標,使用服務端渲染技術(Universal)才是首選方案。它的提速效果基本上是不受JiT和AoT影響的。

    文件大小方面,我做過一個具有內核、表單、HTTP這幾個模塊的試驗性應用,aot+tree shaking+minify+gzip後大約是80k左右,只用到內核的是50k,況且以後再載入時還能緩存。所以,除非在特殊場景,否則以現在的網速通常是不用在乎這點大小的 —— 想想你的一張banner有多大。

    【內部如何實現的,貌似現在只支持 ts ?】

    你放棄了ts的同時就已經放棄了很多,與放棄的那些好處相比,不支持AoT真的只是小事一樁…… 如果希望提高載入速度,那麼建議使用服務端渲染。

    【其他陣營的 React 和 Vue 是否有希望實現該功能 ?】

    對它們還不夠了解,不發表評論。不過要注意你需要的未必是功能,而是要解決某些特定的需求,不要把實現方式誤以為需求。只要能滿足你的需求,無論用什麼方式來實現都沒問題。技術選型時,永遠記住要從需求出發。


    其實術語不重要,主要是理解代碼跑的方式。

    以下個人理解,因為術語太牛逼,有點萌萌然~

    1. 平台不支持,我要轉換一下,然後我提前編譯好唄,你(我們這裡通常說瀏覽器吧)給我跑結果就是了,這裡的結果可以理解為 UIView

    2. 要不我先不編譯好吧,反正編譯過程的定義也是一堆代碼,我直接都打包給你,讓你跑的時候再說吧。這裡就會有一個打包的編譯器。

    3. 然後全部都編譯器來做?會不會太複雜了哇,那麼我們提前編譯一下吧。

    4. 各種編譯方式都有優缺點,那麼我們來看看其他的吧,比如 vue 文檔開始的獨立構建和運行時構建,這是為了什麼需求而做的呢?獨立構建通俗來講就是我用編譯器來唄,運行時構建,給我個render,我直接跑吧。

    這裡的需求可以猜想,就是為了 SSR ,為什麼呢?就是因為你瀏覽器識別dom,我服務端可不認,我只認數據和後端代碼邏輯,好吧,我就把基本的分離一下,都用代碼來實現,然後發編譯好的字元串給你,你就可以解析了。你會發現這裡如果不分離一些瀏覽器相關的東西就不行。然後這樣的話首次入口就在服務端了,因為會給出基礎以及初始的數據什麼的。

    其實在沒用到的時候可以完全不用關心,只需要看一下項目需求,然後選擇合適的技術,這些對你的影響基本你都感受不到,比如人家給你說,aot 吧,好,嗯,有攻略,資深的估計很快就搞好了用唄,但是對你的90%需求業務開發來說,真正痛點不是這個,對於軟體生命周期來說,最開始這個部分解決了之後就不用再繼續了。

    最後安利一下,可以看出 React 最開始走的路線就是對的。


    我不是來回答問題,我只來是來吐槽A家的AOT/JIT術語的。我覺得真搞編譯器的人看到他們那所謂AOT/JIT肯定都一口老血噴出來(如上面的同學說的,你又沒有Profile-Based Optimization,扯什麼JIT)。而作為前端來說,總覺得他們是在拿前端不太熟悉的看似高大上的術語來忽悠人。

    說起來,還是React陣營的人有創造力,Virtual DOM、Flux、SSR……雖然其實都不是什麼新東西,但至少都是自創名詞。

    我黑完了,請摺疊我吧。


    推薦閱讀:
  • ionic雙向數據綁定失效是為什麼?
    行內元素中一個 display:none; overflow:hidden;導致的問題?
    哪些網頁適合前端新手去模仿?實踐過程中需要注意什麼?
    axure既然能畫高保真模型,為什麼不技術上優化代碼,直接用於前端?

    TAG:前端開發 | JavaScript | Angular? |