nextTick源码解析

nextTick()的解释:

前情提要:在vue中修改状态时,DOM更新不是同步的。vue会把所有数据变更缓存到一个队列中,到下一次触发时间的时候才执行。

好处是:可以将多次数据更新合并成一次,减少DOM的更新次数,提高性能。

nextTick():

接受一个回调函数,这个回调函数在数据修改之后立即使用。可以确保传入的回调函数会在数据更新完成之后执行。

nextTick()的源码分析:

实现原理:

nextTick中维护了一个回调函数队列,一个pending,一个timeFunc()。

pending的作用:用来标识同一个时间只执行一次。

timeFunc的作用: 选择当前环境优先支持的异步方法,多次调用nextTick函数中,timeFunc只会执行一次。

步骤:

(1)将回调函数放入回调函数队列中等待

(2)如果是第一次使用nextTick函数,会使用timeFunc判断一下当前环境中支持的异步方式。

(3)将执行任务放入微任务或者任务中。事件循环到任务时,依次执行回调函数队列中的函数。

 // 标记 nextTick 最终是否以微任务执行
export let isUsingMicroTask = false    

// 回调函数队列,保存使用 nextTick 时传入的回调函数
const callbacks = []     
// 用来标记任务队列中是否含有任务,如果有,就执行timeFunc函数;
let pending = false     

// 声明 nextTick 函数,接收一个回调函数和一个执行上下文作为参数
// 回调的 this 自动绑定到调用它的实例上
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
        timerFunc()
    }
    
    // 如果没有传入回调,并且当前环境支持 promise,就返回一个 promise
    // 在返回的这个 promise.then 中 DOM 已经更新好了
    if (!cb && typeof Promise !== 'undefined') {
        return new Promise(resolve => {
            _resolve = resolve
        })
    }
}


// 判断当前环境优先支持的异步方法,优先选择微任务
// 优先级:Promise---> MutationObserver---> setImmediate---> setTimeout

let timerFunc   
// 判断当前环境是否原生支持 promise
if (typeof Promise !== 'undefined' && isNative(Promise)) { 
    const p = Promise.resolve()
    timerFunc = () => {
       // 用 promise.then 把 flushCallbacks 函数包裹成一个异步微任务
        p.then(flushCallbacks)
        if (isIOS) setTimeout(noop)
        // 这里的 setTimeout 是用来强制刷新微任务队列的
        // 因为在 ios 下 promise.then 后面没有宏任务的话,微任务队列不会刷新
    }
    // 标记当前 nextTick 使用的微任务
    isUsingMicroTask = true
    
    // 如果不支持 promise,就判断是否支持 MutationObserver
    // 不是IE环境,并且原生支持 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))   
    // 监听这个文本节点,当数据发生变化就执行 flushCallbacks 
    observer.observe(textNode, { characterData: true })
    timerFunc = () => {
        counter = (counter + 1) % 2
        textNode.data = String(counter)  // 数据更新
    }
    isUsingMicroTask = true    // 标记当前 nextTick 使用的微任务
    
    // 判断当前环境是否原生支持 setImmediate
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
    timerFunc = () => { setImmediate(flushCallbacks)  }
} else {

    // 以上三种都不支持就选择 setTimeout
    timerFunc = () => { setTimeout(flushCallbacks, 0) }
}


// 如果多次调用 nextTick,会依次执行上面的方法,将 nextTick 的回调放在 callbacks 数组中
// 最后通过 flushCallbacks 函数遍历 callbacks 数组的拷贝并执行其中的回调
function flushCallbacks() {
    pending = false    
    const copies = callbacks.slice(0)    // 拷贝一份 callbacks
    callbacks.length = 0    // 清空 callbacks
    for (let i = 0; i < copies.length; i++) {    // 遍历执行传入的回调
        copies[i]()
    }
}

参考博客:nextTick实现原理,必拿下! - 掘金 (juejin.cn)

Vue中$nextTick源码解析 - 掘金 (juejin.cn)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Vue.js的$nextTick函数是用于在DOM更新之后执行异步操作的方法。该方法的实现使用了微任务队列,即将异步操作推入到微任务队列中,在DOM更新后执行异步操作。$nextTick方法是Vue.js响应式系统的重要部分,它确保了Vue.js组件的异步行为和数据响应式。 具体的实现过程如下: 1. 首先检查是否支持原生的Promise对象,如果支持,则直接返回Promise.resolve()。 2. 如果不支持原生的Promise对象,则创建一个新的Promise对象。 3. 将一个空函数推入微任务队列中。 4. 在新创建的Promise对象的resolve回调中,再次推入一个空函数到微任务队列中。 5. 当浏览器执行到微任务队列中的空函数时,DOM更新已经完成,可以执行异步操作了。 下面是$nextTick方法的源代码: ```javascript Vue.prototype.$nextTick = function(fn: Function) { return nextTick(fn, this) } ``` 其中,nextTick是一个工具函数,它实现了具体的异步操作逻辑: ```javascript 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) } }) if (!pending) { pending = true if (useMacroTask) { macroTimerFunc() } else { microTimerFunc() } } // $flow-disable-line if (!cb && typeof Promise !== 'undefined') { return new Promise(resolve => { _resolve = resolve }) } } ``` 该函数定义了一个callbacks数组用于存储异步操作,当调用nextTick函数时,将回调函数push进callbacks数组中,然后判断是否有待执行的异步操作,如果没有,则通过macroTimerFunc或microTimerFunc函数执行异步操作。 最后,如果调用$nextTick方法时没有传入回调函数,则会返回一个新的Promise对象,用于异步操作的等待和处理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值