Angular中Constructor 和 ngOnInit 的本质区别
2017-09-11 16:29
549 查看
在stackoverflow上被问得很多的一个关于Angular的问题就是Difference between Constructor and ngOnInit,阅读量超过10万。我回答了这个问题,但还是决定在这篇文章展开讲一下。这上面的很多答案和网上的一些文章都把关注点放在这两者用法的区别上,在这里我想给出一个深入到组件初始化进程的更全面的答案。
在一个组件类中引不引入这个方法完全取决于你。编译过程中Angular编译器会检查组件是否引入了这个方法,然后用合适的标记去标记这个类:
在变化检测过程中,在组件实例内,这个标记会被用来决定是否调用
相反,constructor是个不同的东西。在一个TypeScript类实例化过程中,无论写不写constructor,它都会被调用。这就是为什么一个TypeScript类的constructor会被转译成一个 JavaScript constructor function:
转译成
在创建类实例时这个函数会被用
所以,如果你在类中省略constructor,这个类会被转译成一个空函数:
这就是为什么我说在类中无论写不写constructor,它都会被调用。
构造组件树
运行变化检测
而且,组件的constructor会在Angular构造组件树的时候被调用。所有生命周期钩子包括
Angular构造组件树的时候,根模块注入器就已经配置好,所以你可以注入任何全局依赖。而且,当Angular实例化一个子组件类的时候,父组件的注入器也已经配置好,所以你可以注入父组件中定义的提供商(providers),包括父组件自身。组件的constructor是在注入器的上下文中被调用的唯一方法,所以如果你需要任何依赖,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进程如何输入。
我们用个简单例子证明这些阶段。假设有如下模板:
Angular开始引导应用程序。如上所述,它首先创建每个组件的类,因此调用
直到那时Angular才会运行变化检测、更新
你可以在Here is why you will not find components inside Angular了解更多知识。
然而,constructor的使用不仅限于依赖注入(DI)。举个例子,
惯例就是,在constructor中,逻辑应尽可能少。
习惯上用
原文:The essential difference between Constructor and ngOnInit in Angular
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或者输入绑定。
原文:The essential difference between Constructor and ngOnInit in Angular
相关文章推荐
- Angular中Constructor 和 ngOnInit 的本质区别
- Angular 的 ngOnInit 和 Constructor 的区别!
- Angular 的 ngOnInit 和 Constructor 的区别
- Angular2中constructor与ngOninit的区别
- ngOnInit与constructor的区别
- Angular2 constructor VS ngOnInit
- 详解Angular之constructor和ngOnInit差异及适用场景
- Angular之constructor和ngOnInit差异及适用场景
- Angular之constructor和ngOnInit差异及适用场景
- 详解Angular 中 ngOnInit 和 constructor 使用场景
- Angular之constructor和ngOnInit差异及适用场景
- ExtJS4中initComponent和constructor的区别
- 【Angular】ngOnInit()方法介绍
- combo 添加listeners,使用 initComponent、constructor 的区别
- ExtJS4中initComponent和constructor的区别
- 【Angular4】constructor & ngOnInit
- What is the difference between Constructor and ngOnInit?
- [Ext JS 4] Extjs 之 initComponent 和 constructor的区别