一、生命周期概念
首先,每一个vue实例都有一个完整的生命周期,主要分为五个阶段:创建、初始化、渲染、运行、销毁。 也就是从创建(new Vue())、初始化数据(init)、编译模板(render function)、挂载Dom($el)、渲染($mount)→更新(update)→渲染(Virtual Dom re-Render)、销毁(destory)等一系列过程,我们称这是Vue的生命周期。
通俗说就是Vue实例从创建到销毁的过程,就是生命周期。
二、vue生命周期图解
三、具体详解
1.创建阶段
new Vue() 创建
表示开始创建一个vue实例对象。
2.初始化阶段
创建之后这个时候就会调用this._init()方法,
letuid =0Vue.prototype._init =function(options){constvm= thisvm._uid = uid++ // 组件唯一标识vm.$options= mergeOptions( // 合并optionsresolveConstructorOptions(vm.constructor),options|| {},vm) ... initLifecycle(vm) // 开始一系列的初始化 initEvents(vm) initRender(vm) callHook(vm,'beforeCreate') initInjections(vm) initState(vm) initProvide(vm) callHook(vm,'created') ...if(vm.$options.el) {vm.$mount(vm.$options.el) }}
2.1 合并options配置
1. 初始化new Vue
在执行new Vue构造函数时,参数就是一个对象,也就是用户自定义配置;会将它和vue之前定义的原型方法、全局API 属性、还有全局的Vue.mixin内的参数,将这些都合并成一个新的options, 最后赋值给一个新的属性$options。
2. 子组件初始化
如果是子组件初始化, 除了合并以上那些外,还会将父组件的参数进行合并, 如有父组件定义在子组件上的event, props 等等。
经过合并之后就可以通过this.options.data访问到用户定义的data函数,this.options.name访问到用户定义的组件名称,这个合并后的属性很重要,会被经常使用到。
2.2 初始化事件和生命周期
刚初始化一个空的vue实例对象,这个时候只有一些生命周期函数和默认的事件,其他的东西都还为创建。
1.initLifecycle(vm)
确认组件(也是vue实例)的父子关系以及初始化vm.$parent, $children实例属性。后面可进行事件的触和数据的传递。
2.initEvents(vm)
主要作用是将父组件在使用v-on 或者 @ 注册自定义事件添加到子组件的事件中心中。
原生事件
在执行initEvent之前模板编译阶段,会判断遇到的是html标签名 还是组件名。
如果是html标签名,就会在转为真是dom之后使用addEventListener注册浏览器原生事件。 当然这个步骤是在挂载dom的最后阶段,这歌时候是初始化阶段,主要处理自定义事件。
自定义事件
在经过合并options 阶段后, 子组件就可以从 vm.$options._parentListeners读取到父组件传过来的自定义事件。 通过updateListeners方法, 它的作用是借助之前定义的$on, $emit方法, 完成父子组件事件的通信。
3. initRender(vm)
主要作用是挂载,将render函数转化为vnode的方法。
exportfunctioninitRender(vm){ vm._vnode = null ...
vm._c =(a,b,c,d) =>createElement(vm,a,b,c,d,false)//转化编译器的vm.$createElement =(a,b,c,d) =>createElement(vm,a,b,c,d,true)// 转化手写的 ...}
主要作用是挂载vm._c 和 vm.createElement两个方法,它门只是最后一个参数不同,这两个方法都可以将render函数转为vnode,从命名大家应该可以看出区别,vm.c转换的是通过编译器将template转换而来的render函数;而vm.createElement 两个方法,它门只是最后一个参数不同,这两个方法都可以将render函数转为vnode, 从命名大家应该可以看出区别,vm._c 转换的是通过编译器将template 转换而来的 render 函数; 而 vm.createElement两个方法,它门只是最后一个参数不同,这两个方法都可以将render函数转为vnode,从命名大家应该可以看出区别,vm.c转换的是通过编译器将template转换而来的render函数;而vm.createElement 转换的是用户自定义的 render 函数, 比如:
new Vue({data: { msg:'hello Vue!'}, render(h) {// 这里的 h 就是vm.$createElementreturnh('span',this.msg); }}).$mount('#app');
4.callHook(vm, ''beforeCreate)
执行beforeCreate里的内容;
灵魂一问:请问可以在beforeCreate 钩子内通过 this 访问到data中定义的变量么,为什么? 请问这个钩子可以做什么?
是不可以访问的。 因为在vue 初始化阶段, 这个时候data 中的变量还没有挂载到this 上, 这个时候访问值会是 undefined。 beforeCreate 这个钩子在平时业务开发中比较少用。 像插件内部install 方法通过Vue.use 方法安装时一般会选在beforeCreate 这个钩子内执行, vue-router 和 vuex 就是这么干的。
2.3 初始化注入和校验
1. initInjections(vm) 主要作用是初始化inject,可以访问到对应的依赖。
inject 和 provide 是vue@2.2 版本添加的一对需要一起使用的API, 它允许父级组件向它之后的子孙组件提供依赖,让子孙组件无论嵌套多深都可以访问到。
provide : 提供一个对象或是返回一个对象的函数。 inject : 是一个字符串数组或对象。
举一个栗子:
app.vue 根组件export default { provide() { return { app: this } }, data() { return { info:'hello world!'} }}child.vue 子孙组件export default { inject: ['app'], methods: { handleClick() { this.app.info='hello vue 变 world 啦!'} }}
这样可以简单实现vuex的功能,但是要注意组件之间层级关系,可能会造成混乱的现象。
2. initState(vm)
初始化会被使用到的状态:props、methods、data、computed、watch。
initProps(vm,propOptions)
主要作用是检测子组件接收的值是否符合规则,以及对应的值可以用this直接访问。
initMethods(vm, methods):
主要作用是将methods内的方法挂载到this下。命名规范的检查。不能与props的key重名,以及不能以_、$开头。
initData(vm)
主要作用是初始化data,挂载到this下。
3. initProvide(vm)
主要作用是初始化provide为子组件提供依赖。
4. callHook(vm, 'created')
作用是执行用户定义的created钩子函数,有mixin混入的也一并执行。
灵魂一问:请问methods 内的方法可以使用箭头函数么? 会产生怎样的结果?
答案: 是不可以使用箭头函数的, 因为箭头函数的this是在定义时就绑定的。 在vue的内部, methods 内每个方法的上下文是当前的vm组件实例, methods[key].bind(vm)。 如果使用箭头函数,函数的上下文就变成了父级的上下文, 也就是undefined了, 结果就是通过undefined 访问任何变量都会报错。
在created中,data和methods都已经初始化好了。最早也只能在created里调用methods方法和操作data数据。
另外注意
如果要在created阶段中进行dom操作,就要将操作都放在 Vue.nextTick() 的回调函数中,因为created() 钩子函数执行的时候 DOM 其实并未进行任何渲染,而此时进行 DOM 操作无异于徒劳,所以此处一定要将 DOM 操作的 js 代码放进 Vue.nextTick() 的回调函数中。 一般我们最好不要在created里操作dom。
Vue.nextTick( [callback, context] ):在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
3.渲染阶段
3.1 编译模板
从created到beforeMount的过程中,首先会判断vue实例中有没有el选项,如果有的话则进行下面的编译,但是如果没有el选项,则停止生命周期,直到vue实例上调用vm.$mount(el)。 如果有el,再判断是否有template参数,如果有,则把其当作模板编译成render函数,如果没有,则把外部的html作为模板编译。template中的模板优先级高于outer HTML模板。 在vue对象中还有一个render函数,它是以createElement作为参数,然后做渲染操作,而且我们可以直接嵌入JSX. 综合排名优先级:render函数选项 > template选项 > outer HTML
3.2 beforeMount
此函数进行时,模板已经在内存中编译好了,但是尚未挂载到页面中去,此时,页面还是旧的。
3.3 挂载
这一步,将内存中编译好的模板,替换到浏览器的页面中去。
3.4 mounted
这个节点表示整个vue实例已经初始化完毕了,此时组件已经脱离了创建-渲染阶段,进入到运行阶段。
4.运行阶段
运行阶段的生命周期函数式beforeCreate和created,这个两个事件会根据数据data的改变,有选择性的触发0次或多次。
4.1 beforeUpdate
这个生命周期里,页面中显示的数据还是旧的,但是咱们data的数据是最新的,页面尚未和最新的数据保持同步。
4.2 虚拟Dom重新编译渲染
这一步执行的是:现根据data中最新的数据,在内存中,重新渲染出一份最新的内存DOM树, 当最新的内存DOM树 被更新之后, 会把最新的内存DOM树,重新渲染到真是的页面中去,这个时候就完成了数据从data(Mode层)=> View(视图层)的更新。
4.3 Updated
updated执行时,页面和data已经保持同步了,都是最新的。
5.销毁阶段
5.1 beforeDestory
当执行beforeDestory钩子函数的时候,Vue实例就已经从运行阶段进入到了销毁阶段:
这个时候,实例上所有的data和methods,以及过滤器、指令等等 都还处于 可用状态,此时还没有真正执行销毁的过程。
5.2 destroyed
当执行到destroyed钩子函数的时候,组件已经被完全销毁了,此时Vue实例所有的data、methods、过滤器、指令... 都用不了。
总结:以上就是我所总结的对Vue生命周期的理解,也是参考了各方面的解释,结合自己的理解,对Vue生命周期进行梳理。当梳理了之后,你会发现你对Vue会又更好的理解。
更多内容请访问我的博客网站:http://www.jscwwd.com/article/5e65f4e249a13d1a89caf57c##toc412