1. 程式人生 > 實用技巧 >JS一些類實現方式的效能研究

JS一些類實現方式的效能研究

從javaeye看到一貼,探討如何實現計時器,集思廣益,最後竟然提出了十多種的實現。這再次證明了js的寫法很靈活(舉個反面的例子,如Python,其哲學原則是one way to go!)。這裡整理一下,研究一下各種實現的效能問題。現在js越來越向富客戶端發展,UI元件乃至網頁遊戲,對效能問題是相當重視的。

實現1

  function Timer(id){
    this.id = id;
    this.begin = function(count){
      this.show(this.id, count)();
      setInterval(this.show(this.id, count-1),1000);
    }
    this.show = function(id, count){
      return function(){
        document.getElementById(id).innerhtml = count < 0 ? "over" :count;
        count--;
      }
    }
  }

點評:採用最經典的建構函式來實現類。好處是明快易懂,缺點是多個例項都是獨立的記憶體,資料與方法都不共享。而且還存在一個明顯的缺陷,計時完成之後,setInterval沒有被clear,嚴重影響了效能。

實現2

function Timer(id){
    this.id = id;
    this.timer = null;
    this.count = 0;
    this.begin = function(count){
        this.count = count;
        this.show(this)();
        this.timer = setInterval(this.show(this),1000);
    }
    this.show = function(obj){
        return function(){
            if(obj.count < 0){
                document.getElementById(obj.id).innerhtml = "over";
                clearInterval(obj.timer);
                return ;
            }
            document.getElementById(obj.id).innerHTML = obj.count;
            obj.count--;
        }
    }
}

點評:解決了實現1的setInterval未被clear的問題,但依然是利用建構函式來實現類,因此Timer每個例項都複製了一份show和begin,而不是共享同一份。

實現3

function Timer(id){
    this.id = id;
    this.timer = null;  
    this.count = 0;
    this.begin = function(count){
        this.count = count;
        Timer.show(this)();
        this.timer = setInterval(Timer.show(this),1000);
    }
    Timer.show = function(obj){
        return function(){
            if(obj.count < 0){
                document.getElementById(obj.id).innerHTML = "over";
                clearInterval(obj.timer);
                return ;
            }
            document.getElementById(obj.id).innerHTML = obj.count;
            obj.count--;
        }
    }
}

點評:把show()弄成類的靜態方法,讓所有例項共享此方法。注意,show()是個currying函式,這就解決了各例項的資料互相汙染的問題。

實現4

function Timer(id){
    this.id = id;
    this.timer = null;
    this.count = 0;
    this.begin = function(count){
        this.count = count;
        this.show(this)();//注意和實現三的區別:這裡不是Timer.show(this)();
        this.timer = setInterval(this.show(this),1000);//注意和實現三的區別:這裡不是Timer.show(this)();
    }
}
Timer.prototype.show = function(obj){
    return function(){
        if(obj.count < 0){
            document.getElementById(obj.id).innerHTML = "over";
            clearInterval(obj.timer);
            return ;
        }
        document.getElementById(obj.id).innerHTML = obj.count;
        obj.count--;
    }
}  

點評:這次把show()做成原型方法,由於所有例項都是共享原型,當例項在其成員中找不到此方法,它就會沿著原型鏈往上找。這個運用了很經典的類實現方式——混合的建構函式 /原型方式。

實現5

function Timer(id){
    this.id = id;
    this.timer = null;
    this.count = 0;

}
Timer.prototype.begin = function(count){
    this.count = count;
    this.show(this)();//注意這裡不是Timer.show(this)();
    this.timer = setInterval(this.show(this),1000);//注意這裡不是Timer.show(this)();
}
Timer.prototype.show = function(obj){
    return function(){
        if(obj.count < 0){
            document.getElementById(obj.id).innerHTML = "over";
            clearInterval(obj.timer);
            return ;
        }
        document.getElementById(obj.id).innerHTML = obj.count;
        obj.count--;
    }
}

點評:這次把bigin()也做成原型方法。

實現6

function Timer(id){  
    this.id = id;  
    this.timer = null;  
    this.count = 0;  
}  
Timer.prototype = {  
    begin : function(count){  
        this.count = count;  
        this.show(this)();//注意這裡不是Timer.show(this)();  
        this.timer = setInterval(this.show(this),1000);//注意這裡不是Timer.show(this)();  
    },  
    show : function(obj){     
        return function(){  
            if(obj.count < 0){  
                document.getElementById(obj.id).innerHTML = "over";  
                clearInterval(obj.timer);  
                return ;  
            }  
            document.getElementById(obj.id).innerHTML = obj.count;  
            obj.count--;  
        }  
    }   
} 

