小松的技术博客

六和敬

若今生迷局深陷,射影含沙。便许你来世袖手天下,一幕繁华。 你可愿转身落座,掌间朱砂,共我温酒煮茶。

前端nextTick函数

前端DOM的一个优化点是读写分离,那样可以减少界面渲染的次数。特别是DOM变动特别频繁的界面,如果能够把多次操作一次性写入,那么界面流畅度将会有很大提升。

为了这种性能提升,前端很多框架都有了一个nextTick函数,我们调用这个函数将操作压入队列,然后由它复制在恰当的时机一次性执行。

然后就是如何实现这个函数了,这里的写入是异步的,可能我们马上想到用一个队列来保存我们的操作函数,然后调用setTimeout异步执行队列里面的函数。

var nextTick = (function(){
    var queue = [];
    var pending = false;
    function callbacks(){
        pending = true;
        var copies = queue.slice(0);
        queue = [];
        for(var i=0; i<copies.length;i++){
            copies[i]();
        }
    }
    return function(fn){
        queue.push(fn);
        if(pending){
            return;
        }
        pending = true;
        setTimeout(callbacks);
    }
})()

但是setTimeout存在精度问题,是有延迟的,并非上乘方案,只能作为兼容方案。不同浏览器为我们提供了不同的异步api,我们可以通过这些api,打造更加高效的nextTick函数,先来了解下这些api

setImmediate

setTimmediate(handler)是IE10以上版本提供了这个异步api,它会监控UI线程的调用栈,一旦调用栈为空,则将handler压入栈中。使用上与setTimeout(handler)一样

了解详细:http://www.cnblogs.com/fsjohnhuang/p/4151595.html

MutationObserver

Mutation Observer(变动观察器)是监视DOM变动的接口。DOM发生任何变动,Mutation Observer会得到通知。它的触发是异步触发的,DOM发生变动以后,并不会马上触发,而是要等到当前所有DOM操作都结束后才触发,这非常适合DOM变动非常平凡的界面,可惜不是所有浏览器都支持。

var nextTick = (function(){
    var queue = [];
    function callbacks(){
        var copies = queue.slice(0);
        queue = [];
        for(var i=0; i<copies.length;i++){
            copies[i]();
        }
    }
    var counter = 1;
    var observer = new MutationObserver(callbacks);
    var textNode = document.createTextNode(counter)
    observer.observe(textNode, {
        characterData: true
    });

    return function (fn){
        queue.push(fn)
        counter = (counter + 1) % 2
        textNode.data = counter
    }
})();

了解详情:http://javascript.ruanyifeng.com/dom/mutationobserver.html

在avalon中,针对IE67使用了script的onreadystatechange来构建更快的异步回调

var nextTick = (function(){
    var queue = [];
    var pending = false;
    function callbacks(){
        pending = false;
        var copies = queue.slice(0);
        queue = [];
        for(var i=0; i<copies.length;i++){
            copies[i]();
        }
    }
    return function(fn){
        queue.push(fn);
        if(pending){
            return;
        }
        pending = true;
        var node = document.createElement("script")
        node.onreadystatechange = function () {
            callback(); //在interactive阶段就触发
            node.onreadystatechange = null;
            head.removeChild(node);
            node = null;
        };
        var head = document.getElementsByTagName("head")[0];
        head.appendChild(node)
    }
})()

加上对浏览器的支持判断条件,则完整的写法为:

var nextTick = (function(){
    var queue = [];
    var pending = false;
    function callbacks(){
        pending = false;
        var copies = queue.slice(0);
        queue = [];
        for(var i=0; i<copies.length;i++){
            copies[i]();
        }
    }
    var tickImmediate = window.setImmediate;
    var tickObserver = window.MutationObserver
        || window.WebKitMutationObserver
        || window.MozMutationObserver;;

    var timerFunc;


    if (tickImmediate) { //IE10 \11 edage
        timerFunc = tickImmediate;
    } else if(tickObserver){ //支持MutationObserver
        var counter = 1;
        var observer = new tickObserver(callbacks);
        var textNode = document.createTextNode(counter);
        observer.observe(textNode, {
            characterData: true
        });
        timerFunc = function () {
            counter = (counter + 1) % 2;
            textNode.data = counter;
        }
    }else if(window.VBArray){  //IE6 \7
        timerFunc = function(){
            var node = document.createElement("script");
            node.onreadystatechange = function () {
                callback(); //在interactive阶段就触发
                node.onreadystatechange = null;
                head.removeChild(node);
                node = null;
            };
            var head = document.getElementsByTagName("head")[0];
            head.appendChild(node)
        }
    }else{
        timerFunc = setTimeout;
    }

    return function (fn){
        queue.push(fn);
        if(pending){
            return;
        }
        pending = true;
        timerFunc(callbacks,0)
    }

})()
←支付宝← →微信 →
comments powered by Disqus