DIV CSS 佈局教程網

 DIV+CSS佈局教程網 >> 網頁腳本 >> JavaScript入門知識 >> 關於JavaScript >> 最佳的addEvent事件綁定是怎樣誕生的
最佳的addEvent事件綁定是怎樣誕生的
編輯:關於JavaScript     
當我們編寫腳本的時候創建了交叉引用,例如如下代碼:
復制代碼 代碼如下:
window.onload = function () {
var x = document.getElementsByTagName('H3');
for (var i=0;i<x.length;i++)
{
x[i].onclick = openClose;
x[i].relatedElement = x[i].nextSibling; // simplified situation
x[i].relatedElement.relatedElement = x[i];
}
}

或者在函數中使用腳本語言最常見的閉句Closures的時候,IE都無法回收內存。而閉句在給DOM對象注冊事件處理器(event handler)的時候最為常用。Novemberborn提供了一些example可以讓你運行並切實感受到這個bug。
我最喜愛的QuirkMode 去年初意識到這個bug存在巨大隱患,覺得有必要呼吁廣大web開發者關注並竭力避免這個問題,於是舉辦了一個慈善邀請賽,鼓勵大家提交各自 addEvent/removeEvent 方案。並終於在去年10月下旬宣布了他們認為的勝利者:John Resig,讓John贏得勝利的代碼如下:
復制代碼 代碼如下:
function addEvent( obj, type, fn ) {
if ( obj.attachEvent ) {
obj['e'+type+fn] = fn;
obj[type+fn] = function(){obj['e'+type+fn]( window.event );}
obj.attachEvent( 'on'+type, obj[type+fn] );
} else
obj.addEventListener( type, fn, false );
}
function removeEvent( obj, type, fn ) {
if ( obj.detachEvent ) {
obj.detachEvent( 'on'+type, obj[type+fn] );
obj[type+fn] = null;
} else
obj.removeEventListener( type, fn, false );
}

QuirkMode 對選擇John為勝利者的解釋概括來說就是以上代碼最簡潔有效,在避免內存問題的同時還巧妙的保證了this關鍵字在ie的attachEvent中能正常工作。缺點當然還是存在:

不支持 Netscape 4 和 Explorer 5 Mac。(有可能國內的程序員會嗤之以鼻,但國外很強調廣泛的兼容性)
在 removeEvent 中遺漏了remove obj["e"+type+fn]。
總之不管怎麼說,簡單取勝。
結果一出,眾多參賽與評論者不服氣,很快又挑出了John的代碼的幾處毛病:

addEvent中本身就使用了閉句,所以沒有根本解決IE內存洩露的問題。
沒有解決同類型的事件可能被重復注冊而被IE重復執行的問題。
幾個高手於是提出了改進性的方案:
復制代碼 代碼如下:
/*
Original idea by John Resig
Tweaked by Scott Andrew LePera, Dean Edwards and Peter-Paul Koch
Fixed for IE by Tino Zijdel (crisp)
Note that in IE this will cause memory leaks and still doesn't quite function the same as in browsers that do support the W3C event model:
- event execution order is not the same (LIFO in IE against FIFO)
- functions attached to the same event on the same element multiple times will also get executed multiple times in IE
*/
function addEvent( obj, type, fn ) {
if (obj.addEventListener)
obj.addEventListener( type, fn, false );
else if (obj.attachEvent) {
obj["e"+type+fn] = fn;
obj.attachEvent( "on"+type, function() { obj["e"+type+fn](); } );
}
}
function removeEvent( obj, type, fn ) {
if (obj.removeEventListener)
obj.removeEventListener( type, fn, false );
else if (obj.detachEvent) {
obj.detachEvent( "on"+type, obj["e"+type+fn] );
obj["e"+type+fn] = null;
}
}

很明顯,雖然修正了John代碼的一些不足。但內存洩露依然存在,部分浏覽器依然不支持,還是無法避免ie重復注冊。另外根據注釋:當在同一個對象上注冊多個事件處理器的時候,IE與其他浏覽器的執行順序是不同的,這又是一個隱患。

幾天之後,一個被認為最嚴謹的方案由Dean Edwards 提出。Dean他的方案與眾不同:

不執行對象檢測(Object detection)
沒有調用 addeventListener/attachEvent 方法
保持this關鍵字的運行於正確的上下文環境
正確傳遞 event 對象參數
完全跨浏覽器至此(包括IE4和NS4)
不存在內存洩露
Dean的代碼如下:
復制代碼 代碼如下:
// written by Dean Edwards, 2005
// http://dean.edwards.name/function ;addEvent(element, type, handler) {
// assign each event handler a unique ID
// 為事件處理函數設定一個唯一值
if (!handler.$$guid) handler.$$guid = addEvent.guid++;
// create a hash table of event types for the element
if (!element.events) element.events = {};
// create a hash table of event handlers for each element/event pair
var handlers = element.events[type];
if (!handlers) {
handlers = element.events[type] = {};
// store the existing event handler (if there is one)
// 如果對象已經注冊有事件處理,那麼要保留下來,並保存為第一個
if (element["on" + type]) {
handlers[0] = element["on" + type];
}
}
// store the event handler in the hash table
handlers[handler.$$guid] = handler;
// assign a global event handler to do all the work
// 指派一個全局函數做統一的事件處理,同時避免了反復注冊
element["on" + type] = handleEvent;
};
// a counter used to create unique IDs
addEvent.guid = 1;function removeEvent(element, type, handler) {
// delete the event handler from the hash table
if (element.events && element.events[type]) {
delete element.events[type][handler.$$guid];
}
};function handleEvent(event) {
// grab the event object (IE uses a global event object)
event = event || window.event;
// get a reference to the hash table of event handlers
// 這裡的 this 隨 handlerEvent function 被觸發的source element 變化而變化
var handlers = this.events[event.type];
// execute each event handler
for (var i in handlers) {
//這樣寫才能保證注冊的事件處理函數中的 this 得到正確的引用,直接handlers[i]()是不行的
this.$$handleEvent = handlers[i];
this.$$handleEvent(event);
}
};

這段代碼相比之前就大了不少了,不過確實很精妙。可是這段代碼卻引入了其他的問題,比如無法處理事件處理函數的返回值,for..in循環可能因為 (Object.prototype)的錯誤應用而中斷等等...很快Dean推出一個"updated version"。

要做到最好真的好辛苦。

目前似乎Dean的最終版本是最全面的解決方案。不過就我個人意見,感覺有些吹毛求疵了。盡量使用浏覽器本身的實現和保持簡單是我一貫堅持的主張。但洋人這種嚴謹的態度,還是讓我深深敬佩。
XML學習教程| jQuery入門知識| AJAX入門| Dreamweaver教程| Fireworks入門知識| SEO技巧| SEO優化集錦|
Copyright © DIV+CSS佈局教程網 All Rights Reserved