點評:把原型方法整到一個物件中去。

實現7

function Timer(id){  
    this.id = id;  
    this.timer = null;  
    this.count = 0;  
    Timer.prototype.begin = function(count){  
        this.count = count;  
        this.show(this)();//主要這裡不是Timer.show(this)();  
        this.timer = setInterval(this.show(this),1000);//主要這裡不是Timer.show(this)();  
    }  
    Timer.prototype.show = function(obj){     
        return function(){  
            if(obj.count < 0){  
                document.getElementById(obj.id).innerHTML = "over";  
                clearInterval(obj.timer);  
                return ;  
            }  
            document.getElementById(obj.id).innerHTML = obj.count;  
            obj.count--;  
        }  
    }   
}  

點評:把原型方法整到建構函式中去,但千萬不要弄成這個樣子:

function Timer(id){
    this.id = id;
    this.timer = null;
    this.count = 0;
    Timer.prototype= {
        begin:function(count){/*……*/},
        show : function(obj){ /*……*/}
    }
} 

因為直接為prototype 賦值,會使得自動新增的constructor 成員丟失!

實現8

var Timer = {
    begin : function(id,count){
        var obj = {};
        obj["id"] = id;
        obj["count"] = count;
        Timer.show(obj)();
        obj["timer"] = setInterval(Timer.show(obj),1000);//注意這裡不是Timer.show(this)();
    },
    show : function(obj){
        return function(){
            if(obj["count"] < 0){
                document.getElementById(obj["id"]).innerHTML = "over";
                clearInterval(obj["timer"]);
                return ;
            }
            document.getElementById(obj["id"]).innerHTML = obj["count"] ;
            obj["count"]--;
        }
    }
}

點評:這次採用字面量構造物件。Timer成了一個全域性物件(當我們第一次呼叫begin方法才產生此全域性物件),類似於window,生成例項方法不再是new,而是採用工廠方式的方法,每begin一次就生成一個例項,資料是獨立的,方法是共享的。

實現9

var Timer = (function(){
    var items = {};
    function begin(id,count){
        var obj = {};
        obj["id"] = id;
        obj["count"] = count;
        Timer.show(obj)();
        obj["timer"] = setInterval(Timer.show(obj),1000);//注意這裡不是Timer.show(this)();
        Timer.items[id] = obj;
    };
    function show(obj){
        return function(){
            if(obj["count"] < 0){
                document.getElementById(obj["id"]).innerHTML = "over";
                clearInterval(obj["timer"]);
                return ;
            }
            document.getElementById(obj["id"]).innerHTML = obj["count"] ;
            obj["count"]--;
        }
    }
    return {
        items : items,
        begin : begin,
        show : show
    }
})()

點評:利用閉包,和上面的有點類似,但此全域性變數是DOM樹完成後就立即生成的,它擁有以下三個方法items : begin 與 show。許多類庫也採用此方式實現,不過它們稱之為名稱空間而已……

實現10

function Timer(id){
    this.element = document.getElementById(id);
    this.timer = null;
    this.count = 0;
}
Timer.prototype = {
    begin : function(count){
        this.count = count;
        this.show();
        var _this = this;
        this.timer = setInterval(function(){_this.show();}, 1000);
    }
    ,
    show : function(){
        this.element.innerHTML = this.count < 0 ? clearInterval(this.timer) || "over" : this.count--;
    }
}

點評:這是實現3的改進版,改進了每呼叫一次show就遍歷DOM樹的缺點(document.getElementById),而是第一次找到的元素放入例項的成員。利用三元表示式與短路運算使得程式碼更加內斂。儲存this,免去每次都往上層的作用域尋找。

廣州vi設計http://www.maiqicn.com 辦公資源網站大全https://www.wode007.com

實現11

function Timer(id) {
    this.container = document.getElementById(id);
}

Timer.prototype = {
    constructor: Timer,
    begin: function(count) {
        var container = this.container;
        setTimeout(function() {
            container.innerHTML = count > 0 ? count-- : "over";
            if(count + 1) {
                setTimeout(arguments.callee, 1000);
            }
        }, 1000);
    }

};

點評:YUI風格的實現方式,該類庫大量使用arguments ,callee等內部屬性。現在Timer類的成員很小,內部私有屬性也很少,效能明顯比以上所有實現要好得多。