之前说完了Vue的基础知识,说了语法、数据代理、数据监听、计算属性、指令、过滤器等等,但是没有涉及到Vue的声明周期,其实之所以把生命周期放在后面讲,是因为,如果最开始讲生命周期,里面涉及到的概念性东西比较多,初学者不容易理解,只能死记硬背。但是在理解了前面的这些基础知识之后,回过头来理解生命周期,就会很方便了。
源起 new Vue()
当我们通过 new Vue(),实例化一个Vue对象之后,就正式开启了 Vue 的旅程,这个对象内部包含了 Vue 对于数据的代理与监听、过滤器与指令的实现、计算属性与方法的编译等等一系列 Vue 提供的Api,以及我们即将详细说明的 生命周期,下面我们通过一个小例子来引出 Vue 的生命周期。
这个例子就是一个渐变的过程,通过 Vue 来实现,现在我们不知道 Vue 的声明周期,然后结合我们之前 使用的方法,我们可以这么来做
欢迎学习 Vue
1、通过 绑定在data 内部的 opacity 属性,来控制 页面层透明度展示
2、通过定时器来循环控制透明度变化,
但是需要注意的是,因为我们此时没有用到生命周期,所以我们的定时器是不能写在 new Vue() 内部的配置项中的。例如下面的写法就是错误的。
const vm = new Vue({
el: '#root',
data() {
return {
opacity: 1
}
},
// 错误写法1: new Vue 内部传递一个对象,单独写一个定时器语法错误
setInterval(() => {}),
// 错误写法2:即使按照语法写了一个对象,但是 Vue 是不认识这个玩意的,打印 vm看一下就知道了
a: setInterval(() => {}),
})
这么一看就会觉得很奇怪啊,我都是操作 data 内部的数据了,为啥还非要在外面 接收了 vm 实例,然后再来通过 vm 实例来操作 data 内部的属性,这不是多此一举么,难道不能直接在 new Vue ({}) 通过配置来实现这个操作么?答案当然是可以的,不然后面的组件化就没法进行了。
在 new Vue 内部配置
之前改变状态是写在外部的,这样不合理,所以我们把它配置在内部,之前讲到过 methods 属性,用来配置 某些方法,这里我们也这样写,这里的vm改成this就可以了,因为是在 new Vue() 内部取值的。
那么问题又来了,定义了方法,那我要怎么使用呢?当然,你可以加一个按钮,通过 @click事件来触发这个方法,但是我需要的是页面在初始化的时候,就调用这个方法,那咋办呢?
按照之前的插值语法或许可以这样?
欢迎学习 Vue
{{change()}}
直接在 页面上通过插值语法来调用这个方法,当解析到这一行代码时,发现插值语法,然后解析语法发现调用了这个方法,执行这个方法之后,发现没有返回值,没有返回值默认返回undefined,而undefined默认不展示在页面上,这样不就可以达到目的了么。确实,这样是可以实现的,但是存在问题,
我们发现,页面变换已经变得很诡异了,并不是我们需要的效果,而且控制台上的输出已经成指数级增长了,这是因为,当页面第一次解析了插值语法之后,然后执行了 change 方法,此时 opacity 属性改变,然后页面重载,再次解析到了插值语法,重新执行change方法,再次改变 opacity属性,循环往复,每解析一次就开启一个定时器,然后就造成了这个效果
mounted :Vue完成模板解析,将初始生成的虚拟DOM转化为真实DOM,挂载到页面上后调用
1、mounted:和 methods 同级,是一个生命周期钩子函数
2、 mounted 内部的代码只执行一次,就是在 初始化 的时候,如果后续状态变化了,那叫更新
3、在 mounted 内部,如果需要使用 new Vue() 中的数据或方法,也是直接使用 this 指向
const vm = new Vue({
el: '#root',
data() {
return {
opacity: 1
}
},
methods: {
change() {
console.log('开启了一个定时器')
setInterval(() => {
this.opacity -= 0.1
if (this.opacity <= 0) this.opacity = 1
}, 100)
}
},
mounted() {
// 也可以直接把 change 函数中的定时器 挪到 mounted 中,这只是我的个人习惯
this.change()
},
})
在这里就引出了 Vue 的生命周期钩子函数中的挂载之后调用的函数 mounted 生命周期钩子函数
Vue2生命周期
这就是 Vue2 官网上的生命周期图,在这里 对每个生命周期以及每个模块都标注了tips,可以根据这些图例来辅助理解Vue的生命周期。
首先需要明白的是,这张图上面并不全部都是生命周期,真正的生命周期钩子函数只有8个,就是用红框单独框住的。其他的是 Vue 实例化过程中的流程。
1、生命周期:beforeCreate(创建前)
1、beforeCreate(创建前)流程:在Vue实例创建之前 ,其实应该是说在数据代理和数据监听之前 先初始化生命周期,将生命周期定义在 Vue 实例中(生命周期函数有多少个,都叫啥,什么时候调用这些生命周期),然后接着 Vue 内部自带的事件修饰符(例如:once)定义,告诉 Vue 解析到了这些事件需要怎么处理。但是此时 Vue 中传入的 data 数据还未被代理,此时 vm 实例还未接收到 data 数据,也就不用说 vm_data 了。
2、beforeCreate(创建前):此时无法在 new Vue() 内部通过this,或者在 外部通过 vm 访问到 data 中的数据,和 methods 中的方法
2、生命周期:created(创建后)
1、created(创建后)流程:在这个流程中,将传入的 data 数据进行数据代理 (挂载到vm实例上的data属性和方法)和 数据监测(对象监听 和 数组监听 _data 中的由get 和 set 转化过的data) 对象监测
2、created(创建后):此时能够在通过 this 或 vm 实例访问到 data 中的数据以及 methods中的方法
3、生命周期:beforeMount(载入前)
1、beforeMount(载入前)流程:这里的流程比较复杂,经过了两次判断,从而走了不同的流程,下面详细解析一下
a、首先问你在 new Vue() 的时候,有没有传入 el 属性,一般我们是会传的,例如上面的例子传的就是 root。如果传了,那就直接走下面的流程,如果没传,我们可以在 vm 实例创建完成之后,通过 vm.$mount(el) 操作来实现相同的效果
new Vue({
// 配置项 & 生命周期
}).$mount('#root')
b、然后问你有没有 传入 templete 配置项,根据是否传入 来判断走哪一个流程 ,
(1)、如果没传,那就将 传入的 el 属性的 outerHtml 作为模板来编译( 之所以是 outerHTML 而不是 innerHTML 是因为,root所在的div 标签也是需要被编译进去的,可以在 root 所在的标签上 添加一个绑定属性来鉴定,因为如果改标签被编译了,那么绑定的属性也会编译)。
(2)、如果传了,那就是通过 render 函数,将 templete 配置项中的模板进行编译。templete 配置项其实就是一个字符串,直接将需要展示的页面复制粘贴进去就行,但是会发现语法报错,所以需要使用es6字符串模板。
el: '#root',
template: `欢迎学习 Vue
111111
`,
现在语法不报错了,但是在编译过程中会出现一个错误
这个错误实际上说的就是,这个模板里面有两个根节点,解决办法就是在模板外部添加一个根节点进行包裹。
el: '#root',
template: `欢迎学习 Vue
111111
`,
编译完成之后,我们可以在DOM节点中看到, templete 中的模板完全替代了 我们的el节点
c、在这个编译解析过程中,此阶段 Vue 开始解析模板,生成的虚拟DOM还存在内存中,因为虚拟DOM此时还未转化为真实DOM,页面暂时还不能展示解析好的内容。展示的是未经编译的代码。
2、beforeMount(载入前):此时页面呈现的是未经 Vue编译的 DOM 结构,所有对 DOM 的操作,最终都是不奏效的(因为下一步的操作直接将原来就生成的虚拟DOM生成了真实DOM,即使在这里改变了 DOM 结构,但是初始化的虚拟DOM还是为改变的,所以不奏效)。
4、mounted(载入后)
1、mounted(载入后)流程:将内存中的虚拟DOM转化为真实DOM,然后创建一个vm实例下的 $el 属性,将真实DOM往 vm.$el 上存了一份,作为后期更新数据之后的复用节点对比。然后用 vm.$el 属性替代原本的 el 属性。在这个流程中,会插入页面中,此时页面展示的是经过编译之后的结果
2、mounted(载入后):
a、此时页面中呈现的是经过 Vue 编译过后的DOM。
b、在这个生命周期中,对DOM的操作均是有效的(但尽可能避免在此阶段操作DOM)
c、自此生命周期钩子函数执行完毕,代表初始化过程结束,一般在此进行:开启定时器,发送网络请求、订阅消息、绑定自定义事件等初始化操作
5、beforeUpdate(更新前)
1、beforeUpdate(更新前)流程:这个流程没有做啥操作
2、beforeUpdate(更新前):通过Vue的数据监听发现了数据更改之后,然后同步更新data中的数据,但是此时数据是新的,页面却还未改变,即:页面尚未与数据保持同步。
6、updated(更新后)
1、updated(更新后)流程:数据更新之后根据新的数据生成新的虚拟DOM,随后与旧的虚拟DOM进行比较,然后生成新的真实DOM(此时会复用$el中保存的真实DOM),然后完成页面的更新。
2、updated(更新后):此时数据是新的,页面也是新的,即:页面与数据已经保持同步。
7、beforeDestroy(销毁前)
1、beforeDestroy(销毁前)流程:如果此时我不希望在我的数据更新之后,页面上继续响应变化,那么可以调用 vm.$destroy() 这个方法(这是有直接触发)或者等待路由切换等被动触发,那么 Vue 就进入了销毁流程
2、beforeDestroy(销毁前):此时vm中的所有:data、methods、指令等等都还是处于可用状态,也就是说,之前的页面数据还是照常展示(能取到data中的数据),还可以点击按钮触发绑定的事件(但是事件触发后改变的data不会继续更新),马上进入销毁流程,一般在此阶段我们可以关闭定时器、取消订阅信息、解绑自定义事件等等操作。
8、destroyed(销毁后)
1、destroyed(销毁后)流程:移除watcher监听器、子组件以及事件监听。
2 、destroyed(销毁后):销毁整个vm 实例
但是在 Vue 调用这些生命周期钩子函数之前,存在一些流程,这些流程不是我们能够干预的,是 Vue 自己走完这些流程之后,自动执行了生命周期钩子函数,进而完成了 对 Vue 实例的完善