vue中nextTick的理解以及如何实现
在下次DOM更新循环结束之后执行回调 文件位置 core/utils/next-tick
异步更新原理
data属性对应一个Dep
Dep在数据访问get的时候触发Dep.depend搜集watcher
Dep在更改数据的时候set触发Dep.notify便利触发watcher更新(此处vue会调用nextTick,所以dom更新是异步的)
// src > core > observer > watcher.js + scheduler.js// 当一个 Data 更新时,会依次执行以下代码 // 1. 触发 Data.set // 2. 调用 dep.notify // 3. Dep 会遍历所有相关的 Watcher 执行 update 方法 class Watcher { // 4. 执行更新操作 update() { queueWatcher(this); } } const queue = []; function queueWatcher(watcher: Watcher) { // 5. 将当前 Watcher 添加到异步队列 queue.push(watcher); // 6. 执行异步队列,并传入回调 nextTick(flushSchedulerQueue); } // 更新视图的具体方法 function flushSchedulerQueue() { let watcher, id; // 排序,先渲染父节点,再渲染子节点 // 这样可以避免不必要的子节点渲染,如:父节点中 v-if 为 false 的子节点,就不用渲染了 queue.sort((a, b) => a.id - b.id); // 遍历所有 Watcher 进行批量更新。 for (index = 0; index < queue.length; index++) { watcher = queue[index]; // 更新 DOM watcher.run(); } }
Vue并不会立即触发watcher跟新dom(算是性能优化)。而是把具体的方法传给nextTick的callback数组中,nextTick中包含watcher等更新dom方法以及用户调用nextTick的回调函数
nextTick执行timeFunc函数(适配浏览器promise => MutationObserve => setImmediate => setTimeout等异步任务),异步(promise等)回调执行flushCallbacks
nextTick方法
let pending = false // 异步锁 nextTick (cb?: Function, ctx?: Object) { callbacks.push(() => { // callback数组添加回调函数 cb.call(ctx) }) if (!pending) { // 判断是否需要添加任务队列 pending = true // 关锁 timerFunc() // timeFunc为兼容浏览器的任务队列方法法promise,setTiemout等,回调执行flushCallbacks } }
flushCallbacks方法
const callbacks = [] // 回调队列 function flushCallbacks () { pending = false // 开锁 const copies = callbacks.slice(0) // 复制一份回调数组 callbacks.length = 0 // 清空原callback数组 for (let i = 0; i < copies.length; i++) { copies[i]() } }
timeFunc主要是兼容浏览器
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]' )) { 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)) { timerFunc = () => { setImmediate(flushCallbacks) } } else { timerFunc = () => { setTimeout(flushCallbacks, 0) } }
nextTick为什么定义pending锁
添加一个回调函数就关闭锁,此时处于执行完同步代码就执行异步代码的情况,这个变量作用是只开启一个异步任务,仍然可以继续添加回调函数,限制不开启多个异步更新队列
nextTick为什么复制一份callback数组,回调函数队列
防止nextTick套用nextTick的情况,如果不做特殊处理会出现nextTick里面的nextTick提前进入任务队列,
相当于下一个班车的乘客提前上了上一个班车(例子生动形象)
实现简易版的nextTick
1 let pendding = false; 2 let callback = [] 3 function flushCallback() { pedding = false 4 let copies = callback.slice(0) // 复制一份callback 5 callback.length = 0 // 清空callback 6 for(var i = 0; i < copies.length; i++) { 7 copies[i]() 8 } 9 } 10 function nextTick(cb) { 11 callback.push(cb) 12 if (!pendding) { // 判断锁是否开着 13 pendding = true; // 关锁 14 setTimeout(flushCallback, 0) // 添加一个异步任务 15 } 16 }

浙公网安备 33010602011771号