前言
含义和使用
nextTick的官方解释:
在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
啥意思呢,即我们对Vue中data数据的修改会导致界面对应的响应变化,而通过nextTick方法,可以在传入nextTick的回调函数中获取到变化后的DOM,讲起来可能还是有点梦幻,下面我们直接使用nextTick
体验一下效果。
比如我们有如下代码:
<template>
<div>
<button @click='update'>更新数据</button>
<span id='content'>{
{message}}</span>
</div>
</template>
<script>
export default{
data:{
message:'hello world'
},
methods:{
update(){
this.message='Hello World'
console.log(document.getElementById('content').textContent);
this.$nextTick(()=>{
console.log(document.getElementById('content').textContent);
})
}
}
}
</script>
上述代码第一次输出结果为hello world
,第二次结果为更新后的Hello World
。
hello world
Hello World
即我们在update方法中第一行对message的更新,并不是马上同步到span中,而是在完成span的更新之后回调了我们传入nextTick的函数。
// 修改数据
vm.data = 'Hello'
//---> DOM 还没有更新
Vue.nextTick(function () {
//---> DOM 更新了
})
这里我们也可以理解为Vue中数据的更新不会同步触发dom元素的更新,也就是说dom更新是异步执行的,并且在更新之后调用了我们传入nextTick的函数。
那么问题来了,Vue为什么需要nextTick呢?nextTick又是如何实现的呢?
探索
这里我们就抱着好奇的心态,理解一下nextTick函数的实现原理,加深对Vue底层原理的理解。
要想理解nextTick的设计意图和实现原理我们需要两块的前置知识理解:
- Vue响应式原理(理解设计意图)
- 浏览器事件循环机制(理解原理)
因此本次行文先简单讲解以上两部分内容,最后将知识整合详细介绍nextTick的实现原理。
响应式原理
这部分内容主要介绍Vue的响应式实现原理,已经理解的同学可以跳过。
Vue响应原理的核心是数据劫持和依赖收集,主要是利用Object.defineProperty()
实现对数据存取操作的拦截,我们把这个实现称为数据代理;同时我们通过对数据get方法的拦截,可以获取到对数据的依赖,并将出所有的依赖收集到一个集合中。
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
//拦截get,当我们访问data.key时会被这个方法拦截到
get: function reactiveGetter () {
//我们在这里收集依赖
return data[key];
},
//拦截set,当我们为data.key赋值时会被这个方法拦截到
set: function reactiveSetter (newVal) {
//当数据变更时,通知依赖项变更UI
}
})
下面为了更好的理解之后nextTick的实现原理,我们需要先实现一个简化版的Vue。
Vue类
首先我们实现一个Vue类,用于创建Vue对象,它的的构造方法接收一个options参数,用于初始化Vue。
class Vue{
constructor(options){
this.$el=options.el;
this._data=options.data;
this.$data=this._data;
//对data进行响应式处理
new Observe(this._data);
}
}
//创建Vue对象
new Vue({
el:'#app',
data:{
message:'hello world'
}
})
上面的代码中我们首先创建了一个Vue
的类,构造函数跟我们平时使用的Vue大致一致,为了容易理解我们这里只处理了参数el
和data
。
我们发现构造函数的最后一行创建了一个Observe类的对象,并传入data
作为参数,这里的Observe
就是对data
数据进行响应式处理的类,接下来我们看一下Observe
类的简单实现。
Observe类
我们在Observe类中实现对data的监听,就是通过Object.defineProperty()方法实现的数据劫持,代码如下。
class Observe{
constructor(data){
//如果传入的数据是object
if(typeof data=='object'){
this.walk(data);
}
}
//这个方法遍历对象中的属性,并依次对其进行响应式处理
walk(obj){
//获取所有属性
const keys=Object.keys(obj);
for (let i = 0; i < keys.length; i++) {
//对所有属性进行监听(数据劫持)
this.defineReactive(obj, keys[i])
}
}
defineReactive(obj,key){
if(typeof obj[key]=='object'){
//如果属性是对象,那么那么递归调用walk方法
this.walk(obj[key]);
}
const dep=new Dep();//Dep类用于收集依赖
const val=obj[key];
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
//get代理将Dep.target即Watcher对象添加到依赖集合中
get() {
//这里在创建Watcher对象时会给Dep.target赋值
if (Dep.target