Vue 生命周期详解
Vue 生命周期流程
最开始,用户使用 new Vue() 创建根 Vue 实例,或者 Vue 实例化子组件都会调用_init
方法(我们将这两种实例都称为vm
):
function Vue(options) { //Vue 构造函数
...
this._init(options)
}
...
const Sub = function (options) { // 定义子组件构造函数
this._init(options)
}
vm
实例化时会调用原型方法this._init
方法进行初始化:
Vue.prototype._init = function(options) {
vm.$options = mergeOptions( // 合并options
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
...
initLifecycle(vm) // 开始一系列的初始化
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate') //执行 beforeCreate 钩子
initInjections(vm)
initState(vm)
initProvide(vm)
callHook(vm, 'created') //执行 created 钩子
...
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
beforeCreate
首先,将用户提供的options
对象,父组件定义在子组件上的event
、props
(子组件实例化时),vm
原型方法,和Vue
构造函数内置的选项合并成一个新的options
对象,赋值给vm.$options
。
接下来,执行 3 个初始化方法:
- initLifecycle(vm): 主要作用是确认组件的父子关系和初始化某些实例属性。找到父组件实例赋值给
vm.$parent
,将自己push
给父组件的$children
; - initEvents(vm): 主要作用是将父组件使用
v-on
或@
注册的自定义事件添加到子组件的私有属性vm._events
中; - initRender(vm): 主要作用是初始化用来将
render
函数转为vnode
的两个方法vm._c
和vm.$createElement
。用户自定义的render
函数的参数h
就是vm.$createElement
方法,它可以返回vnode
。等以上操作全部完成,就会执行beforeCreate
钩子函数,此时用户可以在函数中通过this
访问到vm.$parent
和vm.$createElement
等有限的属性和方法。
created
接下来会继续执行 3 个初始化方法:
- initInjections(vm): 初始化
inject
,使得vm
可以访问到对应的依赖; - initState(vm): 初始化会被使用到的状态,状态包括
props
,methods
,data
,computed
,watch
五个选项。调用相应的init
方法,使用vm.$options
中提供的选项对这些状态进行初始化,其中initData
方法会调用observe(data, true)
,实现对data
中属性的监听,实际上是使用Object.defineProperty
方法定义属性的getter
和setter
方法; - initProvide(vm):初始化
provide
,使得vm
可以为子组件提供依赖。
这 3 个初始化方法先初始化inject
,然后初始化props/data
状态,最后初始化provide
,这样做的目的是可以在props/data
中使用inject
内所注入的内容。
等以上操作全部完成,就会执行created
钩子函数,此时用户可以在函数中通过this
访问到vm
中的props
,methods
,data
,computed
,watch
和inject
等大部分属性和方法。
beforeMount
如果用户在创建根 Vue 实例时提供了el
选项,那么在实例化时会直接调用vm.$mount
方法开始挂载:
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
如果未提供el
选项,则需要用户手动调用vm.$mount
方法开挂载。vm.$mount
方法:
运行时版本:
Vue.prototype.$mount = function(el) { // 最初的定义
return mountComponent(this, query(el));
}
完整版:
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function(el) { // 拓展编译后的
var options = this.$options;
if(!options.render) {
if(options.template) {
... //一些判断
} else if (el) { //传入的 el 选项不为空
options.template = getOuterHTML(el);
}
if (options.template) {
options.render = compileToFunctions(template, ...).render //将 template 编译成 render 函数
}
}
...
return mount.call(this, query(el)) //即 Vue.prototype.$mount.call(this, query(el))
}
在完整版的vm.$mount
方法中,如果用户未提供render
函数,就会将template
或者el.outerHTML
编译成render
函数。
然后会执行mountComponent
函数:
export function mountComponent(vm, el) {
vm.$el = el
...
callHook(vm, 'beforeMount')
...
const updateComponent = function () {
vm._update(vm._render()) // 调用 render 函数生成 vnode,并挂载到 HTML中
}
...
if (vm.$vnode == null) {
vm._isMounted = true;
callHook(vm, 'mounted');
}
}
如果用户提供了el
选项,则会获取用于挂载的真实节点,将此节点赋值给vm.$el
属性。
等以上操作全部完成,就会执行beforeMount
钩子函数,如果用户提供了el
选项,此时在函数中可以通过this
访问到vm.$el
属性,此时它的值为el
提供的真实节点。
mounted
在mountComponent
方法中,会执行vm._render
方法获取vnode
:
Vue.prototype._render = function() {
const vm = this
const { render } = vm.$options
const vnode = render.call(vm, vm.$createElement)
return vnode
}
在vm._render
方法中会调用vm.$options.render
函数,传入实参vm.$createElement
(对应声明render
函数时的形参h
),得到返回结果vnode
。
在执行一个如下的render
函数的过程中:
render(h) {
return h(
"div", //标签名
[ //子节点数组
[
[h("h1", "title h1")], //子节点也是通过 h 函数生成 vnode 的
[h('h2', "title h2")]
],
[
h(obj, [ //子组件传入 obj 而不是标签名
h("p", "paragraph")
])
]
]
);
}
执行render
函数的过程就是递归调用h
函数的过程,h
函数会根据子组件的options
选项对象生成一个vnode
,以便之后将它转化为真实节点。
不管是根节点挂载时首次渲染,还是在数据改变后更新页面,都会调用updateComponent
方法。_render
方法返回的vnode
是一个树形结构的JavaScript
对象,接下来在updateComponent
中会调用_update
将这棵虚拟DOM
树转化为真实的DOM
树:
const updateComponent = function () {
vm._update(vm._render()) // 调用 render 函数生成 vnode,并挂载到 HTML中
}
而vm._update
方法会将vm.__patch__
方法返回的真实Dom
节点赋值给vm.$el
:
Vue.prototype._update = function(vnode) {
...
if (!prevVnode) {
// 首次渲染
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */);
} else {
// 更新
vm.$el = vm.__patch__(prevVnode, vnode);
}
...
}
往vm.__patch__
方法传入的参数vm.$el
是之前在mountComponent
方法中赋值的真实Dom
元素,是挂载对象。vm.__patch__
会生成并插入真实Dom
:
Vue.prototype.__patch__ = createPatchFunction({ nodeOps, modules })
nodeOps
是一些操作原生Dom
的方法的集合,modules
是class/attrs/style
等属性创建、更新、销毁时相应钩子方法的集合,而createPatchFunction
函数返回了一个patch
函数:
export function createPatchFunction(backend) {
...
const { modules, nodeOps } = backend
return function patch (oldVnode, vnode) { // 接收新旧 vnode 的 `patch`函数
...
//isDef 函数 : (v) => v !== undefined && v !== null
const isRealElement = isDef(oldVnode.nodeType) // 是否是真实 Dom
if(isRealElement) { // 首次渲染传入的 vm.$el 是真实 Dom
oldVnode = emptyNodeAt(oldVnode) // 将 vm.$el 转为 VNode 格式
}
...
}
}
调用emptyNodeAt
函数将传入的vm.$el
转化为VNode
格式。VNode
是Vue
定义的虚拟节点类,vnode
是VNode
类的实例对象。
function emptyNodeAt(elm) {
return new VNode(
nodeOps.tagName(elm).toLowerCase(), // 对应tag属性
{}, // 对应data
[], // 对应children
undefined, //对应text
elm // 真实dom赋值给了elm属性
)
}
包装后的:
{
tag: 'div',
elm: '<div id="app"></div>' // 真实dom
}
然后继续创建真实Dom
:
export function createPatchFunction(backend) {
...
return function patch (oldVnode, vnode) {
const insertedVnodeQueue = [] //用于缓存 insertedVnode
...
const oldElm = oldVnode.elm //包装后的真实 Dom <div id='app'></div>
const parentElm = nodeOps.parentNode(oldElm) // 首次父节点为<body></body>
createElm( // 创建真实 Dom
vnode, // 传入的 vnode
insertedVnodeQueue, // 空数组
parentElm, // <body></body>
nodeOps.nextSibling(oldElm) // 下一个兄弟节点
)
return vnode.elm // 返回真实 Dom ,之后在 _update 中覆盖 vm.$el
}
}
createElm
方法根据节点类型生成真实Dom
节点,并插入parentElm
中。而createElm
方法在创建元素节点的过程中,会调用createChildren
方法创建子节点,而createChildren
方法又会调用createElm
方法生成子节点的真实Dom
节点,形成了createElm
方法的递归调用:
function createElm(vnode, insertedVnodeQueue, parentElm, ...) {
...
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) { //此时可忽略这一步
return
}
...
// 如果要创建的节点是元素节点
vnode.elm = nodeOps.createElement(tag) // 先创建一个空元素用于挂载子节点
createChildren(vnode, children, insertedVnodeQueue) // 调用 `createChildren` 方法创建子节点
insert(parentElm, vnode.elm, refElm) // 将真实元素 vnode.elm 插入父节点中
...
}
递归创建子节点,插入父节点,最终生成vm
的真实Dom
节点vnode.elm
。
等以上操作全部完成,就会执行mounted
钩子函数,此时在函数中可以通过this
访问到vm.$el
属性,此时它为虚拟vnode
转化而来的真实Dom
。
activated
如果我们研究的实例vm
是一个组件实例,而且它被<keep-alive>
组件包裹,那么它将额外具有两个钩子函数activated
和deactivated
。我们假设vm
是根 Vue 实例root
的一个后代组件。
在root
挂载时,会在它的patch
方法中调用createElm
方法生成真实Dom
节点并插入<body>
(root
的父节点)。
如果有子节点,会先调用createChildren
方法,在createChildren
中通过createElm
方法生成每个子节点的真实Dom
节点,再将子Dom
节点插入root
的Dom
节点中:
function createChildren(vnode, children, insertedVnodeQueue) {
if (Array.isArray(children)) {
for (var i = 0; i < children.length; ++i) {
createElm(children[i], insertedVnodeQueue, vnode.elm, null, true, children, i); // 实参 vnode.elm 传给 parentElm 形参
}
}
...
}
所以再次回到上面的createElm
方法,此时它被用于创建子节点,如果子节点为组件,在createElm
中会调用createComponent
方法对子组件进行初始化,生成子组件实例(假设就是vm
),初始化子组件调用的是 init
钩子(vnode
有 4 个 management hook
:init
, prepatch
, insert
和 destroy
,在 render
函数生成 vnode
时会加载到 vnode.data.hook
上)。
function createComponent(vnode, insertedVnodeQueue, parentElm, refElm) {
var i = vnode.data;
if (isDef(i)) {
var isReactivated = isDef(vnode.componentInstance) && i.keepAlive;
if (isDef(i = i.hook) && isDef(i = i.init)) {
i(vnode, false /* hydrating */ ); // 暂停执行 createComponent,开始调用 vnode.data.hook.init 钩子进行初始化
}
if (isDef(vnode.componentInstance)) {
// 等 init 钩子执行完再执行,此时 vm 已执行完 $mount 方法,所以在 initComponent 方法中将 vnode push 到 insertedVnodeQueue 中
initComponent(vnode, insertedVnodeQueue);
insert(parentElm, vnode.elm, refElm); // 将真实元素 vnode.elm 插入父节点中
if (isTrue(isReactivated)) {
reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm);
}
return true
}
}
}
在 init
钩子中调用 Sub
构造函数实例化子组件:
init: function init(vnode, hydrating) {
...
//调用 `Sub`构造函数实例化子组件,执行 `beforeCreate` 和 `created` 钩子
var child = vnode.componentInstance = createComponentInstanceForVnode(vnode, activeInstance);
//调用 vm.$mount,执行 `beforeMount` 钩子,然后执行 updateComponent,重复上面的流程
child.$mount(hydrating ? vnode.elm : undefined, hydrating);
},
初始化完成后,会调用子组件实例vm
的$mount
方法进行挂载,执行patch
方法,在vm
的patch
方法中又会调用createElm
方法生成真实Dom
,这时子组件实例会难以避免地再次执行createComponent
方法:
function createElm(vnode, insertedVnodeQueue, parentElm, refElm) {
...
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) { // 如果子节点为组件,调用 createComponent 方法对子组件进行初始化;之后在子组件的 `patch` 方法中又会调用 `createElm` 方法
return
}
//继续创建真实节点
...
vnode.elm = nodeOps.createElement(tag)
createChildren(vnode, children, insertedVnodeQueue); //从这里开始暂停,在 createChildren 中 createElm 子节点
insert(parentElm, vnode.elm, refElm); //将真实元素 vnode.elm 插入父节点中
...
}
这个时候createComponent
不会执行初始化操作,而是直接返回undefined
,这样就可以继续创建真实节点,如果后代还有组件,又是一个循环……
所以,父子节点的创建、挂载钩子执行顺序为:
父beforeCreate
=> 父created
=> 父beforeMount
=> 子beforeCreate
=> 子created
=> 子beforeMount
回到mounted
生命周期的createPatchFunction
方法,在它返回的patch
方法中,私有变量insertedVnodeQueue
用于存储这些插入的后代组件的vnode
:
function patch() {
var insertedVnodeQueue = [];
...
invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch); //调用 insert 钩子
return vnode.elm //真实 Dom 元素
}
...
//`patch`方法就是 _update 中的 __patch__ 方法,
//它返回真实 Dom 元素给根 Vue 实例的 $el,之后会在 mountComponent 中调用根 Vue 实例的 mounted 钩子(具体看前面 mountComponent 和 _update 方法)
root.$el = root.__patch__(...) // _update 中
...
callHook(root, 'mounted'); // mountComponent 中
vm
是root
的后代,vm.$vnode
也在root
实例的patch
方法的insertedVnodeQueue
中。在invokeInsertHook
函数中,会调用这些vnode
的insert
钩子:
function invokeInsertHook(vnode, queue, initial) {
// delay insert hooks for component root nodes, invoke them after the
// element is really inserted
if (isTrue(initial) && isDef(vnode.parent)) {
vnode.parent.data.pendingInsert = queue; //缓存 insertedVnode
} else {
//只有最初的实例的 initial 为 false,所以会延迟到根 Vue 实例 patch 方法的末尾调用所有后代组件的 insert 钩子
for (var i = 0; i < queue.length; ++i) {
queue[i].data.hook.insert(queue[i]); //调用缓存的 insertedVnode 的 insert 钩子
}
}
}
假如当前调用的是vm.$vnode.data.hook.insert
方法:
insert: function insert(vnode) { //传入 vm.$vnode
var context = vnode.context; //父组件实例
var componentInstance = vnode.componentInstance; //vnode 对应的组件实例 vm
if (!componentInstance._isMounted) {
componentInstance._isMounted = true;
callHook(componentInstance, 'mounted'); //调用 vm 的 mounted 钩子函数(所以子组件的 mounted 钩子先于父组件被调用)
}
if (vnode.data.keepAlive) { //true
if (context._isMounted) {
// 父组件更新中
queueActivatedComponent(componentInstance); // 父组件更新时,将 `vm` push 到 Vue 全局变量 activatedChildren 中,等待执行 `activated` 钩子函数
} else {
// 父组件挂载中
activateChildComponent(componentInstance, true /* direct */ ); //调用 `vm` 的 `activated` 钩子函数
}
}
}
由此可知,Vue
会按照root
实例的patch
方法的insertedVnodeQueue
中vnode
的顺序执行mounted
钩子。而在节点树中,越底端的组件越先创建好完好的真实Dom
节点并插入父Dom
节点中,其vnode
也越先被push
到insertedVnodeQueue
中,所以越先执行它的mounted
钩子。
所以,完整的父子节点的创建、挂载钩子执行顺序为:
父beforeCreate
=> 父created
=> 父beforeMount
=> 子beforeCreate
=> 子created
=> 子beforeMount
=> 子mounted
=> 父mounted
在vm.$vnode.data.hook.insert
方法中调用的activateChildComponent
函数会调用vm
及其后代组件的activated
钩子函数:
function activateChildComponent(vm, direct) {
...
if (vm._inactive || vm._inactive === null) {
vm._inactive = false;
for (var i = 0; i < vm.$children.length; i++) {
activateChildComponent(vm.$children[i]); //递归调用子组件的 activated 钩子
}
callHook(vm, 'activated'); //调用 vm 的 activated 钩子
}
}
在vm
首次挂载,调用mounted
钩子函数后,会马上调用activated
钩子函数。
之后vm
的activated
钩子函数会在 keep-alive
组件激活时调用激活时被调用,具体调用时机是在flushSchedulerQueue
函数执行完queue
中所有的watchers
后。
deactivated
vm
的deactivated
钩子函数会在 keep-alive
组件停用时被调用。
在patch
方法的最后,会删除旧节点:
function patch() {
...
removeVnodes(parentElm, [oldVnode], 0, 0); // 在 removeVnodes 中调用 invokeDestroyHook(oldVnode) 删除旧节点
invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);
return vnode.elm
}
如果要删除的vnode
有destroy
钩子,则调用vnode.data.hook.destroy
:
function invokeDestroyHook(vnode) {
var i, j;
var data = vnode.data;
if (isDef(data)) {
if (isDef(i = data.hook) && isDef(i = i.destroy)) {
i(vnode); //调用 vnode.data.hook.destroy 钩子
}
...
}
}
destroy: function destroy(vnode) {
var componentInstance = vnode.componentInstance;
if (!componentInstance._isDestroyed) {
if (!vnode.data.keepAlive) {
componentInstance.$destroy(); // 调用 vm.$destroy()
} else {
deactivateChildComponent(componentInstance, true /* direct */ ); //调用子组件的 'deactivated' 钩子
}
}
}
调用`vm
的deactivated
钩子,递归调用子组件的deactivated
钩子:
function deactivateChildComponent() {
...
for (var i = 0; i < vm.$children.length; i++) {
deactivateChildComponent(vm.$children[i]); //递归调用子组件的 'deactivated' 钩子
}
callHook(vm, 'deactivated'); //调用 'deactivated' 钩子
...
}
这些操作在父组件的patch
方法中执行,父组件patch
后,会调用mounted
或者updated
钩子。
beforeUpdate
每个组件实例都对应一个watcher
实例,它是在mountComponent
方法中,在调用mounted
钩子之前实例化的:
export function mountComponent(vm, el) {
...
callHook(vm, 'beforeMount')
...
const updateComponent = function () {
vm._update(vm._render(), hydrating);
};
new Watcher(vm, updateComponent, noop, {
before: function before () { //在 run 之前执行
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate'); // beforeUpdate 钩子等待执行
}
}
}, true /* isRenderWatcher */);
...
callHook(vm, 'mounted');
}
如果是RenderWatcher
,vm._watcher
会用它赋值:
var Watcher = function Watcher (vm, expOrFn, cb, options, isRenderWatcher) {
this.vm = vm; //关联组件
if (isRenderWatcher) {
vm._watcher = this;
}
vm._watchers.push(this);
...
this.before = options.before;
...
if (typeof expOrFn === 'function') {
this.getter = expOrFn; //即 vm._watcher.getter = updateComponent
}
this.value = this.lazy ? undefined : this.get(); //this.get 中会调用 this.getter,所以 new Watcher 就立即调用 updateComponent
}
watcher
会在组件渲染的过程中把接触
过的数据属性记录为依赖。之后当依赖的值发生改变,触发依赖的setter
方法时,会通知watcher
,从而使它关联的组件(vm
)重新渲染。
一旦侦听到数据变化,Vue
将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个watcher
被多次触发,只会被推入到队列中一次。
等当前事件循环结束,下一次事件循环开始,Vue
会刷新队列并执行已去重的工作。Vue
会尝试使用Promise.then
、MutationObserver
和setImmediate
发布的微任务来执行queue
中的watcher
。
function flushSchedulerQueue () {
queue.sort(function (a, b) { return a.id - b.id; }); //queue 是在 Vue 构造函数中的声明的变量
...
for (index = 0; index < queue.length; index++) {
watcher = queue[index];
if (watcher.before) {
watcher.before(); //执行 beforeUpdate 钩子函数
}
id = watcher.id;
has[id] = null;
watcher.run(); //执行 watcher
...
}
...
// call component updated and activated hooks
callActivatedHooks(activatedChildren.slice()); //执行 activated 钩子函数
callUpdatedHooks(queue.slice()); //执行 updated 钩子函数
}
刷新前根据 id
对 queue
中的 watcher
进行排序。这样可以确保:
- 父
watcher
排在子watcher
前,组件从父级更新到子级。(因为父母总是在子级之前创建,所以id
更小); - 在一个组件中,用户声明的
watchers
总是在render watcher
之前执行,因为user watchers
更先创建; - 如果在父组件的
watcher
运行期间,销毁了某个子组件,可以跳过该子组件的watcher
。
在执行watcher.run
方法之前,会执行watcher.before
方法,从而执行beforeUpdate
钩子函数。
updated
在执行watcher.run
方法时,会调用watcher.getter
方法,而其中某个watcher
(vm._watcher
)关联的就是我们的vm
,它的getter
是可以更新vm
的updateComponent
方法:
Watcher.prototype.run = function run () {
if (this.active) {
var value = this.get(); //调用 watcher.get 方法
...
}
...
}
Watcher.prototype.get = function get () {
...
try {
value = this.getter.call(vm, vm); //调用 watcher.getter 方法
}
...
}
调用updateComponent
方法
updateComponent = function () {
vm._update(vm._render(), hydrating);
};
vm._render
方法会重新执行render
函数生成vnode
,然后vm._update
方法会将vnode
转化为真实Dom
,挂载到HTML
中,并覆盖vm.$el
。
等以上操作全部完成,在flushSchedulerQueue
函数的最后会执行子组件的activated
钩子函数和vm
的updated
钩子函数:
function flushSchedulerQueue () {
...
callActivatedHooks(activatedChildren.slice()); //执行 activated 钩子函数
callUpdatedHooks(queue.slice()); //执行 updated 钩子函数
}
function callUpdatedHooks (queue) {
var i = queue.length;
while (i--) {
var watcher = queue[i];
var vm = watcher.vm;
if (vm._watcher === watcher && vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'updated'); //执行 updated 钩子函数
}
}
}
在updated
钩子函数中通过this.$el
访问到的vm.$el
属性的值为更新后的真实Dom
。beforeUpdate
和updated
钩子函数的执行顺序真好相反,因为在flushSchedulerQueue
函数中是索引递增处理queue
中的watcher
的,所以执行beforeUpdate
钩子函数的顺序和queue
中watcher
的顺序相同;而在callUpdatedHooks
函数中是按索引递减的顺序执行_watcher
关联实例的updated
钩子的,和queue
中_watcher
顺序相反。
再加上父watcher
排在子watcher
前,所以如果父、子组件在同一个事件循环中更新,那么生命周期钩子的执行顺序为:
父beforeUpdate
=> 子beforeUpdate
=> 子updated
=> 父updated
beforeDestroy
调用vm.$destroy
销毁vm
实例:
Vue.prototype.$destroy = function() {
var vm = this;
if (vm._isBeingDestroyed) {
return
}
callHook(vm, 'beforeDestroy');
vm._isBeingDestroyed = true;
// remove self from parent
var parent = vm.$parent;
if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
remove(parent.$children, vm);
}
// teardown watchers
if (vm._watcher) {
vm._watcher.teardown();
}
var i = vm._watchers.length;
while (i--) {
vm._watchers[i].teardown();
}
// remove reference from data ob
// frozen object may not have observer.
if (vm._data.__ob__) {
vm._data.__ob__.vmCount--;
}
// call the last hook...
vm._isDestroyed = true;
// invoke destroy hooks on current rendered tree
vm.__patch__(vm._vnode, null);
// fire destroyed hook
callHook(vm, 'destroyed');
// turn off all instance listeners.
vm.$off();
// remove __vue__ reference
if (vm.$el) {
vm.$el.__vue__ = null;
}
// release circular reference (#6759)
if (vm.$vnode) {
vm.$vnode.parent = null;
}
};
在调用beforeDestroy
钩子前未进行销毁操作,所以在这一步,实例仍然完全可用。
destroyed
vm.$destroy
执行的操作有
- 删除
vm.$parent.$children
中的vm
; - 销毁
vm._watcher
(渲染 watcher),销毁vm._watchers[i]
中的所有watcher
; - 删除数据 observer 中的引用;
- 调用
destroyed
钩子函数; - ...
其中vm.__patch__(vm._vnode, null)
可以销毁所有子实例。
Vue 生命周期流程图
Vue 父子组件生命周期钩子执行顺序
- 父子组件挂载过程:父
beforeCreate
=> 父created
=> 父beforeMount
=> 子beforeCreate
=> 子created
=> 子beforeMount
=> 子mounted
=> 父mounted
- 子组件被
keep-alive
组件包裹(忽视keep-alive
组件),父子组件挂载过程:父beforeCreate
=> 父created
=> 父beforeMount
=> 子beforeCreate
=> 子created
=> 子beforeMount
=> 子mounted
=> 子activated
=> 父mounted
- 只修改父组件或子组件的数据:
beforeUpdate
=>updated
- 在同一事件循环中修改父子组件的数据(无论先后):父
beforeUpdate
=> 子beforeUpdate
=> 子updated
=> 父updated
- 父组件将数据传给子组件的一个 prop,且它们分别是父、子组件的依赖,在修改父组件的数据时:父
beforeUpdate
=> 子beforeUpdate
=> 子updated
=> 父updated
- 子组件的
v-show
指令绑定父组件的数据,在修改父组件的数据时:父beforeUpdate
=> 父updated
,子组件保持mounted
状态不变; - 子组件的
v-show
指令绑定父组件的数据,子组件被keep-alive
组件包裹,在修改父组件的数据时:父beforeUpdate
=> 父updated
,子组件保持activated
状态不变; -
子组件的
v-if
指令绑定父组件的数据,在修改父组件的数据时:- true => false: 父
beforeUpdate
=> 子beforeDestroy
=> 子destroyed
=> 父updated
- false => true: 父
beforeUpdate
=> 子beforeCreate
=> 子created
=> 子beforeMount
=> 子mounted
=> 父updated
- true => false: 父
-
子组件的
v-if
指令绑定父组件的数据,子组件被keep-alive
组件包裹,在修改父组件的数据时:- true => false: 父
beforeUpdate
=> 子deactivated
=> 父updated
- 首次 false => true: 父
beforeUpdate
=> 子beforeCreate
=> 子created
=> 子beforeMount
=> 子mounted
=> 子activated
=> 父updated
- 再次 false => true: 父
beforeUpdate
=> 子activated
=> 父updated
- true => false: 父
- 子组件的
is
属性绑定父组件的数据,父组件将子组件一切换为子组件二:
父beforeUpdate
=> 子二beforeCreate
=> 子二created
=> 子二beforeMount
=> 子二mounted
=> 父beforeUpdate
=> 子一beforeDestroy
=> 子一destroyed
=> 父updated
=> 父updated
-
子组件的
is
属性绑定父组件的数据,子组件被keep-alive
组件包裹,父组件将子组件一切换为子组件二:- 首次:父
beforeUpdate
=> 父beforeUpdate
=> 子二beforeCreate
=> 子二created
=> 子二beforeMount
=> 子一deactivated
=> 子二mounted
=> 子二activated
=> 父updated
=> 父updated
- 再次:父
beforeUpdate
=> 子一deactivated
=> 子二activated
=> 父updated
- 首次:父
动态组件触发两次父beforeUpdate
、updated
的原因:
在第一次事件循环只触发了一次父组件的_watcher
,在调用 render
函数重新生成父组件vnode
的过程中:
var render = function() { //Vue 编译 template 而来的 render 函数
var _vm = this
var _h = _vm.$createElement
var _c = _vm._self._c || _h
return _c(
"div",
{ attrs: { id: "app" } },
[
_c("img", {
attrs: { alt: "Vue logo", src: require("./assets/logo.png") }
}),
_c("p", [_vm._v(_vm._s(_vm.message))]),
//被 keep-alive 组件包裹的情况,在生成 keep-alive 组件的 vnode 时,第二次触发了父组件的`_watcher`
_c("keep-alive", [_c(_vm.now, { tag: "component" })], 1)
//不被 keep-alive 组件包裹的情况,在生成子二组件的`vnode`时,第二次触发了父组件的`_watcher`
_c(_vm.now, { tag: "component" })
],
1
)
}
其实 keep-alive 组件情况更具体一点,也是在生成 keep-alive 组件的孩子,子二组件的vnode
时触发的_watcher
。
然后这个watcher
会被插到queue
中当前wacther
的后面(根据 wacther.id
的大小插入正确的位置):
function queueWatcher(watcher) {
var id = watcher.id;
if (has[id] == null) { //在 flushSchedulerQueue 中,执行 watcher.run 之前,已经令 has[id] = null;
has[id] = true; //所以同 id 的 wacther 可以被插入 queue 中
if (!flushing) {
queue.push(watcher);
} else {
// if already flushing, splice the watcher based on its id
// if already past its id, it will be run next immediately.
var i = queue.length - 1;
while (i > index && queue[i].id > watcher.id) {
i--;
}
queue.splice(i + 1, 0, watcher);
}
}
...
}
等当前watcher.run
执行完,再执行它。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。