vue nextTick源码

187 阅读2分钟

早之前有分享过vue的nextTick的使用,当时说当数据发生变化,更新后执行回调没有实现,那时候也不知道怎么测试的,其实nextTick方法只是做了一步异步。

先明确一下,修改数据、渲染页面,在vue里面都是同步的,包括生命周期,也是同步执行,而nextTick是用异步的回调,所以才能获取最新的dom或者实例属性。

之前2.2版本只有promise、MutationObserver、setTimeout,而且是一个自执行函数,我觉得那样看的更舒服,2.x最后的版本2.6.9就不太一样了。上源码,然后使用一下。

先确定用哪种异步任务,promise>motatiosObserver>setImmediate>setTimout,这几个的使用方法可以自己查一下:

function isNative (Ctor) {

    return /native code/.test(Ctor.toString())

}

var timerFunc;

if (typeof Promise !== 'undefined' && isNative(Promise)) {

    var p = Promise.resolve();

    timerFunc = function () {

        p.then(flushCallbacks);

    };

    isUsingMicroTask = true;

} else if (!isIE && typeof MutationObserver !== 'undefined' && (

  isNative(MutationObserver) ||

  MutationObserver.toString() === '[object MutationObserverConstructor]'

)) {

    var counter = 1;

    var observer = new MutationObserver(flushCallbacks);

    var textNode = document.createTextNode(String(counter));

    observer.observe(textNode, {

        characterData: true

    });

    timerFunc = function () {

        counter = (counter + 1) % 2;

        textNode.data = String(counter);

    };

    isUsingMicroTask = true;

} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {

    timerFunc = function () {

        setImmediate(flushCallbacks);

    };

} else {

    timerFunc = function () {

        setTimeout(flushCallbacks, 0);

    };

}

因为我们可能多次调用nextTick,多次调用也只会一起循环执行,不会调用一次执行一次,这边第一次调用nextTick就执行timerFunc,又因为timerFunc调用flushCallBacks的时候是异步的,nextTick是同步调用,所以当执行flushCallBacks的时候,callbacks是调用几个就有几个回调,pending就是这个用处。这边清空数组用length=0,以前还真不知道可以这样用。另外为什么要新声明一个copies,我觉得是防止nextTick里面再调用nextTick。

var callbacks = [];

var pending = false;



function flushCallbacks () {

    pending = false;

    var copies = callbacks.slice(0);

    callbacks.length = 0;

    for (var i = 0; i < copies.length; i++) {

        copies[i]();

    }

}



function nextTick (cb, ctx) {

    var _resolve;

    callbacks.push(function () {

        if (cb) {

            try {

                cb.call(ctx);

            } catch (e) {

                handleError(e, ctx, 'nextTick');

            }

        } else if (_resolve) {

            _resolve(ctx);

        }

    });

    if (!pending) {

        pending = true;

        timerFunc();

    }

    if (!cb && typeof Promise !== 'undefined') {

        return new Promise(function (resolve) {

            _resolve = resolve;

        })

    }

}



console.log(1);

nextTick(() => {

    console.log(2);

});

console.log(3);

有人会纠结事件循环是宏任务,微任务,UI渲染的顺序,那nextTick如果是微任务,怎么在UI渲染之前调用为什么还能获取最新的dom。执行下面代码看结果:

<div id="app">内容还没修改</div>



document.getElementById('app').innerText = '内容已经修改';

Promise.resolve().then(() => {

    alert(document.getElementById('app').innerText + 'promise')

})

setTimeout(() => {

    alert(document.getElementById('app').innerText + 'setTimeout')

})

alert(document.getElementById('app').innerText + 'end')

所以大胆得出一个结论,宏任务,微任务,UI渲染没有错,js修改了dom之后,在js里面去获取的时候,根据的是js对dom操作的结果,UI渲染只是页面的展示,并不影响js对dom元素的获取和操作。

上面是vue nextTick的源码,讲真,看别人源码还要去看一些api,然后分析为什么这样写,看着代码看明白了,一关上,就一点写不出来。