我们都知道,在不同的浏览器下,添加和移除事件处理程序方式有所相同,要想写出跨浏览器的事件处理程序,首先我们要了解不同的浏览器下处理事件处理程序的区别
在添加事件处理程序时addEventListener和attachEvent主要有以下几个区别
参数个数不相同,这个最直观,addEventListener有三个参数,attachEvent只有两个,attachEvent添加的事件处理程序只能发生在冒泡阶段,addEventListener第三个参数可以决定添加的事件处理程序是在捕获阶段还是冒泡阶段处理(我们一般为了浏览器兼容性都设置为冒泡阶段)
第一个参数意义不同,addEventListener第一个参数是事件类型(比如click,load),而attachEvent第一个参数指明的是事件处理函数名称(onclick,onload)
事件处理程序的作用域不相同,addEventListener的作用域是元素本身,this是指的触发元素,而attachEvent事件处理程序会在全局变量内运行,this是window,所以刚才例子才会返回undefined,而不是元素id
为一个事件添加多个事件处理程序时,执行顺序不同,addEventListener添加会按照添加顺序执行,而attachEvent添加多个事件处理程序时顺序无规律(添加的方法少的时候大多是按添加顺序的反顺序执行的,但是添加的多了就无规律了),所以添加多个的时候,不依赖执行顺序的还好,若是依赖于函数执行顺序,最好自己处理,不要指望浏览器去解决。
了解了这四点区别后我们可以尝试写一个浏览器兼容性比较好的添加事件处理程序方法
function addEvent(node, type, handler) {
if (!node) return false;
if (node.addEventListener) {
node.addEventListener(type, handler, false);
return true;
}
else if (node.attachEvent) {
node.attachEvent('on' + type, handler, );
return true;
}
return false;
}
这样,我们解决了两个问题
- 参数个数不同,现在是三个参数,
- 现在都在事件冒泡阶段触发
- 第二个问题也得以解决,如果是IE,我们给type添加上on
但是如果handler内有关于this操作的话,this指向依然是不同的,执行顺序的问题也还没有解决方案,这里需要我们自己注意,一般情况下,同一个对象不会添加很多事件处理程序。
*接下来,我们来尝试解决this指向问题:
function addEvent(node, type, handler) {
if (!node) return false;
if (node.addEventListener) {
node.addEventListener(type, handler, false);
return true;
}
else if (node.attachEvent) {
node.attachEvent('on' + type, function() { handler.apply(node); });
return true;
}
return false;
}
通过apply(),this指向了事件目标,但是新的问题又来了,我们这样等于添加了一个匿名的事件处理程序,无法用detachEvent取消事件处理程序,有很多解决方案,我们可以借鉴大师的处理方式,jQuery创始人John Resig是这样做的
function addEvent(node, type, handler) {
if (!node) return false;
if (node.addEventListener) {
node.addEventListener(type, handler, false);
return true;
}
else if (node.attachEvent) {
node['e' + type + handler] = handler; //handler中的this指向node 即事件目标
node[type + handler] = function() {
node['e' + type + handler](window.event);
};
node.attachEvent('on' + type, node[type + handler]); //调用handler函数,并且传入事件对象作为参数
return true;
}
return false;
}
在取消事件处理程序的时候
function removeEvent(node, type, handler) {
if (!node) return false;
if (node.removeEventListener) {
node.removeEventListener(type, handler, false);
return true;
}
else if (node.detachEvent) {
node.detachEvent('on' + type, node[type + handler]);
node[type + handler] = null;
}
return false;
}
这里很巧妙地利用了闭包,看起来很不错。
事件对象的不同属性方法
*DOM中的事件对象和IE中的事件对象有不同的属性/方法
首先是DOM中事件对象有不同的属性/方法:
属性/方法 | 类型 | 读/写 | 方法 |
---|---|---|---|
bubbles | Boolean | 只读 | 事件是否冒泡 |
cancelable | Boolean | 只读 | 事件是否冒泡 |
currentTarget | Element | 只读 | 事件处理程序当前处理元素 |
detail | Integer | 只读 | 与事件相关细节信息 |
eventPhase | Integer | 只读 | 事件处理程序阶段:1 捕获阶段,2 处于目标阶段,3 冒泡阶段 |
preventDefault() | Function | 只读 | 取消事件默认行为 |
stopPropagation() | Function | 只读 | 取消事件进一步捕获或冒泡 |
target | Element | 只读 | 事件的目标元素 |
type | String | 只读 | 被触发的事件类型 |
view | AbstractView | 只读 | 与事件关联的抽象视图,等同于发生事件的window对象 |
然后是IE中的:
属性/方法 | 类型 | 读/写 | 方法 |
---|---|---|---|
cancelBubble | Boolean | 读/写 | 默认为false,设置为true后可以取消事件冒泡 |
returnValue | Boolean | 读/写 | 默认为true,设为false可以取消事件默认行为 |
srcElement | Element | 只读 | 事件的目标元素 |
type | String | 只读 | 被触发的事件类型 |
虽然DOM和IE的event对象不同,但基于它们的相似性,我们还是可以写出跨浏览器的事件对象方案,可以不用担心浏览器的问题会影响我们功能的实现
function getEvent(e) {
return e || window.event;
}
function getTarget(e) {
return e.target || e.scrElement;
}
function preventDefault(e) {
if (e.preventDefault)
e.preventDefault();
else
e.returnValue = false;
}
function stopPropagation(e) {
if (e.stopPropagation)
e.stopPropagation();
else
e.cancelBubble = true;
}
*备注:关于事件相关的兼容
onclick-----------------------------------------都支持
attachEvent()-----------------------------------IE8
addEventListener()------------------------------IE9以前不支持
stopPropagation();preventDefault()--------------IE9以前不支持
returnValue-------------------------------------不支持preventDefault()方法取消默认行为时的解决方法
cancelBubble=true-------------------------------不支持stopPropagation()方法阻止冒泡时的解决方法