[vue2源码]深度理解Vue中nextTick

    科技2022-07-11  91

    概念:

    nextTick: nextTick主要是使用了宏任务 (macrotask) 和微任务 (microtask) ,定义了一个异步方法,多次调用 nextTick 会将方法存入callback队列中,通过这个异步方法清空当前队列

    macrotask: setTimeout, setInterval, setImmediate, I/O, UI rendering

    microtask: process.nextTick, Promise, MutationObserver

    顺序:

    任务队列中,在每一次事件循环中,macrotask只会提取一个执行,而microtask会一直提取,直到microsoft队列为空为止,主线程执行完成该任务后又会检查microtasks队列并完成里面的所有任务后再执行macrotask

    场景:

    在数据变化后要执行的某个操作,并随着操作需要改变dom更新的时候在 created 或者 mounted 阶段,需要操作后更新dom时如果先执行了nextTick再更新数据会无效

    nextTick和$nextTick:

    nextTick(callback): 全局方法,当数据发生变化,更新后执行回调 $nextTick(callback): 实例方法,自动把context参数绑定为调用它的实例,当dom发生变化,更新后执行的回调,一般会使用this. $nextTick

    原理:

    调用nextTick并传入两个参数:回调函数cb和回调函数的执行上下文ctx判断是否有回调函数有就存入队列没有就返回promise判断是否在执行回调函数如果没有则执行timeFunc异步方法,多次执行nextTick只会执行一次timerFunctimeFunc中选择一个异步方法: (1)先尝试promise回调,进行异步执行flushCallbacks (2)若不支持则继续尝试MutationObserver回调,会创建一个文本节点进行监听 (3)若不支持则继续尝试setImmediate回调,在setImmediate下执行flushCallbacks (4)都不支持则使用setTimeout(flushCallbacks, 0)执行flushCallbacks方法

    源码:

    nextTick

    export function nextTick (cb?: Function, ctx?: Object) { let _resolve // 将拿到的回调函数存放到数组中 callbacks.push(() => { if (cb) { try { // 错误捕获 cb.call(ctx) } catch (e) { handleError(e, ctx, 'nextTick') } } else if (_resolve) { _resolve(ctx) } }) // 如果当前没有在执行,就会执行timeFunc if (!pending) { //标记正在执行 pending = true // 多次执行nextTick只会执行一次,timerFunc就是一个异步方法 timerFunc() } }

    timeFunc

    // 判断是否原生支持promise if (typeof Promise !== 'undefined' && isNative(Promise)) { const p = Promise.resolve() // flushCallbacks就包裹了一个promise timerFunc = () => { // 如果支持则异步的去执行flushCallbacks p.then(flushCallbacks) if (isIOS) setTimeout(noop) } // 标记微任务 isUsingMicroTask = true // 判断是否原生支持MutationObserver } else if (!isIE && typeof MutationObserver !== 'undefined' && ( isNative(MutationObserver) || MutationObserver.toString() === '[object MutationObserverConstructor]' )) { // 也是一个微任务 let counter = 1 // new了一个MutationObserver类 const observer = new MutationObserver(flushCallbacks) // 创建了一个文本节点 const textNode = document.createTextNode(String(counter)) // 原生api,帮我们监听一个节点 // 当数据发生变化了就会异步执行flushCallbacks方法 observer.observe(textNode, { characterData: true }) timerFunc = () => { counter = (counter + 1) % 2 // 数据更新 textNode.data = String(counter) } // 标记微任务 isUsingMicroTask = true // 判断是否原生支持setImmediate } else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { // setImmediate原生方法,默认ie下有,高版本的谷歌也支持 timerFunc = () => { // 直接执行 setImmediate(flushCallbacks) } } else { // 如果以上都不支持则采用setTimeout timerFunc = () => { setTimeout(flushCallbacks, 0) } }

    flushCallbacks

    // 回调函数队列 const callbacks = [] // 空闲状态准备执行 let pending = false // 多个nextTick中传递的回调函数依次执行 function flushCallbacks () { pending = false // 拷贝一份禁止套娃 const copies = callbacks.slice(0) // 清空队列 callbacks.length = 0 // cb执行过程中可能又会往callbacks中加入内容 // 遍历完拷贝的队列,新任务在下一轮存入callbacks执行 for (let i = 0; i < copies.length; i++) { copies[i]() } }
    Processed: 0.052, SQL: 8