异步更新
标签:timerFunc,异步,缓存,watcher,源码,let,vue2,id From: https://www.cnblogs.com/dgqp/p/17334426.html
异步更新原因
以下情况下:
vm.name = '123' vm.name = '234' vm.name = '123' ...
如果我们频繁的修改一个数据,就会多次触发视图渲染
dep.notify->watcher.update
这样就会降低性能,因此就需要采用异步更新策略,仅仅在最后执行一次视图更新操作。
思路
当数据变化时,先将数据变更的逻辑缓存下来,不直接处理,如果有相同的数据更新进行合并,在最后仅执行一次视图更新。
缓存
watcher
更新逻辑思路:将
watcher
集中缓存到一个队列中,在缓存过程中进行合并,最后一次性执行。
queueWatcher
方法:缓存队列,用于watcher
的去重和缓存(唯一标识id)let queue = []; // 用于缓存渲染watcher let has = []; // 存放watcher唯一标识(id),用于watcher的查重 let pending = false; // 防抖 等待状态标识,用于控制setTimeout直走一次 function queueWatcher(watcher) { const id = watcher.id; // 获取watcher的id if (!has[id]) { // has对象没有当前watcher queue.push(watcher); // 缓存watcher。但不调用 has[id] = true; // 缓存标记 if (!pending) { // 不需要等待,相当于防抖策略,只执行一次 nextTick(flushSchedulerQueue()); // 执行函数(宏任务) } pending = true; // 首次进入被置为true,使微任务执行完成宏任务执行 } }
解释一下
pending
:
- 在
queueWatcher
方法中,同一watcher
只会保存一次,不同的watcher
就会多次加入到queue
中,pending
标记用于控制setTimeout
中的watcher
批量执行逻辑仅执行一次,相当于防抖策略nextTick
使一个宏任务,相当于异步代码,当全部watcher
存入队列中,就会执行内部的批量执行逻辑。然后需要在
Watcher
中封装一个run
方法:run() { let newValue = this.get(); if (this.user) { this.cb(vm, newValue, oldValue); } }
flushSchedulerQueue
方法。将刷新队列逻辑抽取出来封装到
flushSchedulerQueue
方法中function flushSchedulerQueue() { let flushQueue = queue.slice(0); flushQueue = []; has = {}; peding = false; flushQueue.forEach((q) => q.run()); }
nextTick
封装在对异步任务进行封装:
export function nextTick(cb) { callbacks.push(cb); if (!waiting) { timerFunc(); wating = true; } }
针对不同的异步任务进行分类:
let timerFunc; // promise if (Promise) { timerFunc = () => { Promise.resolve().then(flushCallbacks); }; // MutationObserver } else if (MutationObserver) { let observer = new MutationObserver(flushCallbacks); // 异步执行 let textNode = document.createTextNode(1); observer.observe(textNode, { characterData: true, }); timerFunc = () => { textNode.textContent = 2; }; // setImmediate } else if (setImmediate) { timerFunc = () => { setImmediate(flushCallbacks); }; // 最后setTimeout } else { timerFunc = () => { setTimeout(flushCallbacks); }; }