vue中的nextTick实现原理__Vue.js
粉丝福利 : 关注VUE中文社区公众号,回复视频领取粉丝福利
前言
根据数据的响应式原理,在数据变化之后会触发dom的更新。但是如果一个数据更新就引起dom的重新渲染,这样显然会导致大大降低性能。因此vue采用异步更新,更新数据后不能立刻拿到最新的节点,但是可以通过nextTick等待页面更新好之后再获取最终的dom。
数据更新
在vue中,数据的更新通过调用watcher上的update方法实现。而每一次调用update就可以把需要更新的watcher暂存起来,形成wathcer队列。
update() {
// this.get(); // 渲染更新
// 不要每次都调用get方法
queueWatcher(this); // 暂存的概念
}
watcher队列
通过queueWatcher函数,将需要批量更新的watcher存在一个队列中,并做去重操作。flushScheduleQueue将队列中的watcher执行。这里的关键就是nextTick,它将watcher执行的操作变成了异步。
let queue = []; // 将需要批量更新的watcher存在一个队列中 稍后让watcher执行
let has = {};
let pending = false;
function flushScheduleQueue() {
queue.forEach(watcher => {watcher.run(),watcher.cb()})
queue = []
has = {}
pending = false
}
function queueWatcher(watcher) {
const id = watcher.id
if (has[id] == null) {
queue.push(watcher)
has[id] = true;
// 等待所有同步代码执行完毕之后再执行
if (!pending) {
nextTick(flushScheduleQueue)
pending = true
}
}
}
nextTick
nextTick这个异步函数,不单是给watcher做内部调用,用户也可以通过$nextTick调用,但是整个异步的过程只会调用一次,因此需要把这些异步的操作暂存起来。
let callbacks = [];
function flushCallbacks() {
while (callbacks.length) {
let cb = callbacks.shift()
cb()
}
pending = false
}
let pending = false;
export function nextTick(cb) { // 内部会调用nextTick 用户也会调用 但是异步只需要更新一次
callbacks.push(cb);
if(!pending) {
timerFunc(); // 这个方法是异步方法 做了兼容性处理
pending = true;
}
}
根据上面的代码,我们只要通过函数timerFunc异步调用flushCallbacks函数就可以实现nextTick的异常更新,但是这个异步方法需要做兼容性处理。
nextTick的兼容性处理
let timerFunc;
if (Promise) {
timerFunc = ()=>{
Promise.resolve().then(flushCallbacks)
}
}else if (MutationObserver) { // 可以监控DOM变化,并且是异步更新
let observer = new MutationObserver(flushCallbacks);
let textNode = document.createTextNode(1)
observer.observe(textNode,{characterData:true})
timerFunc = ()=>{
textNode.textContent = 2;
}
}else if (setImmediate) {
timerFunc = ()=>{
setImmediate(flushCallbacks)
}
}else {
timerFunc = ()=>{
setTimeout(flushCallbacks);
}
}
总结
nextTick实现的几个重点:
- 1.将异步操作的函数暂存起来
- 2.通过pending防止重复调用异步函数(类似防抖/节流)
- 3.对异步操作做兼容性处理
版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 作者: 知识的搬运工 原文链接:https://juejin.im/post/6869757494873292813