在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或者输入绑定。

Angular中Constructor 和 ngOnInit 的本质区别的更多相关文章

  1. 【转】Angular之constructor和ngOnInit差异及适用场景

    原文:http://liuwenzhuang.github.io/2016/03/04/angular2-constructor-versus-ngOnInit.html -------------- ...

  2. Angular之constructor和ngOnInit差异及适用场景(转)

    原始地址:https://blog.csdn.net/u010730126/article/details/64486997 Angular中根据适用场景定义了很多生命周期函数,其本质上是事件的响应函 ...

  3. Angular之constructor和ngOnInit差异及适用场景

    constructor会在类生成实例时调用,Angular无法控制constructor,constructor中应该只进行依赖注入而不是进行真正的业务操作 ngOnInit属于Angular生命周期 ...

  4. 支付宝接入文档中TRADE_SUCCESS和TRADE_FINISHED的本质区别

    之前一直不知道这2种状态到底有什么不同.支付宝中担保交易和即时到账交易对其的描述为: TRADE_SUCCESS  交易成功(或支付成功) TRADE_FINISHED    交易完成 一头雾水... ...

  5. VS中的Debug 和 Release 编译方式的本质区别

    VS中的Debug 和 Release 编译方式的本质区别 Debug 通常称为调试版本,它包含调试信息,并且不作任何优化,便于程序员调试程序.Release 称为发布版本,它往往是进行了各种优化,使 ...

  6. angular 2+ 变化检测系列三(Zone.js在Angular中的应用)

    在系列一中,我们提到Zone.js,Zones是一种执行上下文,它允许我们设置钩子函数在我们的异步任务的开始位置和结束位置,Angular正是利用了这一特性从而实现了变更检测. Zones.js非常适 ...

  7. angular中的服务

    angular中的服务 angular中的服务相当于一个状态管理,可以将数据放在服务里面进行获取以及编辑. 服务的安装命令: ng g service count 安装好后,会在服务的ts文件中引入一 ...

  8. Angular 个人深究(一)【Angular中的Typescript 装饰器】

    Angular 个人深究[Angular中的Typescript 装饰器] 最近进入一个新的前端项目,为了能够更好地了解Angular框架,想到要研究底层代码. 注:本人前端小白一枚,文章旨在记录自己 ...

  9. Angular中的$q的形象解释及深入用法

    作者:寸志链接:https://zhuanlan.zhihu.com/p/19622332来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 早上,老爸说:“儿子,天气如何 ...

随机推荐

  1. python cookbook第三版学习笔记十三:类和对象(三)描述器

    __get__以及__set__:假设T是一个类,t是他的实例,d是它的一个描述器属性.读取属性的时候T.d返回的是d.__get__(None,T),t.d返回的是d.__get__(t,T).说法 ...

  2. 机器学习实战 logistic回归 python代码

    # -*- coding: utf-8 -*- """ Created on Sun Aug 06 15:57:18 2017 @author: mdz "&q ...

  3. Apache安装编译遇到APR的问题

    http://mirrors.tuna.tsinghua.edu.cn/apache//httpd/httpd-2.4.27.tar.bz2Apache下载链接 在解压Apache源码包进入目录运行 ...

  4. hdu--1421--dp--搬寝室

    注意:dp[i][j]初始化的时候不能小于0xffffff明显大于2^15但是吧再小就WA了 #include<iostream> #include<algorithm> #i ...

  5. 58. Length of Last Word【leetcode】

    Given a string s consists of upper/lower-case alphabets and empty space characters ' ', return the l ...

  6. JS高级编程读书笔记

    导读:由于书的内容较多,内容划分也非常详尽,所以会分好几篇来写. 此页面仅作为跳转,权当个目录来用. 我会分块进行整理,大致如下: 第一章 简介 讲述javascript的历史,不打算整理,同学们大概 ...

  7. .NET并行处理和并发1-Threads and Theading

    线程是操作系统分配处理器时间的基本单元,并且进程中可以有多个线程同时执行代码. 每个线程都维护异常处理程序.调度优先级和一组系统用于在调度该线程前保存线程上下文的结构. 线程上下文包括为使线程在线程的 ...

  8. Java静态绑定与动态绑定

    程序绑定的概念: 绑定指的是一个方法的调用与方法所在的类(方法主体)关联起来.对java来说,绑定分为静态绑定和动态绑定:或者叫做前期绑定和后期绑定. 静态绑定: 在程序执行前方法已经被绑定(也就是说 ...

  9. Node.js之错误处理与断言处理

    Node.js之错误处理与断言处理 1. 使用 domain 模块处理错误 try..catch 多用于捕捉同步方法中的抛出错误,但不能用try..catch捕捉异步方法中抛出de错误 如: 1 va ...

  10. STM32F030系列实现仿位带操作

    1.闲言 最近开发的时候,用到了STM32F030F4P6型号的单片机,它只有20个引脚,价格非常便宜,但是功能齐全:定时器.外部中断.串口.IIC.SPI.DMA和WWDG等等,应用尽有,非常适合用 ...