title: 实现通用的事件注册方法
date: 2017-07-15
tags: [原生js]
categories: js
初稿:2017-07-15
更新: 2018-07-29
实现通用的事件注册方法
为什么要讨论事件注册的兼容性?
由于历史原因,不同的浏览器对事件注册的实现方式有些许差异。
事件注册有哪些方法?
on+"event"
例如:onclick/onmouseover/onmouseenter/...支持最广,笔者用的最多,倘若要在一个元素上添加多次同一事件,此时就显得无能为力了,以最后一次绑定的事件为准。
addEventListener
W3C 标准方法,功能也最强大,支持添加多个事件
//element.addEventListener(type,listener,useCapture);
obj.addEventListener('click', method1, false)
obj.addEventListener('click', method2, false)
obj.addEventListener('click', method3, false)
执行顺序为 method1->method2->method3,第三个参数是指以“冒泡”还是“捕获”的标准绑定事件,一般为 false(冒泡).
并且可以使用 removeEventListener() 方法移除由 addEventListener()方法添加的事件句柄。
注意: 如果要移除事件句柄,addEventListener() 的执行函数必须使用外部函数,如上实例所示 (method1/2/3)。
匿名函数,类似 "window.removeEventListener("event", function(){ myScript });" 该事件是无法移除的。
如果浏览器不支持 removeEventListener() 方法,你可以使用 detachEvent() 方法实现。
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.尽量不要用,支持绑定多个事件,与 addEventListener()执行顺序相反,即 method3->method2->method1
进行兼容性处理
1.简单通过 if 判断
if (window.addEventListener) {
//功能最强大
div.addEventListener('click', function() {
alert('hello,world')
})
} else if (window.attachEvent) {
//非标准特性 尽量不要使用
div.attachEvent('click', function() {
alert('hello,world')
})
} else {
//支持最好
div['onclick'] = function() {
alert('hello,world')
}
}
2.封装成函数
function registeEvent(elem, type, handler, useCapture) {
if (window.addEventListener) {
//功能最强大
elem.addEventListener(type, handler, useCapture)
} else if (window.attachEvent) {
//非标准特性 尽量不要使用
elem.attachEvent(type, handler)
} else {
//支持最好
elem['on' + type] = handler
}
}
到这里,我们每次注册事件时都通过 registeEvent 注册,很明显,每次注册都要判断浏览器的能力是否支持,每一次都要检测,这不是我们想要的。
3. 使用立即执行函数进行优化
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内部this指向window而不是触发对象,使用call方法修改this
})
}
} else {
return function(elem, type, handler) {
elem['on' + type] = handler
}
}
})()
此后只用通过 registeEvent 方法注册事件即可,且只在最开始的时候进行一次能力检测.
registeEvent(div, 'click', function() {
alert('hello,world')
})
但是此时依旧存在一个缺点,如果我们从头到尾没有绑定过事件,即使用 registeEvent 函数,那么立即执行函数白白执行了一次完全是多余的(或许有些吹毛求疵),我们可以使用惰性载入函数来进行优化。
4. 惰性载入函数方案
此时 registerEvent 依然被声明为一个普通函数,在函数里依然有一些分支判断。但是在第一次进入条件分支之后,在函数内部会重写这个函数,重写之后的函数就是我们期望的 registerEvent 函数,在下一次进入 registerEvent registerEvent 函数里不再存在条件分支语句:
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内部this指向window而不是触发对象,使用call方法修改this
})
}
} else {
registeEvent = function(elem, type, handler) {
elem['on' + type] = handler
}
}
}
附上一些兼容性解决方案 EventUtil:
var EventUtil = {
addEvent: function(element, type, handler) {
// 添加绑定
if (element.addEventListener) {
// 使用DOM2级方法添加事件
element.addEventListener(type, handler, false)
} else if (element.attachEvent) {
// 使用IE方法添加事件
element.attachEvent('on' + type, handler)
} else {
// 使用DOM0级方法添加事件
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) {
// 返回事件对象引用
return event ? event : window.event
},
// 获取mouseover和mouseout相关元素
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) {
//返回事件源目标
return event.target || event.srcElement
},
preventDefault: function(event) {
//取消默认事件
if (event.preventDefault) {
event.preventDefault()
} else {
event.returnValue = false
}
},
stoppropagation: function(event) {
//阻止事件流
if (event.stoppropagation) {
event.stoppropagation()
} else {
event.canceBubble = false
}
},
// 获取mousedown或mouseup按下或释放的按钮是鼠标中的哪一个
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:
//按下的是鼠标主按钮(一般是左键)
return 0
case 2:
case 6:
//按下的是中间的鼠标按钮
return 2
case 4:
//鼠标次按钮(一般是右键)
return 1
}
}
},
//获取表示鼠标滚轮滚动方向的数值
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 高级程序设计第三版》《JavaScript 设计模式与开发实践》
参考文章:手把手教你用原生 JavaScript 造轮子(1)——分页器