nextTick 原理,在Vue2、Vue3中分别是什么步骤实现的,简单模拟实现流程

Posted by CodingWithAlice on February 17, 2025

nextTick 原理,在Vue2、Vue3中分别是什么步骤实现的,简单模拟实现流程

nextTick

  • 作用:在下次 DOM 更新循环结束之后,执行延迟回调
Vue2

利用 JavaScript 的异步执行机制(如微任务、宏任务),将传入 nextTick 的回调函数延迟到 DOM 更新后执行

  • 原因:因为 Vue 的数据更新是异步执行的,当你修改数据时,Vue 不会立即更新 DOM,而是将这些更新操作放入一个队列中,等到「下一个事件循环」时再「批量更新」 DOM,所以需要 nextTick 来确保在 DOM 更新后再执行某些操作 - 实现步骤:
    • 1、回调函数收集:nextTick 会将传入的回调函数收集到一个队列中
    • 2、异步执行方式选择:Vue 2 会根据不同的浏览器环境选择合适的异步执行方式,优先使用微任务(如 Promise、MutationObserver),如果不支持微任务则使用宏任务(如 setTimeout)
    • 3、触发异步执行:当第一次调用 nextTick 时,会触发异步执行,将队列中的回调函数在下次 DOM 更新循环结束后依次执行
Vue3

原理与 Vue2类似,但是使用 queueMicrotask 来统一处理微任务

  • 实现步骤: - 1、回调函数收集:nextTick 会将传入的回调函数收集到一个队列中 - 2、使用 queueMicrotask 将执行的回调函数放入微任务,确保在 DOM 更新后使用
//  Vue2 中的 nextTick 模拟实现
const callbacks = []; 
let pending = false;  // 标记是否正在执行回调函数
function flushCallbacks(){ // 异步执行回调函数
    pending = false;
    const copies = callbacks.slice(0);
    copies.forEach(it => it());
}

// step2:根据当前浏览器环境,选择异步执行方式
let timeFunc;
if(typeof Promise !== 'undefined'){
    const p = Promise.resolve(); // 使用 Promise 作为微任务
    timeFunc = () => {
        p.then(flushCallbacks);
    }
} else if(typeof MutationObserver !== 'undefined') {
    const obs = new MutationObserver(flushCallbacks); // 使用 MutationObserver 作为微任务
    let count = 1;
    const textNode = document.createTextNode(String(count));
    obs.observe(textNode, { characterData: true }); // 监听目标节点的文本内容变化
    timeFunc = () => {
        count = count + 1;
        textNode.data = String(count);
    }
} else if(typeof setImmediate !== 'undefined'){
    timeFunc = () => {
        setImmediate(flushCallbacks); // 使用 setImmediate 作为宏任务
        // 在 Node.js 的事件循环中(在一些浏览器中也有支持),setImmediate 的回调函数会在 I/O 回调之后、setTimeout 和 setInterval 的回调之前执行。它的执行时机是在当前轮次的事件循环结束后,下一轮事件循环开始时
    }
} else {
    timeFunc = () => {
        setTimeout(flushCallbacks, 0); // 使用 setTimeout 作为宏任务
    }
}

// step3: nextTick 函数
function nextTick(cb, ctx) {
    let _resolve;
    // step1:存储回调函数的队列
    callbacks.push(() => {
        if(cb){ 
            try { cb.call(ctx) } catch(e){ console.error(e) }
        } else if(_resolve) {
            _resolve(ctx);
        }
    })
    // step4:执行异步任务
    if(!pending){
        pending = true; // 正在执行回调函数
        timeFunc();
    }
    // 如果没有传入回调函数,返回一个 Promise
    if (!cb && typeof Promise !== 'undefined') {
        return new Promise(resolve => {
            _resolve = resolve;
        })
    }
}

// 使用示例
new Vue({
    data() {
        return { msg: 'hello' }
    },
    mounted() {
        this.msg = 'world';
        // 在 DOM 更新后执行回调
        this.$nextTick(() => {
            console.log('DOM 已经更新');
        });
    }
})
// Vue3 中的 nextTick 模拟实现
const queue = []; // 存储回调函数的队列
let isFlushing = false; // 标记是否正在执行回调函数

// 执行队列中的回调函数
function flushJobs() {
    isFlushing = true;
    let job;
    while ((job = queue.shift())) {
        job();
    }
    isFlushing = false;
}

// nextTick 函数
function nextTick(cb) {
    return new Promise((resolve) => {
        const runner = () => {
            if (cb) {
                try { cb() } catch (e) { console.error(e) }
            }
            resolve();
        };
        // step1:存储回调函数的队列
        queue.push(runner);
        // step2:执行异步任务
        if (!isFlushing) {
            queueMicrotask(flushJobs);
        }
    });
}

// 使用示例
import { createApp, nextTick } from 'vue';
const app = createApp({
    data() {
        return { message: 'Hello'};
    },
    mounted() {
        this.message = 'World';
        nextTick(() => { console.log('DOM 已经更新') })
    }
});

app.mount('#app');