Vue中nextTick()的理解

目录

前言

一、JavaScript的事件循环机制

1.1执行栈

1.2任务队列

1.2.1宏任务

1.2.2微任务 

1.3事件循环

1.4宏任务与微任务执行例子

二、Vue的异步更新策略

2.1为什么Vue选择异步更新

2.2Vue的异步更新实现

2.3nextTick

2.4使用nextTick的最佳时机


前言

在了解nextTick()之前,需要先了解JavaScript的“事件循环机制”和Vue的“异步更新策略”。

一、JavaScript的事件循环机制

1.1执行栈

JavaScript是“单线程”的,所有代码在“执行栈”中依次执行。

即:在代码中,代码执行顺序为从上至下,依次执行 

console.log('1')
function foo() { console.log('2') }
foo()
console.log('3')
// 执行顺序:1 → 2 → 3

1.2任务队列

异步操作(setTimeout、Promise)等会将回调函数放入任务队列,等待执行栈为空时执行。

即:所有同步代码执行完后,再统一执行需要异步操作的代码

任务队列分为两种:

  • 宏任务:setTimeout、setInterval、I/O操作、UI渲染等
  • 微任务:Promise.then、MutationObeserver

1.2.1宏任务

宏任务是事件循环中的“宏观任务”。

常见场景

  1. 定时器:setTimeout、setInterval
  2. I/O操作:文件读写、网络请求
  3. UI渲染浏览器的DOM更新、布局计算
  4. 事件回调:鼠标点击、键盘输入等事件的回调

执行规则

  1. 事件循环的一个周期内,先执行一个宏任务
  2. 宏任务执行过程中,可以同步产生新的宏任务或微任务
  3. 宏任务执行完毕后,会立即执行所有微任务(直到微任务队列为空)

1.2.2微任务 

微任务是在当前宏任务执行结束后、下一个宏任务开始前执行的“微观任务”,优先级高于宏任务

常见场景

  1. Promise回调:Promise.then()、Promise.catch()
  2. MutationObeserver:监听DOM变化的API
  3. Vue的nextTick:Vue内部基于Promise实现的微任务

执行规则

  1. 微任务会在“当前宏任务执行完毕后,下一个宏任务开始前执行
  2. 统一宏任务中产生的多个微任务会被“批量执行”(清空微任务队列)
  3. 微任务执行过程中可以继续产生新的微任务,这些微任务会立即加入队列并立即执行

1.3事件循环

事件循环的工作流程:

  1. 执行栈所有同步代码执行完毕
  2. 处理“微任务队列”中的所有任务(直到队列为空)
  3. 执行一个宏任务(UI渲染)
  4. 重复步骤2和3

1.4宏任务与微任务执行例子

下面是一个宏任务与微任务的执行示例:

console.log('同步代码开始') // 同步代码

// 宏任务 1:setTimeout
setTimeout(() => {
  console.log('setTimeout 回调(宏任务1)')
  // 微任务:在宏任务中添加新微任务
  Promise.resolve().then(() => {
    console.log('setTimeout 中的微任务')
  })
}, 0)

// 宏任务2:setTimeout
setTimeout(() => {
    console.log('setTimeout 回调(宏任务2)')
})

// 微任务 1:Promise.then
Promise.resolve().then(() => {
  console.log('Promise.then 回调(微任务)')
  // 微任务:在微任务中添加新微任务
  Promise.resolve().then(() => {
    console.log('微任务中的微任务')
  })
})

console.log('同步代码结束') // 同步代码

执行结果

执行顺序分析

  1. 首先顺序执行所有“同步代码
  2. 之后,执行“微任务队列”中的所有微任务
  3. 之后,从“宏任务队列”中取出一个宏任务执行
  4. 之后,再次执行“微任务队列”中的所有微任务
  5. 之后,再次从“宏任务队列”中取出一个宏任务执行
  6. 最后,微任务队列宏任务队列全部为空,但是事件循环仍然在循环并不断检测宏任务队列微任务队列

