阅读 695

vue nextTick 异步更新机制

为什么要写这篇文章

1.是为了巩固一下自己的知识;
2.分享下自己的一些心得希望能帮到其他有过此困扰的同学,要是哪里写的不对还望大佬们指出来;

介绍nextTick之前咱们先来看一下js的任务队列

一、macro-task(宏任务)

// 这三个都属于是宏任务队列
1.setTimeout
2.setInterval
3.setImmediate

二、micro-task (微任务)

// 下边这是微任务
process.nextTick
Promise // 不过promise有个不太一样的地方就是.then()方法执行的微任务队列
new Promise(() => // 这里是同步任务队列).then(() => // 这里是)

三、同步任务

// 这个就比较好理解了,其实就是代码自上而下顺序执行,当前一个代码执行完之后才会进行下一步操作

function fun(){
   console.log('开始执行 1 次');
}
fun();  // 因为在js中是自上而下执行所以会先执行到这个地方;
console.log('开始执行 ~ ') // 这个会在上边的函数执行完之后执行;
复制代码

四、那么js任务的执行顺序怎样的呢?

// 按照优先级执行
同步任务(优先级最高)> micro-task (微任务) > macro-task(宏任务)

function fun(){ // 同步任务
   console.log('开始执行 1 次'); // 同步任务
}

new Promise(resolve => { // 同步任务
	console.log('我先执行');
	resolve('到我执行了');
}).then(data => { // 异步任务
	console.log(data);
})

setTimeout(() => { // 宏任务
	console.log('我是宏任务');
}, 0)

fun(); // 同步任务
console.log('开始执行 ~ '); // 同步任务
复制代码

// 上边的执行结果是这样子的
// 我先执行
// 开始执行 1 次
// 开始执行 ~
// 到我执行了
// 我是宏任务

上边这个例子说明了什么呢?说明,我们在写代码的时候,代码顺序重要吗重要啊,当然重要了,如果你有两个任务,然后任务B依赖于任务A,那这个时候就需要先执行A在执行B,不过呢我们要考虑好什么地方用宏任务什么地方使用微任务,什么地方使用同步任务

那介绍这么多,Vue 的nextTick 是怎么执行的呢,是属于同步任务还是微任务还是宏任务呢?

我们先来看一下vue的源码是怎么写的

上边的截图我们看到了nextTick传进来的第一个参数也就是我们的执行方法,会被放到callbacks数组中,也就是vue内部自己实现的一个执行的队列,咱们接着往下看

上边的截图我们可以看到还有个方法flushCallbacks, 这个方法的作用是什么呢,这个方法的作用其实就是利用了先进先出的思想去执行队列里边的任务,因为如果有多个nextTick在实例中出现,vue会默认先执行先进入队列里边的任务

我们再来看下边这张截图,会发现,其实整个任务队列都是放在了一个微任务队列中去执行的,所以呢看到这里大家应该就明白了nextTick里边的队列其实都是在微任务队列里边存放执行的,但callbacks本身又不是一个微任务他自己是一个同步任务,这个时候其实我们在代码中使用的时候,比如你在父组件中还套用了子组件,而子组件中还有微任务存在,这个时候就会出现一个问题,也就是微任务也是按顺序执行的,你在父组件中的nextTick执行完了却发现某些子组件的信息没能拿到,这个时候就可以使用一个宏任务代替nextTick就可以了,下边我们再看一个例子;

这是一个演示demo可以复制出来自己运行试一下

<div id="demo">
    <h1>异步更新</h1>
    <p id="p1">{{foo}}</p>
</div>
<script>
   // 创建实例
   const app = new Vue({
      el: '#demo',
      data: { foo: 'ready~~' },
      mounted() {
          this.foo = Math.random()
          console.log('1:' + this.foo);
          this.foo = Math.random()
          console.log('2:' + this.foo);

          this.foo = Math.random()
          console.log('3:' + this.foo);
          // 异步行为,此时内容没变
          console.log('p1.innerHTML:' + p1.innerHTML)

          // Promise.resolve().then(() => {
          // // 这里才是最新的值
          // console.log('p1.innerHTML:' + p1.innerHTML)
          // }) 

          this.$nextTick(() => {
             // 这里才是最新的值
             console.log('p1.innerHTML:' + p1.innerHTML)
          }) 

       }
   });
</script>
复制代码

上边这个例子中console.log()是一个同步任务;
同步任务的优先级要高于微任务和宏任务,所以会先执行console.log()和this.foo的修改值
如果这个时候我们直接打印P1.innerHTML的值是原来的值,因为vue会将update更新操作放到任务队列中
所以使用this.$nextTick()回调打印,这个时候nextTick实际上是一个任务队列,会将我们的回调函数追加到任务队列中去,
等我们的同步任务队列执行完成之后再依次执行

到这里就介绍完了,如果大家有什么想法也可以评论留言,共同探讨一下;
最近在整理自己学习vue3的文章有兴趣的朋友可以一起学习讨论vue2 学习之 vue3使用后续也会不断地更新

文章分类
前端
文章标签