Vue关于$nextTick源码的学习笔记
程序员文章站
2022-03-28 11:13:27
...
我们都知道Vue在视图更新采用的是异步更新策略,简单来说,Vue 在修改数据后,视图不会立刻更新,而是等同一事件中的所有数据变化完成之后,再统一进行视图更新。
官网给出的$nextTick的用法:在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
说到更新,那肯定要提到 Watcher,我们来看源码:
Watcher:
// 以下是我精简过的代码
// 初始化时
this.deep = this.user = this.lazy = this.sync = false
...
update () {
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
...
queueWatcher
也就是当触发update更新的时候,会去执行queueWatcher方法:
let has = {};
let queue = [];
let waiting = false;
let flushing= false;
// queueWatcher函数 部分代码
export function queueWatcher (watcher: Watcher) {
// 防止queue队列wachter对象重复
// 意思就是 去重 实现同一个数据多次修改,只触发一个Watcher
const id = watcher.id
if (has[id] == null) {
has[id] = true
if (!flushing) {
queue.push(watcher)
} else {
let i = queue.length - 1
while (i > index && queue[i].id > watcher.id) {
i--
}
queue.splice(i + 1, 0, watcher)
}
// queue the flush
if (!waiting) {
waiting = true
if (process.env.NODE_ENV !== 'production' && !config.async) {
flushSchedulerQueue()
return
}
nextTick(flushSchedulerQueue)
}
}
}
// flushSchedulerQueue函数 部分代码
function flushSchedulerQueue () {
let watcher, id;
for (index = 0; index < queue.length; index++) {
watcher = queue[index];
id = watcher.id;
has[id] = null;
// 执行更新
watcher.run();
}
// 更新完毕恢复标志位
waiting = false;
}
- queue 里面存放着我们本次要更新的watcher对象,queueWatche r函数做了一个去重操作,相同的 watcher 对象只会被加入到queue队列一次。
- flushSchedulerQueue 函数依次调用了wacther对象的run方法执行更新,并作为回调传递给了nextTick函数。
- waiting 这个标记位代表我们是否已经向 nextTick 函数传递了更新任务,nextTick 会在当前更新任务结束后再去处理传入的回掉,只需要传递一次,更新完毕再重置这个标志位。
- waiting 变量,这是很重要的一个标志位,它保证 flushSchedulerQueue 回调只允许被置入callbacks一次。
$nextTick实现:
let callbacks = [];
let pending = false;
let timerFunc;
/**----- nextTick -----*/
function nextTick (cb) {
// 把传进来的回调函数放到callbacks队列里
callbacks.push(cb);
// pending代表一个等待状态 等这个tick执行
if (!pending) {
pending = true
timerFunc()
}
// 如果没传递回调 提供一个Promise化的调用
if (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve
})
}
}
/**----- timerFunc ----*/
// 1、优先考虑Promise实现
if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
if (isIOS) setTimeout(noop)
}
isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
MutationObserver.toString() === '[object MutationObserverConstructor]')) {
// 2、降级到MutationObserver实现
let counter = 1
const observer = new MutationObserver(flushCallbacks)
const textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
characterData: true
})
timerFunc = () => {
counter = (counter + 1) % 2
textNode.data = String(counter)
}
isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
// 3、降级到setImmediate实现
timerFunc = () => {
setImmediate(flushCallbacks)
}
} else {
// 4、如果以上都不支持就用setTimeout来兜底了
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
function flushCallbacks () {
// 将callbacks中的cb依次执行
pending = false
const copies = callbacks.slice(0)
callbacks.length = 0
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
}
- 首先Vue通过callback数组来模拟事件队列,事件队里的事件,通过nextTickHandler方法来执行调用,而何时进行执行,是由timerFunc来决定的。
- timerFunc 是实现的核心,它会优先使用Promise等microtask,保证在同一个事件循环里面执行,这样页面只需要渲染一次。
- Promise --> MutationObserver--> setImmediate --> setTimeout,vue在这里用了降级处理的策略。
上一篇: 关于Vue的学习笔记
下一篇: JavaScript学习笔记(五)