title: 實(shí)現(xiàn)通用的事件注冊(cè)方法
date: 2017-07-15
tags: [原生js]
categories: js
初稿:2017-07-15
更新: 2018-07-29
實(shí)現(xiàn)通用的事件注冊(cè)方法
為什么要討論事件注冊(cè)的兼容性袜匿?
由于歷史原因,不同的瀏覽器對(duì)事件注冊(cè)的實(shí)現(xiàn)方式有些許差異送讲。
事件注冊(cè)有哪些方法弄砍?
on+"event"
例如:onclick/onmouseover/onmouseenter/...支持最廣,筆者用的最多姑子,倘若要在一個(gè)元素上添加多次同一事件,此時(shí)就顯得無(wú)能為力了,以最后一次綁定的事件為準(zhǔn)怪与。
addEventListener
W3C 標(biāo)準(zhǔn)方法,功能也最強(qiáng)大缅疟,支持添加多個(gè)事件
//element.addEventListener(type,listener,useCapture);
obj.addEventListener('click', method1, false)
obj.addEventListener('click', method2, false)
obj.addEventListener('click', method3, false)
執(zhí)行順序?yàn)?method1->method2->method3分别,第三個(gè)參數(shù)是指以“冒泡”還是“捕獲”的標(biāo)準(zhǔn)綁定事件,一般為 false(冒泡).
并且可以使用 removeEventListener() 方法移除由 addEventListener()方法添加的事件句柄存淫。
注意: 如果要移除事件句柄耘斩,addEventListener() 的執(zhí)行函數(shù)必須使用外部函數(shù),如上實(shí)例所示 (method1/2/3)桅咆。
匿名函數(shù)括授,類似 "window.removeEventListener("event", function(){ myScript });" 該事件是無(wú)法移除的。
如果瀏覽器不支持 removeEventListener() 方法岩饼,你可以使用 detachEvent() 方法實(shí)現(xiàn)荚虚。
var x = window.getElementById('myDIV')
if (x.removeEventListener) {
// // 所有瀏覽器,除了 IE 8 及更早IE版本
x.removeEventListener('mousemove', myFunction)
} else if (x.detachEvent) {
// IE 8 及更早IE版本
x.detachEvent('onmousemove', myFunction)
}
attachEvent
IE 家的方法籍茧,火狐與其他家瀏覽器都不支持,attachEvent——兼容:IE7版述、IE8;不兼容 firefox寞冯、chrome渴析、IE9、IE10吮龄、IE11俭茧、safari、opera.盡量不要用螟蝙,支持綁定多個(gè)事件恢恼,與 addEventListener()執(zhí)行順序相反,即 method3->method2->method1
進(jìn)行兼容性處理
1.簡(jiǎn)單通過 if 判斷
if (window.addEventListener) {
//功能最強(qiáng)大
div.addEventListener('click', function() {
alert('hello,world')
})
} else if (window.attachEvent) {
//非標(biāo)準(zhǔn)特性 盡量不要使用
div.attachEvent('click', function() {
alert('hello,world')
})
} else {
//支持最好
div['onclick'] = function() {
alert('hello,world')
}
}
2.封裝成函數(shù)
function registeEvent(elem, type, handler, useCapture) {
if (window.addEventListener) {
//功能最強(qiáng)大
elem.addEventListener(type, handler, useCapture)
} else if (window.attachEvent) {
//非標(biāo)準(zhǔn)特性 盡量不要使用
elem.attachEvent(type, handler)
} else {
//支持最好
elem['on' + type] = handler
}
}
到這里胰默,我們每次注冊(cè)事件時(shí)都通過 registeEvent 注冊(cè)场斑,很明顯漓踢,每次注冊(cè)都要判斷瀏覽器的能力是否支持,每一次都要檢測(cè)漏隐,這不是我們想要的喧半。
3. 使用立即執(zhí)行函數(shù)進(jìn)行優(yōu)化
var registeEvent = (function createEventRegister() {
if (window.addEventListener) {
return function(elem, type, handler, useCapture) {
elem.addEventListener(type, handler, useCapture)
}
} else if (window.attachEvent) {
return function(elem, type, handler) {
elem.attachEvent(type, function() {
handler.call(elem, window.event) //注:attachEvent內(nèi)部this指向window而不是觸發(fā)對(duì)象,使用call方法修改this
})
}
} else {
return function(elem, type, handler) {
elem['on' + type] = handler
}
}
})()
此后只用通過 registeEvent 方法注冊(cè)事件即可,且只在最開始的時(shí)候進(jìn)行一次能力檢測(cè).
registeEvent(div, 'click', function() {
alert('hello,world')
})
但是此時(shí)依舊存在一個(gè)缺點(diǎn)青责,如果我們從頭到尾沒有綁定過事件挺据,即使用 registeEvent 函數(shù),那么立即執(zhí)行函數(shù)白白執(zhí)行了一次完全是多余的(或許有些吹毛求疵)脖隶,我們可以使用惰性載入函數(shù)來(lái)進(jìn)行優(yōu)化扁耐。
4. 惰性載入函數(shù)方案
此時(shí) registerEvent 依然被聲明為一個(gè)普通函數(shù),在函數(shù)里依然有一些分支判斷产阱。但是在第一次進(jìn)入條件分支之后婉称,在函數(shù)內(nèi)部會(huì)重寫這個(gè)函數(shù),重寫之后的函數(shù)就是我們期望的 registerEvent 函數(shù)构蹬,在下一次進(jìn)入 registerEvent registerEvent 函數(shù)里不再存在條件分支語(yǔ)句:
var registeEvent = function createEventRegister() {
if (window.addEventListener) {
registeEvent = function(elem, type, handler, useCapture) {
elem.addEventListener(type, handler, useCapture)
}
} else if (window.attachEvent) {
registeEvent = function(elem, type, handler) {
elem.attachEvent(type, function() {
handler.call(elem, window.event) //注:attachEvent內(nèi)部this指向window而不是觸發(fā)對(duì)象,使用call方法修改this
})
}
} else {
registeEvent = function(elem, type, handler) {
elem['on' + type] = handler
}
}
}
附上一些兼容性解決方案 EventUtil:
var EventUtil = {
addEvent: function(element, type, handler) {
// 添加綁定
if (element.addEventListener) {
// 使用DOM2級(jí)方法添加事件
element.addEventListener(type, handler, false)
} else if (element.attachEvent) {
// 使用IE方法添加事件
element.attachEvent('on' + type, handler)
} else {
// 使用DOM0級(jí)方法添加事件
element['on' + type] = handler
}
},
// 移除事件
removeEvent: function(element, type, handler) {
if (element.removeEventListener) {
element.removeEventListener(type, handler, false)
} else if (element.datachEvent) {
element.detachEvent('on' + type, handler)
} else {
element['on' + type] = null
}
},
getEvent: function(event) {
// 返回事件對(duì)象引用
return event ? event : window.event
},
// 獲取mouseover和mouseout相關(guān)元素
getRelatedTarget: function(event) {
if (event.relatedTarget) {
return event.relatedTarget
} else if (event.toElement) {
// 兼容IE8-
return event.toElement
} else if (event.formElement) {
return event.formElement
} else {
return null
}
},
getTarget: function(event) {
//返回事件源目標(biāo)
return event.target || event.srcElement
},
preventDefault: function(event) {
//取消默認(rèn)事件
if (event.preventDefault) {
event.preventDefault()
} else {
event.returnValue = false
}
},
stoppropagation: function(event) {
//阻止事件流
if (event.stoppropagation) {
event.stoppropagation()
} else {
event.canceBubble = false
}
},
// 獲取mousedown或mouseup按下或釋放的按鈕是鼠標(biāo)中的哪一個(gè)
getButton: function(event) {
if (document.implementation.hasFeature('MouseEvents', '2.0')) {
return event.button
} else {
//將IE模型下的button屬性映射為DOM模型下的button屬性
switch (event.button) {
case 0:
case 1:
case 3:
case 5:
case 7:
//按下的是鼠標(biāo)主按鈕(一般是左鍵)
return 0
case 2:
case 6:
//按下的是中間的鼠標(biāo)按鈕
return 2
case 4:
//鼠標(biāo)次按鈕(一般是右鍵)
return 1
}
}
},
//獲取表示鼠標(biāo)滾輪滾動(dòng)方向的數(shù)值
getWheelDelta: function(event) {
if (event.wheelDelta) {
return event.wheelDelta
} else {
return -event.detail * 40
}
},
// 以跨瀏覽器取得相同的字符編碼王暗,需在keypress事件中使用
getCharCode: function(event) {
if (typeof event.charCode == 'number') {
return event.charCode
} else {
return event.keyCode
}
}
}
參考書籍:《JavaScript 高級(jí)程序設(shè)計(jì)第三版》《JavaScript 設(shè)計(jì)模式與開發(fā)實(shí)踐》
參考文章:手把手教你用原生 JavaScript 造輪子(1)——分頁(yè)器