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 更新,说明实例成功被销毁了 ~~
vue 的生命周期的思想贯穿在组件开发的始终,通过熟悉其生命周期调用不同的钩子函数,我们可以准确地控制数据流和其对 DOM 的影响;vue 生命周期的思想是 Vnode 和 MVVM 的生动体现和继承,以后我将继续学习相关的知识,在一个更高的高度学习 vue,我会在学习中继续和大家不断分享学习的点滴感悟,更希望获得大家对我的文章积极提出问题和建议,我们共同讨论,共同进步 ~~