vue中$nextTick的实现原理

vue中$nextTick的实现原理

DansRoh Lv4

介绍

Vue中的nextTick方法是Vue的一个重要异步工具,它用于将回调延迟到下一个DOM更新循环之后执行。
当你在Vue组件中更改了数据,而想要基于更新后的DOM执行操作时nextTick非常有用。
它等待Vue完成DOM的更新,然后执行你的回调函数。

实现原理

Vue的nextTick实现依赖于JavaScript的事件循环和微任务队列。
Vue尝试利用原生的Promise.then、MutationObserver,或者setImmediate,如果以上都不可用,则回退到setTimeout(fn, 0)。

以下是Vue源码中与nextTick实现相关的简化逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
let callbacks = []
let pending = false

function flushCallbacks () {
pending = false
const copies = callbacks.slice(0)
callbacks.length = 0
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
}

let timerFunc

if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
...
}
} else if (...) { // 检查是否可以使用MutationObserver
...
} else if (...) { // 检查是否可以使用setImmediate
...
} else { // 最后的回退方案是setTimeout
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}

export function nextTick (cb, ctx) {
let _resolve
callbacks.push(() => {
if (cb) {
try {
cb.call(ctx)
} catch (e) {
...
}
} else if (_resolve) {
_resolve(ctx)
}
})
if (!pending) {
pending = true
timerFunc()
}
if (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve
})
}
}
  1. 回调收集:所有传递给nextTick的回调被推入一个队列(callbacks数组)中等待执行。
  2. 异步延迟:timerFunc根据环境选择最优的异步延迟函数,使回调队列在当前执行栈结束之后尽快得到执行。
  3. 批量执行:被timerFunc调用的flushCallbacks函数会一次性执行所有排队的回调,这是一种性能优化,减少了重绘和重排的次数。

为什么优先使用微任务

使用微任务的优点在于微任务的执行时机早于宏任务(比如setTimeout),这使得Vue可以尽可能快地更新DOM。
同时也允许用户的回调函数能够在DOM更新完成后立即运行,从而可以访问更新后的DOM。

关于MutationObserver

  • MutationObserver是Web API的一部分,用于监控DOM树的变化。
  • 它允许开发者监听指定DOM元素的增加、删除、属性或内容的变动,并在这些变化发生时能够运行一些特定的代码。
  • 这对于需要响应DOM变化的情况,如动态内容加载、第三方库或框架的集成,非常有用。

关于setImmediate

setImmediate是一个非标准的API,最初由Node.js引入, 目的是将一些代码的执行排在事件队列的尽头,但在IO事件和计时器之前执行。
在浏览器环境中,setImmediate的支持并不广泛,主要是IE和Node.js支持。
因此,在编写通用代码时,依赖setImmediate可能会导致兼容性问题。

总结

nextTick实现原理,将传入的回调收集进入一个callbacks数组。
然后遍历callbacks,将每一个回调加入异步任务队列。
优先尝试加入Promise.resolve().then()微任务队列,如果不支持Promise,将会继续尝试使用web浏览器的api MutationObserver(), 其次尝试setImmediate()。
如果上面都不可行,则使用setTimeout(callback,0)的方式。😊

  • 标题: vue中$nextTick的实现原理
  • 作者: DansRoh
  • 创建于 : 2024-05-09 00:00:00
  • 更新于 : 2024-06-24 17:16:18
  • 链接: https://blog.shinri.me/2024/05/09/25_vue中$nextTick的实现原理/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论