二、Vue的异步更新策略

2.1为什么Vue选择异步更新

我们都知道Vue最大的特点在于它的“响应式更新”,但是如果每次数据更新都立即更新DOM,会导致大量的DOM操作,影响性能

// 同步更新会触发三次DOM重渲染
data.count = 1;
data.count = 2;
data.count = 3;

// 异步更新只会触发一次DOM重渲染
setTimeout(() => {
    Promise.resolve().then(() => {
        data.count = 1;
        data.count = 2;
        data.count = 3;
    })
}, 0);

2.2Vue的异步更新实现

当我们修改Vue响应式数据时:

  1. Vue会将DOM更新延迟到下一个“tick
  2. 同一轮事件循环中的所有数据变更会被“合并
  3. 所有变更处理完成后,Vue会“批量更新DOM

注意

  1. 正因为Vue的“批量更新DOM”,所以在我们修改数据后,无法立即获取更新后的DOM
  2. Vue中的DOM更新是在“微任务阶段”执行的

2.3nextTick

nextTickVue中一个处理“异步操作”的回调函数

nextTick本质上:“将回调函数放入微任务队列”,因此只要执行DOM操作后,接着使用nextTick()即可获取更新后的DOM

注意:

nextTick()的执行时机也可能会影响调用结果,即nextTick()插入微任务队列的先后位置,也将影响结果。

nextTick()可以正确调用微任务队列前面任务已经执行的结果,不能调用nextTick()之后未执行的结果。

详细例子如下:

import { ref, nextTick } from 'vue'
const A = ref(0)
const B = ref(0)

// 模拟微任务队列中的三个任务
const enqueueMicrotasks = () => {
  // 任务1:修改响应式变量A
  Promise.resolve().then(() => {
    A.value = 100
    console.log('任务1:修改A为', A.value)
  })
  
  // 任务2:nextTick回调,读取B的最新值
  nextTick(() => {
    console.log('任务2:nextTick读取B的值为', B.value) // 输出 0(未被任务3修改)
  })
  
  // 任务3:修改响应式变量B
  Promise.resolve().then(() => {
    B.value = 200
    console.log('任务3:修改B为', B.value)
  })
}

// 执行微任务队列
enqueueMicrotasks()

执行结果:

可以看到,nextTick并没有正确读取B的值,因为B的修改被单独作为一个微任务排放在nextTick()回调的后面,所以自然调取不到。

另一个nextTick()例子:

// 模拟微任务队列中的三个任务
const enqueueMicrotasks = () => {
  // 任务1:修改响应式变量A
  A.value = 100
  
  // 任务2:nextTick回调,读取B的最新值
  nextTick(() => {
    console.log('任务2:nextTick读取B的值为', B.value)
    console.log('任务2:nextTick读取A的值为', A.value)
  })
  
  // 任务3:修改响应式变量B
  B.value = 200
}

// 另一个微任务队列
const enqueueMicrotasks2 = () => {
  A.value = 300
}

// 执行微任务队列
enqueueMicrotasks()
enqueueMicrotasks2()

执行结果:

  • enqueueMicrotasksenqueueMicrotasks2都是同步代码,包括里面的A.value和B.value这三个赋值语句都是同步代码,因此先依次执行这三段同步代码语句
  • nextTick()是微任务,因此在三段同步代码语句执行完毕后,会获取到最新的DOM元素

结论:

  1. 对响应式数据的修改不被单独作为一个微任务时,nextTick()此时放置在任何一个位置都可以获取到新数据
  2. 在Promise.then、Promise.catch中对响应式数据的修改,会单独作为一个微任务放置在微任务队列中,这个时候要正确使用nextTick()回调的位置

2.4使用nextTick的最佳时机

  • 当需要确保DOM已经更新时
  • 当需要访问组件引用或DOM元素时
  • 当需要在数据变更后执行依赖DOM操作时

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

是洋洋a

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值