Vue 生命周期
vue所有功能的实现都是围绕其生命周期进行的,在生命周期的不同阶段调用对应的钩子函数可以实现组件数据管理和DOM渲染两大重要功能。学习实例的生命周期,能帮助我们理解vue实例的运行机制,更好地利用钩子函数完成我们的业务代码。
1、即将创建:对应的钩子函数为beforeCreate。此阶段为实例初始化之后,此时的数据观察和事件机制都未形成。
<template> <div class="router-page-wrap" style="background: #fc595d;"> this is usercenter <input type="text" v-model="message" ref="input"> {{message}} </div> </template> <script> var common = require('common'); module.exports = { data : function() { return { message : 'not update' } }, beforeCreate : function () { console.log('this is beforeCreate :', this.message, this.$refs.input); } } </script>
得到的结果是:
此时,实例中的data和el都是undefined。因此,在beforeCreate钩子函数中不能使用data中的数据,也不能获得DOM节点。
2、创建完毕:对应的钩子函数为created。在这个阶段vue实例已经创建,我们在同样打印一下data和DOM元素。
上面代码的基础上我们添加created钩子函数:
得到的结果是什么呢??
此时,我们能够读取到数据data的值,但是DOM还没有生成,所以和DOM相关的属性还不存在,自然也就不能获取DOM元素。在Vue2的源码中也是这样描述的:
首先,运行new Vue()
的时候,会进入代码src/core/instance/index.js
的Vue构造方法中,并执行this._init()
方法。在_init
中,会对各个功能进行初始化,并执行beforeCreate
和created
两个生命周期方法。核心代码如下:
initLifecycle(vm) initEvents(vm) callHook(vm, 'beforeCreate') initState(vm) callHook(vm, 'created') initRender(vm)
可以看到,到当执行钩子函数created时,complier还没有将template解析成render方法,DOM自然不能获取
3、即将挂载:对应的钩子函数是beforemount。在上个阶段我们知道DOM还没生成,相关属性还是undefined,那么此阶段为即将挂载,将发生什么呢?
增加一下代码:
打印一下,我们得到:
结果好像和上一个阶段并没有什么区别......弄的我好失落....那到底beforeMount这个阶段的意义是什么呢?难道这是vue给我设的一个坑??等等~,先别跳!!让我们问问源(zu)码(zong)(︶.̮︶✽)
if (vm.$options.el) { vm.$mount(vm.$options.el) }
在vue2的源码中在讲述mount这一重要步骤时进行了一个关键的判断,就是判断vue的挂载节点是否存在!!那么,挂载节点时如何存在了的呢?这一定就是在beforeMount这一阶段完成的!让我们马上上代码实验一下刚才的断言。在这里换一种创建vue的方式。
现在我们知道vue实例和DOM其实是分开的两个概念,然而,vue实例的必须和DOM相关联并改变DOM才能完成它的使命。这就需要将Vue挂载到DOM上。因此,vue提供了一个el参数来确定挂载的DOM节点。当我们根据vue构造函数new出一个Vue时,只要设定了这个el元素,那么这个新的Vue一切操作将只对这个el及其子元素有效。
这时我们将得到打印的结果如下:
可以看到,这里数据name的值还没被渲染到DOM中,然而通过id已经能够取得dom的根节点了,这个节点即挂载节点。
走到这里,beforeMount的意义已经逐渐清晰了。在这一阶段,我们虽然依然得不到具体的DOM元素,但vue挂载的根节点已经创建,下面vue对DOM的操作将围绕这个el继续进行。在这个阶段vue成功获得了DOM王国国王--根元素el的信任,之后vue通过掌控“数据驱动”这台国家机器获得了对DOM王国的绝对统治权,“挟天子以令诸侯”,vue的所有命令都将在对DOM每个元素的控制中得到精确执行。
beforeMount这个阶段是过渡性的,一般一个项目只能用到一两次,但它却是意义非凡的!!在它之后vue将执行mount函数,带有vue属性的DOM及vue数据将全部被呈现,$refs将可以在DOM中使用并获取元素。最重要的是,之后我们将迎来辉煌的mounted阶段。beforeMount这个过程在Vue2的源码中是这样描述的:
可以看出,$mount函数的操作都基于beforeMount阶段获取的根元素el。($mount函数将根据el,template,render属性调用render方法,即我们平时说的compile过程。compile会把我们写的vue语言编译成render(JSX),这一步一般都是构建工具帮我们完成的啦,啦啦啦~)。
根据源码,_render方法执行完之后,会执行_mount方法,在这个过程中会首先new出形成一个watcher对象,会运行传入的一个_render方法主要就是运行之前compile的render方法,形成vNode节点,也就是大名鼎鼎的虚拟DOM。拿到vNode后,传入vm._update()
方法,进行DOM更新。(watcher和下面将讲到的update涉及到虚拟DOM原理,会在以后的文章中结合vue详细说明,感觉自己挖了一个坑~~)
4、渲染完毕:对应的钩子函数是mounted。mounted是平时我们使用最多的函数了,一般我们的异步请求都写在这里。在这个阶段,数据和DOM都已被渲染出来。
继续增加代码:
打印一下:
“千呼万唤始出来”,我们终于得到了想要的结果!!
不过,这并不是最终的结果。(◐ˍ◑),因为更新即将到来~
5、即将更新渲染:对应的钩子函数是beforeUpdate。vue遵循数据驱动DOM的原则,当我们修改vue实例的data时,vue会自动帮我们更新视图。那么当我们调用beforeMount函数时,会发生什么呢?
为了说明数据变化对DOM的影响,我首先更新代码,增加了一个method方法。到目前为止,代码如下:
<template> <div class="router-page-wrap" style="background: #fc595d;"> this is usercenter <input type="text" v-model="message" ref="input" id="input"> <em ref="em">{{message}}</em> <button v-touch:tap="messageChange">changeMessage</button> </div> </template> <script> var common = require('common'); module.exports = { data : function() { return { message : '我的博客园' } }, methods : { messageChange : function () { this.message = 'emma的博客园' } }, beforeCreate : function () { console.log('this is beforeCreate :', this.message, this.$refs.input); }, created : function () { console.log('this is created :') console.log('message :' , this.message); console.log('DOM element:', this.$refs.input); }, beforeMount : function () { console.log('this is beforeMount :') console.log('message :' , this.message); console.log('DOM element:', this.$refs.input); }, mounted : function () { console.log('this is mounted :') console.log('message :' , this.message); console.log('DOM element:', this.$refs.input); }, beforeUpdate : function () { console.log('=即将更新渲染='); let name = this.$refs.em.innerHTML; console.log('name:'+name); } } </script>
运行之后:
可以看到,beforeUpdate函数在数据更新后并没立即更新数据,但是DOM中的数据已经改变,这是Vue双向数据绑定的作用,以后也会讲到,哇塞,又一个坑~~
6、更新渲染后:对应的钩子函数是updated。为了不使看到同-函数在不能阶段的效果,我注释掉beforeUpdate函数,添加update函数并绑定了刚才的click事件。
我们得到预料中的结果:
现在DOM终于和我们更改过的内容同步了!
7、销毁之前:对应的钩子函数是beforeDestroy。到上一步vue已经成功的通过数据驱动DOM更新,当我们不在需要vue操纵DOM时,就需要销毁Vue,也就是清除vue实例与DOM的关联,调用destroy方法可以销毁当前组件。在销毁前,会触发beforeDestroy钩子函数。
8、销毁之后:对应的钩子函数是destroyed。在销毁后,会触发destroyed钩子函数。
我们通过调用destroy函数观察vue实例销毁前后vue和DOM的变化。增加代码如下:
调用destroy前,我们改变name,在视图随之改变,beforeDestroy获取的DOM和我们更新后的结果一致。
调用destroy后,我们改变name,视图不会改变,destroyed也不会获取到DOM信息了。
销毁之前,修改name的值,可以成功修改视图显示的内容为更新后的内容,调用实例的$destroy( )方法之后,vue实例与DOM的关系解绑,vue数据的任何变化都不会使DOM更新,说明实例成功被销毁了~~