文章目錄
- 實現1
- 實現2
- 實現3
- 實現4
- 實現5
- 實現6
- 實現7
- 實現8
- 實現9
- 實現10
- 實現11
從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--; } } }
time1time2
開始
點評:採用最經典的建構函式來實作類別。好處是明快易懂,缺點是多個執行個體都是獨立的記憶體,資料與方法都不共用。而且還存在一個明顯的缺陷,計時完成之後,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--; } }}
time1time2
開始
點評:解決了實現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 time1 time2開始
點評:把show()弄成類的靜態方法,讓所有執行個體共用此方法。注意,show()是個currying函數,這就解決了各執行個體的資料互相汙染的問題。
實現4function 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--; }} time1time2
開始
點評:這次把show()做成原型方法,由於所有執行個體都是共用原型,當執行個體在其成員中找不到此方法,它就會沿著原型鏈往上找。這個運用了很經典的類實現方式——混合的建構函式 /原型方式。
實現5function 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 time1 time2開始
點評:這次把bigin()也做成原型方法。
實現6function 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--; } } } time1time2
開始
點評:把原型方法整到一個對象中去。
實現7function 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 time1 time2開始
點評:把原型方法整到建構函式中去,但千萬不要弄成這個樣子:
function Timer(id){ this.id = id; this.timer = null; this.count = 0; Timer.prototype= { begin:function(count){/*……*/}, show : function(obj){ /*……*/} }} 因為直接為prototype 賦值,會使得自動添加的 constructor 成員丟失!
實現8var 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"]--; } }}time1time2
開始
點評:這次採用字面量構造對象。Timer成了一個全域對象(當我們第一次調用begin方法才產生此全域對象),類似於window,產生執行個體方法不再是new,而是採用工廠方式的方法,每begin一次就產生一個執行個體,資料是獨立的,方法是共用的。
實現9var 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"] time1 time2開始
點評:利用閉包,和上面的有點類似,但此全域變數是DOM樹完成後就立即產生的,它擁有以下三個方法items : begin 與 show。許多類庫也採用此方式實現,不過它們稱之為命名空間而已……
實現10function 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--; }}time1time2
開始
點評:這是實現3的改進版,改進了每調用一次show就遍曆DOM樹的缺點(document.getElementById),而是第一次找到的元素放入執行個體的成員。利用三元運算式與短路運算使得代碼更加內斂。儲存this,免去每次都往上層的範圍尋找。
實現11function 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); }};time1time2
開始
點評:YUI風格的實現方式,該類庫大量使用arguments ,callee等內部屬性。現在Timer類的成員很小,內部私人屬性也很少,效能明顯比以上所有實現要好得多。