Angular中Constructor 和 ngOnInit 的本質(zhì)區(qū)別

在Medium看到一篇Angular的文章,深入對比了 Constructor 和 ngOnInit 的不同,受益匪淺,于是搬過來讓更多的前端小伙伴看到,翻譯不得當之處還請斧正。
本文出處:The essential difference between Constructor and ngOnInit in Angular
難以譯出原意的術(shù)語都在圓括號里給出原詞了。下面開始正文!


在stackoverflow上被問得很多的一個關(guān)于Angular的問題就是Difference between Constructor and ngOnInit,閱讀量超過10萬。我回答了這個問題,但還是決定在這篇文章展開講一下。這上面的很多答案和網(wǎng)上的一些文章都把關(guān)注點放在這兩者用法的區(qū)別上,在這里我想給出一個深入到組件初始化進程的更全面的答案。

有關(guān)JS和TS語言的區(qū)別

讓我們從語言本身最明顯的區(qū)別開始。在一個類中,ngOnInit只是一個在結(jié)構(gòu)上與其他方法不一樣的方法。Angular團隊只是這樣命名它,但它也可以有其他任何名字:

    class MyComponent {
        ngOnInit() { }
        otherNameForNgOnInit() { }
    }

在一個組件類中引不引入這個方法完全取決于你。編譯過程中Angular編譯器會檢查組件是否引入了這個方法,然后用合適的標記去標記這個類:

    export const enum NodeFlags {
    ...
    OnInit = 1 << 16,

在變更檢測過程中,在組件實例內(nèi),這個標記會被用來決定是否調(diào)用ngOnInit方法:

    if (def.flags & NodeFlags.OnInit && ...) {
        componentClassInstance.ngOnInit();
    }

相反,constructor是個不同的東西。在一個TypeScript類實例化過程中,無論寫不寫constructor,它都會被調(diào)用。這就是為什么一個TypeScript類的constructor會被轉(zhuǎn)譯成一個 JavaScript constructor function

    class MyComponent {
        constructor() {
            console.log('Hello');
        }
    }

轉(zhuǎn)譯成

    function MyComponent() {
        console.log('Hello');
    }

在創(chuàng)建類實例時這個函數(shù)會被用new操作符調(diào)用:

    const componentInstance = new MyComponent(

所以,如果你在類中省略constructor,這個類會被轉(zhuǎn)譯成一個空函數(shù):

    function MyComponent() {}

這就是為什么我說在類中無論寫不寫constructor,它都會被調(diào)用。

有關(guān)組件初始化進程的區(qū)別

從組件初始化階段的角度看,兩者存在巨大差別。Angular bootstrap process(譯注:這個比較微妙,不知道怎么翻譯,暫且譯作引導進程吧)包含兩個主要階段:

  • 構(gòu)造組件樹
  • 運行變更檢測

而且,組件的constructor會在Angular構(gòu)造組件樹的時候被調(diào)用。所有生命周期鉤子包括ngOnInit會被作為接下來的變更檢測階段的一部分被調(diào)用。通常,組件初始化邏輯需要一些依賴注入提供商(DI providers),或者可用的輸入綁定,或者已渲染的DOM,在Angular 引導進程的不同階段,這些都是可用的。

Angular構(gòu)造組件樹的時候,根模塊注入器就已經(jīng)配置好,所以你可以注入任何全局依賴。而且,當Angular實例化一個子組件類的時候,父組件的注入器也已經(jīng)配置好,所以你可以注入父組件中定義的提供商(providers),包括父組件自身。組件的constructor是在注入器的上下文中被調(diào)用的唯一方法,所以如果你需要任何依賴,constructor是唯一獲得這些依賴的地方。@Input的通信機制(communication mechanism)是作為接下來的變更檢測階段的一部分處理的,所以輸入綁定在constructor中不可用。

Angular開始變更檢測的時候組件樹已經(jīng)構(gòu)造完畢,在組件樹中的所有組件的constructor都會被調(diào)用。而且這時候所有組件的模板節(jié)點(template nodes)也已經(jīng)添加到DOM中,這時,初始化組件的所有數(shù)據(jù)都已齊全——依賴注入提供商、DOM和輸入綁定(?DI providers, DOM and input bindings)。

你可以在Everything you need to know about change detection in Angular學習關(guān)于變更檢測的知識,在The mechanics of property bindings update in Angular學習Angular進程如何輸入。

我們用個簡單例子證明這些階段。假設有如下模板:

    <my-app>
    <child-comp [i]='prop'>

Angular開始引導應用程序。如上所述,它首先創(chuàng)建每個組件的類,因此調(diào)用MyAppComponent的constructor。當執(zhí)行組件的constructor時,Angular resolves(譯注:這個詞不知道怎么翻譯比較準確,就直接用原文了) 所有注入到MyAppComponentconstructor的依賴,并把他們作為參數(shù)提供出來(譯注:這里翻譯的比較拗口,原文是When executing a component constructor Angular resolves all dependencies that are injected into MyAppComponent constructor and provides them as parameters)。并且它會創(chuàng)建一個作為my-app宿主元素的DOM節(jié)點,然后它繼續(xù)創(chuàng)建child-comp的宿主元素,并且調(diào)用ChildComponent的constructor。在這個階段,Angular不關(guān)心i輸入綁定和任何生命周期鉤子。所以當這個過程完成的時候,Angular就創(chuàng)建出了如下組件視圖樹:

    MyAppView
        - MyApp component instance
        - my-app host element data
            ChildComponentView
                - ChildComponent component instance
                - child-comp host element data

直到那時Angular才會運行變更檢測、更新my-app的綁定、調(diào)用MyAppComponent實例的ngOnInit。然后它繼續(xù)更新child-comp的綁定和調(diào)用ChildComponent類的ngOnInit。

你可以在Here is why you will not find components inside Angular了解更多知識。

用法上的區(qū)別

現(xiàn)在從用法的角度看看兩者的區(qū)別。

Constructor

在Angular中,一個類的constructor主要用來注入依賴。Angular調(diào)用constructor injection pattern這里已經(jīng)解釋得很詳細,更深入的見解你可以讀Mi?ko Hevery的文章Constructor Injection vs. Setter Injection。

然而,constructor的使用不僅限于依賴注入(DI)。舉個例子,@angular/router模塊的router-outlet指令在路由生態(tài)系統(tǒng)內(nèi)用constructor來注冊自己和自己的位置(viewContainerRef)。我在 Here is how to get ViewContainerRef before @ViewChild query is evaluated把它描述了一遍。

慣例就是,在constructor中,邏輯應盡可能少。

NgOnInit

前文我們看到,當Angular調(diào)用ngOnInit的時候,它已經(jīng)通過constructor完成創(chuàng)建組件DOM、注入所有必要的依賴,也已經(jīng)完成輸入綁定。這時所有必需信息已經(jīng)齊全,這些信息使得ngOnInit成為執(zhí)行初始化邏輯的好地方。

習慣上用ngOnInit來執(zhí)行初始化邏輯,即使這些邏輯不依賴于依賴注入(DI)、DOM或者輸入綁定。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

  • Angular 2架構(gòu)總覽 - 簡書http://www.itdecent.cn/p/aeb11061b82c A...
    葡萄喃喃囈語閱讀 1,548評論 2 13
  • 1.組件 組件負責控制屏幕上的一小塊區(qū)域,我們稱之為視圖。例如,下列視圖都是由組件控制的: 帶有導航鏈接的應用根組...
    不去解釋閱讀 620評論 0 1
  • 版本:Angular 5.0.0-alpha AngularDart(本文檔中我們通常簡稱 Angular ) 是...
    soojade閱讀 911評論 0 4
  • 明媚的日光伴你前行 烏云密布時也仍有逐夢的精靈 淡淡的月光隨你憂傷 一絲絲的難過卻總是來自無名 分離時傷懷與祝福渾...
    南國梓桐君閱讀 538評論 0 1
  • 我最珍愛的瑩姐: 不知不覺間,我們已經(jīng)相處一年了。距你離開也有幾個月了,雖然我們現(xiàn)在已經(jīng)不在同一個城市,不...
    timomimo閱讀 262評論 0 0

友情鏈接更多精彩內(nèi)容