Angular中Constructor 和 ngOnInit 的本质区别

在Medium看到一篇Angular的文章,深入对比了 Constructor 和 ngOnInit 的不同,受益匪浅,于是搬过来让更多的前端小伙伴看到,翻译不得当之处还请斧正。
本文出处:The essential difference between Constructor and ngOnInit in Angular
难以译出原意的术语都在圆括号里给出原词了。下面开始正文!


在stackoverflow上被问得很多的一个关于Angular的问题就是Difference between Constructor and ngOnInit,阅读量超过10万。我回答了这个问题,但还是决定在这篇文章展开讲一下。这上面的很多答案和网上的一些文章都把关注点放在这两者用法的区别上,在这里我想给出一个深入到组件初始化进程的更全面的答案。

有关JS和TS语言的区别

让我们从语言本身最明显的区别开始。在一个类中,ngOnInit只是一个在结构上与其他方法不一样的方法。Angular团队只是这样命名它,但它也可以有其他任何名字:

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

在一个组件类中引不引入这个方法完全取决于你。编译过程中Angular编译器会检查组件是否引入了这个方法,然后用合适的标记去标记这个类:

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

在变更检测过程中,在组件实例内,这个标记会被用来决定是否调用ngOnInit方法:

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

相反,constructor是个不同的东西。在一个TypeScript类实例化过程中,无论写不写constructor,它都会被调用。这就是为什么一个TypeScript类的constructor会被转译成一个 JavaScript constructor function

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

转译成

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

在创建类实例时这个函数会被用new操作符调用:

    const componentInstance = new MyComponent(

所以,如果你在类中省略constructor,这个类会被转译成一个空函数:

    function MyComponent() {}

这就是为什么我说在类中无论写不写constructor,它都会被调用。

有关组件初始化进程的区别

从组件初始化阶段的角度看,两者存在巨大差别。Angular bootstrap process(译注:这个比较微妙,不知道怎么翻译,暂且译作引导进程吧)包含两个主要阶段:

  • 构造组件树
  • 运行变更检测

而且,组件的constructor会在Angular构造组件树的时候被调用。所有生命周期钩子包括ngOnInit会被作为接下来的变更检测阶段的一部分被调用。通常,组件初始化逻辑需要一些依赖注入提供商(DI providers),或者可用的输入绑定,或者已渲染的DOM,在Angular 引导进程的不同阶段,这些都是可用的。

Angular构造组件树的时候,根模块注入器就已经配置好,所以你可以注入任何全局依赖。而且,当Angular实例化一个子组件类的时候,父组件的注入器也已经配置好,所以你可以注入父组件中定义的提供商(providers),包括父组件自身。组件的constructor是在注入器的上下文中被调用的唯一方法,所以如果你需要任何依赖,constructor是唯一获得这些依赖的地方。@Input的通信机制(communication mechanism)是作为接下来的变更检测阶段的一部分处理的,所以输入绑定在constructor中不可用。

Angular开始变更检测的时候组件树已经构造完毕,在组件树中的所有组件的constructor都会被调用。而且这时候所有组件的模板节点(template nodes)也已经添加到DOM中,这时,初始化组件的所有数据都已齐全——依赖注入提供商、DOM和输入绑定( DI providers, DOM and input bindings)。

你可以在Everything you need to know about change detection in Angular学习关于变更检测的知识,在The mechanics of property bindings update in Angular学习Angular进程如何输入。

我们用个简单例子证明这些阶段。假设有如下模板:

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

Angular开始引导应用程序。如上所述,它首先创建每个组件的类,因此调用MyAppComponent的constructor。当执行组件的constructor时,Angular resolves(译注:这个词不知道怎么翻译比较准确,就直接用原文了) 所有注入到MyAppComponentconstructor的依赖,并把他们作为参数提供出来(译注:这里翻译的比较拗口,原文是When executing a component constructor Angular resolves all dependencies that are injected into MyAppComponent constructor and provides them as parameters)。并且它会创建一个作为my-app宿主元素的DOM节点,然后它继续创建child-comp的宿主元素,并且调用ChildComponent的constructor。在这个阶段,Angular不关心i输入绑定和任何生命周期钩子。所以当这个过程完成的时候,Angular就创建出了如下组件视图树:

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

直到那时Angular才会运行变更检测、更新my-app的绑定、调用MyAppComponent实例的ngOnInit。然后它继续更新child-comp的绑定和调用ChildComponent类的ngOnInit

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

用法上的区别

现在从用法的角度看看两者的区别。

Constructor

在Angular中,一个类的constructor主要用来注入依赖。Angular调用constructor injection pattern这里已经解释得很详细,更深入的见解你可以读Miško Hevery的文章Constructor Injection vs. Setter Injection

然而,constructor的使用不仅限于依赖注入(DI)。举个例子,@angular/router模块的router-outlet指令在路由生态系统内用constructor来注册自己和自己的位置(viewContainerRef)。我在 Here is how to get ViewContainerRef before @ViewChild query is evaluated把它描述了一遍。

惯例就是,在constructor中,逻辑应尽可能少。

NgOnInit

前文我们看到,当Angular调用ngOnInit的时候,它已经通过constructor完成创建组件DOM、注入所有必要的依赖,也已经完成输入绑定。这时所有必需信息已经齐全,这些信息使得ngOnInit成为执行初始化逻辑的好地方。

习惯上用ngOnInit来执行初始化逻辑,即使这些逻辑不依赖于依赖注入(DI)、DOM或者输入绑定。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 227,663评论 6 531
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 98,125评论 3 414
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 175,506评论 0 373
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 62,614评论 1 307
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 71,402评论 6 404
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 54,934评论 1 321
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 43,021评论 3 440
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 42,168评论 0 287
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 48,690评论 1 333
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 40,596评论 3 354
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 42,784评论 1 369
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 38,288评论 5 357
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 44,027评论 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 34,404评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 35,662评论 1 280
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 51,398评论 3 390
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 47,743评论 2 370

推荐阅读更多精彩内容

  • Angular 2架构总览 - 简书//www.greatytc.com/p/aeb11061b82c A...
    葡萄喃喃呓语阅读 1,493评论 2 13
  • 1.组件 组件负责控制屏幕上的一小块区域,我们称之为视图。例如,下列视图都是由组件控制的: 带有导航链接的应用根组...
    不去解释阅读 557评论 0 1
  • 版本:Angular 5.0.0-alpha AngularDart(本文档中我们通常简称 Angular ) 是...
    soojade阅读 841评论 0 4
  • 明媚的日光伴你前行 乌云密布时也仍有逐梦的精灵 淡淡的月光随你忧伤 一丝丝的难过却总是来自无名 分离时伤怀与祝福浑...
    南国梓桐君阅读 492评论 0 1
  • 我最珍爱的莹姐: 不知不觉间,我们已经相处一年了。距你离开也有几个月了,虽然我们现在已经不在同一个城市,不...
    timomimo阅读 237评论 0 0