一、概述
vue 的生命周期包含以下几个次要阶段:
(1)初始化阶段
初始化阶段包含新建 Vue 实例、初始化事件、初始化生命周期、初始化注入等;
初始化阶段对应着 beforeCreate() 和 created() 事件之前的阶段;
(2)模板编译阶段
模板编译阶段对应着 beforeMount() 之前的阶段;
(3)挂载阶段
挂载阶段将实例挂载到指定的 DOM 下来,将模板渲染到实在的 DOM 上;
挂载阶段对应着 mounted() 之前的阶段;
(4)销毁阶段
销毁阶段将实例从父组件中删除。
二、初始化阶段
Ⅰ. 初始化阶段,首先要做的是新建一个 Vue 实例,Vue 类中所做的非常简略,调用了 this._init(options) 函数,就实现了 Vue 的定义。
(1)this._init() 函数从何而来?
Vue 源码中 core/instance/init.js 中,定义了一个函数 initMixin(),在此函数中,给 Vue 的原型绑定了_init 办法:
Vue.prototype._init = function (options?: Object) {//do something}
(2)_init 办法中做了什么?
次要做了以下几件事:
- 合并 options 属性,并将合并后的值赋值给 $options 挂载到 vue 实例上;
- 初始化生命周期、事件、渲染、注入、data 等;
- 在适合的工夫调用触发生命周期的钩子函数
- 在实现了所有的初始化工作后,会判断是否传入了 el,进行模板编译和挂载
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
如果没有 el 选项,则须要用户手动挂载 vm.$mount()。
(3)合并属性?
mergeOptions 办法将 vue 实例的构造函数上的 options 与传入的 options 做合并;
有如下几个重点须要理解:
- 构造函数的 options 包含 components、filters、directives 几个空对象,附加一些内置组件;
- 对于不同类型的 options,有着不一样的合并策略,通过 mergeField() 办法,对他们进行合并:
function mergeField (key) {
const strat = strats[key] || defaultStrat
options[key] = strat(parent[key], child[key], vm, key)
}
通过不同的策略,把父子 options 中雷同的属性进行合并;
- 对于生命周期钩子函数的合并函数,后果是将雷同生命周期钩子函数合并成数组,这样做是思考到 vue 的 mixins 注入时,有可能在同样的生命周期中注入不同的函数,而这些函数均须要触发,所以用数组保留。
(4)callHook 是如何执行的?
- 首先获取属性合并中取得的对应钩子函数数组;
- 遍历该数组,将数组中的所有函数都执行一遍。
Ⅱ. 初始化阶段,在实现了实例的新建之后,会调用一些初始化函数。
(1)initLifecycle()
这个办法内容非常简略,次要是将一些属性设置默认值并挂在给实例,次要有 $parent、$root 属性;
(2)initEvents(): 初始化实例的事件零碎 (v-on,@…)
- initEvents(): 1、实例下面绑定_events 为空对象(这里的_events 是父组件绑定在以后组件上的事件)2、判断父组件上是否有注册事件(同样是父组件绑定在子组件的事件),若有,则将父组件上的事件注册到子组件上——调用 updateComponentListeners();
- updateComponentListeners(): 调用了 updateListeners() 函数,将 listeners 和 add/remove 两个办法传入;(target 先设为 vm,调用函数后设为 undefined 是什么起因?保留对 vm 实例的援用)
- 在理解 updateListeners 函数之前,首先看看 add 和 remove 别离做了什么?
* add() 调用实例的 $on 办法,将通过 $on 传入的事件传入到_events 对象中 {eventName:eventFunction()……}
* remove() 调用实例的 $off 办法,通过代码能够看出 $off 有三种用法,第一种:不传参数,将所有事件清空;第二种:只传第一个参数,将_events 对象下的该属性事件数组清空;第三种:传入两个参数,革除某个特定事件;
- updateListeners() 次要做的就是将新传入的事件与原来的事件比照,有新增则调用 add,原有事件列表中存在而现有不存在的则调用 remove 卸载事件;(createFnInvoker 局部读不懂:因为一个事件可能会对应多个回调函数,所以这里做了数组的判断,多个回调函数就顺次调用。留神最初的赋值逻辑,invoker.fns = fns,每一次执行 invoker 函数都是从 invoker.fns 里取执行的回调函数,回到 updateListeners,当咱们第二次执行该函数的时候,判断如果 cur !== old,那么只须要更改 old.fns = cur 把之前绑定的 involer.fns 赋值为新的回调函数即可,并且 通过 on[name] = old 保留援用关系,这样就保障了事件回调只增加一次,之后仅仅去批改它的回调函数的援用。)
- 模板解析阶段对事件的解析
模板解析时,除了解析开始标签,也解析标签中的属性等,processAttrs 办法,解析不同的属性,当遇到 v -on 修饰符时,就会调用 addHandler() 办法。
* addHandler() 中次要做了什么?
首先,对于有不同修饰符的事件(capture,once 等),为事件名称 name 增加对应的符号,做出非凡解决,以便前面生成 render 函数时辨认不同的事件;
接着依据 modifier.native 判断事件是一个浏览器原生事件还是自定义事件,别离对应 el.nativeEvents 和 el.events;
最初将对应的事件函数的字符串保留到对应的 events[name] 中去。比方:
<child @select="selectHandler" @click.native="clickHandler"></child>
造成:
el.events = {
select: {
value: ‘selectHandler’
}}
el.nativeEvents = {
click: {
value: ‘clickHandler’
}}
* 模板编译的代码生成阶段,根据上述产生的 events 和 nativeEvents, 在 genData 函数中生成所须要的 data 数据,最开始的模板中标签上注册的事件最终会被解析成用于创立元素型 VNode 的_c(tagName,data,children) 函数中 data 数据中的两个对象,自定义事件对象 on,浏览器原生事件 nativeOn。
* 上述生成的渲染函数,在 new Vue 阶段创立对应的组件 const listeners = data.on;data.on = data.nativeOn,这就阐明,initEvents 初始化时,初始化的是父组件注册在子组件上的(应用 on 或者 @)自定义事件,浏览器的原生事件则是在父组件内解决。
三、小结
本篇次要建立了初始化阶段所做的一些事件,包含 init 当中是如何合并属性的,如何调用生命周期钩子函数,以及初始化阶段调用的初始化函数,次要介绍了生命周期的初始化以及事件的初始化,其余初始化函数见《Vue 源码之生命周期——初始化阶段②》。