其他方式的nextTick

这几天利用空余时间在完成一个去年未尽的活:从零开始实现一个Promise。

浏览器中原生的Promise,then里面的回调被放入事件循环中的微任务队列,而setTimeout的回调则被放入了宏任务队列。一轮事件循环结束,先清空微任务队列,才会执行一个宏任务。所以,Promise中then的回调执行时机早于setTimeou的回调执行时机的。

我们既然打算自己实现一个Promise的话,那么它的执行时机越早越好,从目前我所知的各种回调来说,没有什么比Promise更早了,那么存不存在一个比setTimeout执行时机更早的呢?

好在,原来看司徒正美的博客有留意到他提过某些标签的onerror事件执行会比较早,具体记不得是哪篇博客了,于是乎自己尝试一番得到了下面这个函数:

function nextTick(fn){
    var img = document.createElement('img');
    img.onerror = function(){fn()}
    img.src='data:;,'
}

测试:


setTimeout(function(){console.log(1)}) nextTick(function(){console.log(2)});

在IE9+及其他现代浏览器上得到的输出顺序都是:2、1

创建一个Dom元素而不插入文档流,代价应该不是很大,如果实在要纠结这个,我们不如把它放入一个闭包中,达到重复利用:

var nextTick = (function(){
    var img = document.createElement('img');
    return function nextTick(fn){
        img.onerror = function(){fn()}
        img.src='data:;,'
    }
})()

Vue中的$nextTick实现曾经用过MessageChannel,不过后面取消了,大部分情况下使用微任务

var nextTick = (function(){
    const channel = new MessageChannel()
    return function nextTick(fn){
        channel.port1.onmessage = ()=>fn()
        channel.port2.postMessage('')
    }
})()

另外,在IE10、IE11及Edge上有一个非标准的函数:setImmediateVue中的nextTick实现也会优先使用它,但我实验发现,它与setTimeou的执行顺序是无法确定。

另外另外另外再提一句,MutationObserver的回调和Promise一样,也属于micro-task,而且他的执行时机更是早于Promise。以下是实现:

function  nextTick(fn){
    var div = document.createElement('div');
    new MutationObserver(fn).observe(div, {  attributes: true })
    div.setAttribute('data-test','test')
}