关于Vue的生命周期函数,目前网上有许多介绍文章,但也都只是分析了表象。这篇文档,将结合Vue源码分析,为何会有这样的表象。javascript
Vue中的生命周期函数也能够称之为生命周期钩子(hook)函数,在特定的时期,调用特定的函数。html
随着项目需求的不断扩大,生命周期函数被普遍使用在数据初始化、回收、改变Loading状态、发起异步请求等各个方面。vue
而Vue实例的生命周期函数有beforeCreate、created、beforeMount、mounted、beforeUpdate、updated、beforeDestpry、destroyed,8个。java
本文假设读者使用过Vue.js,但对相应的开发经验不作要求。若是你对Vue很感兴趣,殊不知如何下手,建议你先阅读官方文本node
如下是这篇文章所需的资源,欢迎下载。git
咱们在该页面来研究,Vue的生命周期函数究竟会在什么时候调用,又会有什么不一样的特性。强烈建议你直接将项目仓库克隆至本地,并在真机环境中,跑一跑。Vue.js已经添加在版本库中,所以你不须要添加任何依赖,直接在浏览器中打开lifeCycle.html
便可。github
$ git clone https://github.com/AmberAAA/vue-guide
定义template
与data
,使其在能够在屏幕实时渲染出来。数组
{ //... template: ` <div> <p v-html="info"></p> </div> `, data () { return { info: '' } } //... }
以beforeCreate
为例,定义所有的证实周期函数。浏览器
beforeCreate() { console.group('------beforeCreate------'); console.log('beforeCreate called') console.log(this) console.log(this.$data) console.log(this.$el) this.info += 'beforeCreate called <br>' console.groupEnd(); }
在浏览器中打开lifeCycle.html
,点击挂载组件后,屏幕依次输出created called
、beforeMount called
、mounted called
。表现出,在挂载组件后,info
被created
, beforeMount
, mounted
赋值,并渲染至屏幕上。可是本应在最开始就执行的beforeCreate
却并无给info
赋值。
卸载组件时,由于程序运行太快,为了方便观察,特地为beforeDestroy
和beforeDestroy
函数在最后添加了断点。发现点击卸载组价后,Vue在v-if=true
时会直接从文档模型中卸载组件(此时组件已经不在document)。异步
控制台输出的内容很是具备表明性。
咱们能够发现,控制台按照建立、挂载、更新、销毁依次打印出对应的钩子函数。展开来看
在触发beforeCreate
函数时,vue实例还还没有初始化$data
,所以也就没法给$data
赋值,也就很好的解释了为何在屏幕上,没有渲染出beforeCreate called
。同时,由于还没有被挂载,也就没法获取到$el
。
在触发created
函数时,其实就代表,该组件已经被建立了。所以给info
赋值后,待组件挂载后,视图也会渲染出created called
。
在触发beforeMount
函数时,其实就代表,该组件即将被挂载。此时组建表现出的特性与created
保持一致。
在触发mounted
函数时,其实就代表,该组件已经被挂载。所以给info
赋值后,待组件挂载后,视图也会渲染出mounted called
,而且控制台能够获取到$el
触发beforeUpdate
与updated
,分别表示视图更新先后,更新前$data
领先视图,更新后,保持一致。在这两个回调函数中,更改data时注意避免循环回调。
触发beforeDestroy
与destroyed
,表示实例在销毁先后,改变$data
,会触发一次updated
,因在同一个函数中(下文会介绍)回调,故捏合在一块儿说明。
名称 | 触发阶段 | $data |
$el |
---|---|---|---|
beforeCreate | 组件建立前 | ✖ | ✖ |
created | 组件建立后 | ✔ | ✖ |
beforeMount | 组件挂载前 | ✔ | ✖ |
mounted | 组件挂载后 | ✔ | ✔ |
beforeUpdate | 组件更新前 | ✔ | ✔ |
updated | 组件更新后 | ✔ | ✔ |
beforeDestroy | 组件建立前 | ✔ | ✔ |
destroyed | 组件建立前 | ✔ | ✔ |
Vue生命周期函数在源码文件/src/core/instance/init.js
中定义,并在/src/core/instance/init.js
、src/core/instance/lifecycle.js
、/src/core/observer/scheduler.js
三个文件中调用了全部的生命周期函数
当在特定的使其,须要调用生命周期钩子时,源码只需调用callHook函数,并传入两个参数,第一个为vue实例,第二个为钩子名称。以下
export function callHook (vm: Component, hook: string) { // #7573 disable dep collection when invoking lifecycle hooks pushTarget() const handlers = vm.$options[hook] if (handlers) { //? 这里为何是数组?在什么状况下,数组的索引会大于1? for (let i = 0, j = handlers.length; i < j; i++) { try { handlers[i].call(vm) } catch (e) { handleError(e, vm, `${hook} hook`) } } } if (vm._hasHookEvent) { vm.$emit('hook:' + hook) } popTarget() }
callHook在打包时,并无暴露在全局做用域。但咱们能够根据Vue实例来手动调用生命周期函数。试着在挂在组件后在控制台输入vue.$children[0].$options['beforeCreate'][0].call(vue.$children[0])
,能够发现组件的beforeCreate钩子已经被触发了。而且表示出了与本意相驳的特性。此时由于组件已经初始化,而且已经挂载,因此成功在控制台打印出$el
与$data
,并在修改info
后成功触发了beforeUpdate
与beforeUpdate
beforeCreate
与created
Vue会在/src/core/instance/init.js
中经过initMixin
函数对Vue实例进行进一步初始化操做。
export function initMixin (Vue: Class<Component>) { Vue.prototype._init = function (options?: Object) { /* .... */ vm._self = vm initLifecycle(vm) initEvents(vm) initRender(vm) callHook(vm, 'beforeCreate') initInjections(vm) // resolve injections before data/props initState(vm) // 定义$data initProvide(vm) // resolve provide after data/props callHook(vm, 'created') /* ... */ } }
能够看出在执行callHook(vm, 'beforeCreate')
以前,Vue还还没有初始化data,这也就解释了,为何在控制台beforeCreate获取到的$data
为undefined
,而callHook(vm, 'created')
却能够,以及屏幕上为何没有打印出beforeCreate called
。
beforeMount
与mounted
Vue在/src/core/instance/lifecycle.js
中定义了mountComponent
函数,并在该函数内,调用了beforeMount
与mounted
。
export function mountComponent ( vm: Component, el: ?Element, hydrating?: boolean ): Component { vm.$el = el // 组件挂载时 `el` 为`undefined` callHook(vm, 'beforeMount') // 因此获取到的`$el`为`undefined` /* ... */ // we set this to vm._watcher inside the watcher's constructor // since the watcher's initial patch may call $forceUpdate (e.g. inside child // component's mounted hook), which relies on vm._watcher being already defined //! 挖个新坑 下节分享渲染watch。 通过渲染后,便可获取`$el` new Watcher(vm, updateComponent, noop, null, true /* isRenderWatcher */) hydrating = false // manually mounted instance, call mounted on self // mounted is called for render-created child components in its inserted hook if (vm.$vnode == null) { vm._isMounted = true // 由于已经渲染,`$el`此时已经能够成功获取 callHook(vm, 'mounted') } return vm }
beforeUpdate
与updated
beforeUpdate
与updated
涉及到watcher,所以将会在之后的章节进行详解。
beforeDestroy
与destroyed
Vue将卸载组件的方法直接定义在原型链上,所以能够经过直接调用vm.$destroy()
方法来卸载组件。
Vue.prototype.$destroy = function () { const vm: Component = this if (vm._isBeingDestroyed) { return } // 吊起`beforeDestroy`钩子函数 callHook(vm, 'beforeDestroy') vm._isBeingDestroyed = true // remove self from parent const parent = vm.$parent if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) { remove(parent.$children, vm) } // teardown watchers if (vm._watcher) { vm._watcher.teardown() } let 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 // 调起`destroyed`钩子函数 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 } } }
vue.$children[0].$options['beforeCreate']
为何是一个数组?updated called
?new Watcher(vm, updateComponent, noop, null, true /* isRenderWatcher */)
这行代码以后发生了什么?beforeUpdate
背后实现原理。