上一次,了解了JS自定義事件。今天在DOM上進行事件方法擴展。
1.基于DOM擴展自定義方法(了解即可)
我們一起來添加一個addEvent方法
if (window.HTMLElement) {
// 使用原型擴展DOM自定義事件
HTMLElement.prototype.addEvent = function(type, fn, capture) {
var el = this;
if (window.addEventListener) {
el.addEventListener(type, function(e) {
fn.call(el, e);
}, capture);
} else if (window.attachEvent) {
el.attachEvent("on" + type, function(e) {
fn.call(el, e);
});
}
};
} else {
// 如果是不支持HTMLElement擴展的瀏覽器
// 通過遍歷所有元素擴展DOM事件
var elAll = document.all, lenAll = elAll.length;
for (var iAll=0; iAll<lenAll; iAll+=1) {
elAll[iAll].addEvent = function(type, fn) {
var el = this;
el.attachEvent("on" + type, function(e) {
fn.call(el, e);
});
};
}
}
HTMLElement 接口表示所有的 HTML 元素(nodeType==1)。以一個<p>標簽元素舉例,其向上尋找原型對象用過會是這樣:HTMLParagraphElement.prototype → HTMLElement.prototype → Element.prototype → Node.prototype → Object.prototype → null。上述代碼HTMLElement直接換成Element也是可以的,但是會讓其他元素(例如文本元素等)也擴展addEvent方法,有些浪費了。
通過上面的擴展,element上就有了addEvent()方法。我們可以像下面展示的那樣使用
<div id="content-wrap" class="content-wrap">這是pattyzzh的領地</div>
document.getElementById("content-wrap").addEvent("click", function() {
alert("歡迎光臨pattyzzh");
});
基于DOM擴展缺點有:缺少標準無規律、提高沖突可能性、性能以及瀏覽器支持。擴展名字任意命,很有可能就會與未來DOM瀏覽器本身支持的方法相互沖突;擴展無規律,很有可能出現A和B同名不同功能的擴展而造成沖突;IE6-7瀏覽器下所有擴展都要通過遍歷支持,其性能開銷可想而知;另外IE8對DOM擴展的支持并不完整,例如其支持Element.prototype,卻沒有HTMLElement.prototype.
2.偽DOM自定義事件
這里的“偽DOM自定義事件”是自己定義的一個名詞,用來區分DOM自定義事件的。例如jQuery庫,其是基于包裝器(一個包含DOM元素的中間層)擴展事件的,既與DOM相關,又不直接是DOM,因此,稱之為“偽DOM自定義事件”。
如果只考慮事件添加,我們的工作其實很簡單,根據支持情況,addEventListener與attachEvent方法分別添加事件即可:
addEvent: function(type, fn, capture) {
var el = this.el;
if (window.addEventListener) {
el.addEventListener(type, fn, capture);
} else if (window.attachEvent) {
el.attachEvent("on" + type, fn);
}
return this;
}
自定義事件添加容易,但是如何觸發它們呢?——考慮到自定義事件與瀏覽器行為無關,同時瀏覽器沒有直接的觸發事件的方法。
自定義事件的觸發
- 對于標準瀏覽器,其提供了可供元素觸發的方法:element.dispatchEvent()。不過,在使用該方法之前,我們還需要做其他兩件事,及創建和初始化。因此,總結說來就是:
document.createEvent()
event.initEvent()
element.dispatchEvent()
createEvent()方法返回新創建的Event對象,支持一個參數,表示事件類型
initEvent()方法用于初始化通過DocumentEvent接口創建的Event的值。支持三個參數:initEvent(eventName, canBubble, preventDefault). 分別表示事件名稱,是否可以冒泡,是否阻止事件的默認操作。
dispatchEvent(eventObj)就是觸發執行了.
舉個例子
$(dom).addEvent("sayHello", function() {
alert("Hello");
});
// 創建
var event = document.createEvent("HTMLEvents");
// 初始化
event.initEvent("sayHello",true, false);
// 觸發, 即彈出文字
dom.dispatchEvent(event);
- 對于IE瀏覽器,由于向下很多版本的瀏覽器都不支持document.createEvent()方法)。IE瀏覽器有不少自給自足的東西,例如下面要說的這個"propertychange"事件,顧名思義,就是屬性改變即觸發的事件。例如文本框value值改變,或是元素id改變,或是綁定的事件改變等等。
當我們添加自定義事件的時候,順便給元素添加一個自定義屬性即可。例如,我們添加自定義名為"sayHello"的自定義事件,順便我們可以對元素做點小手腳:
dom.listener = 0;
再順便把自定義事件fn塞到"propertychange"事件中:
dom.attachEvent("onpropertychange", function(e) {
if (e.propertyName == "listener") {
fn.call(this); //fn是事件處理函數
}
});
這個,當我們需要觸發自定義事件的時候,只要修改DOM上自定義的listener屬性的值即可:
dom.listener = Math.random(); // 值變成隨機數
此時就會觸發dom上綁定的onpropertychange事件,又因為修改的屬性名正好是"listener", 于是自定義的fn就會被執行。這就是IE瀏覽器下事件觸發實現的完整機制。
自定義事件的刪除
與觸發事件不同,事件刪除,各個瀏覽器都提供了對于的時間刪除方法,如removeEventListener和detachEvent。不過呢,對于IE瀏覽器,還要多刪除一個事件,就是為了實現觸發功能額外增加的onpropertychange事件:
dom.detachEvent("onpropertychange", event);
綜合
var $ = function(el) {
return new _$(el);
};
var _$ = function(el) {
this.el = (el && el.nodeType == 1)? el: document;
};
_$.prototype = {
constructor: _$,
addEvent: function(type, fn, capture) {
var el = this.el;
if (window.addEventListener) {
el.addEventListener(type, fn, capture);
var ev = document.createEvent("HTMLEvents");
ev.initEvent(type, capture || false, false);
if (!el["ev" + type]) {
el["ev" + type] = ev; //將自定義事件存儲在該元素屬性下,觸發時使用
}
} else if (window.attachEvent) {
el.attachEvent("on" + type, fn);
if (isNaN(el["cu" + type])) {
// 自定義屬性,用來間接觸發觸發自定義事件
el["cu" + type] = 0;
}
var fnEv = function(event) {
if (event.propertyName == "cu" + type) { fn.call(el); }
};
el.attachEvent("onpropertychange", fnEv);
if (!el["ev" + type]) {
el["ev" + type] = [fnEv];
} else {
el["ev" + type].push(fnEv); //同一事件的多個處理函數
}
}
return this;
},
fireEvent: function(type) {
var el = this.el;
if (typeof type === "string") {
if (document.dispatchEvent) {
if (el["ev" + type]) {
el.dispatchEvent(el["ev" + type]);
}
} else if (document.attachEvent) {
el["cu" + type]++;
}
}
return this;
},
removeEvent: function(type, fn, capture) {
var el = this.el;
if (window.removeEventListener) {
el.removeEventListener(type, fn, capture || false);
} else if (document.attachEvent) {
el.detachEvent("on" + type, fn);
var arrEv = el["ev" + type];
if (arrEv instanceof Array) {
for (var i=0; i<arrEv.length; i+=1) {
//這里還需要再次過濾,否則會刪除該事件所有處理函數(ie下)
//條件?這里提示:1.對象的比較是比較引用 2.el["ev" + type]里裝的fnEv而不是fn 這里就難辦了
el.detachEvent("onpropertychange", arrEv[i]);
}
}
}
return this;
}
};
測試
var fnClick = function(e) {
e = e || window.event;
var target = e.target || e.srcElement;
if (target.nodeType === 1) {
alert("點擊類型:" + e.type);
$(target).fireEvent("sayHello"); //觸發自定義事件
}
}, sayHello1 = function() {
alert("Hello 1");
}, sayHello2 = function() {
alert("Hello 2");
};
// 梅西圖片
var elImage = document.getElementById("myImg");
$(elImage)
.addEvent("click", fnClick)
.addEvent("sayHello", sayHello1)
.addEvent("sayHello", sayHello2);
// 刪除自定義事件按鈕
var elButton = document.getElementById("myBut");
$(elButton).addEvent("click", function() {
$(elImage)
.removeEvent("sayHello", sayHello1)
.removeEvent("sayHello", sayHello2);
alert("清除成功!");
});
html:
<div class="box" id="box">

<input type="button" value="點擊清除自定義事件 " id="myBut">
</div>
運行結果如下:
當我們點擊圖片,會出現下面三個彈窗
當我們點擊按鈕: