首页 >web前端 >js教程 >javascript 浏览器兼容性事件处理机制

javascript 浏览器兼容性事件处理机制

WBOY
WBOY原创
2016-06-01 09:54:141177浏览

使用类库可以比较容易的解决兼容性问题.但这背后的机理又是如何呢? 下面我们就一点点铺开来讲.

首先,DOM Level2为事件处理定义了两个函数addEventListener和removeEventListener, 这两个函数都来自于EventTarget接口. 

<code class="language-javascript">element.addEventListener(eventName, listener, useCapture); 
element.removeEventListener(eventName, listener, useCapture); </code>

EventTarget接口通常实现自Node或Window接口.也就是所谓的DOM元素. 

那么比如window也就可以通过addEventListener来添加监听. 

<code class="language-javascript">function loadHandler() { 
console.log('the page is loaded!'); 
} 
window.addEventListener('load', loadHandler, false); </code>

移除监听通过removeEventListener同样很容易做到, 只要注意移除的句柄和添加的句柄引用自一个函数就可以了. 

<code class="language-javascript">window.removeEventListener('load', loadHandler, false); </code>

如果我们活在完美世界.那么估计事件函数就此结束了. 

但情况并非如此.由于IE独树一帜.通过MSDHTML DOM定义了attachEvent和detachEvent两个函数取代了addEventListener和removeEventListener. 

恰恰函数间又存在着很多的差异性,使整个事件机制变得异常复杂. 

所以我们要做的事情其实就转移成了.处理IE浏览器和w3c标准之间对于事件处理的差异性. 

在IE下添加监听和移除监听可以这样写 

<code class="language-javascript">function loadHandler() { 
alert('the page is loaded!'); 
} 
window.attachEvent('onload', loadHandler); // 添加监听 
window.detachEvent('onload', loadHandler); // 移除监听 </code>

从表象看来,我们可以看出IE与w3c的两处差异: 

1. 事件前面多了个"on"前缀. 
2. 去除了useCapture第三个参数. 

其实真正的差异远远不止这些.等我们后面会继续分析.那么对于现在这两处差异我们很容易就可以抽象出一个公用的函数 

<code class="language-javascript">function addListener(element, eventName, handler) { 
if (element.addEventListener) { 
element.addEventListener(eventName, handler, false); 
} 
else if (element.attachEvent) { 
element.attachEvent('on' + eventName, handler); 
} 
else { 
element['on' + eventName] = handler; 
} 
} 
function removeListener(element, eventName, handler) { 
if (element.addEventListener) { 
element.removeEventListener(eventName, handler, false); 
} 
else if (element.detachEvent) { 
element.detachEvent('on' + eventName, handler); 
} 
else { 
element['on' + eventName] = null; 
} 
} </code>

上面函数有两处需要注意一下就是: 

1. 第一个分支最好先测定w3c标准. 因为IE也渐渐向标准靠近. 第二个分支监测IE. 
2. 第三个分支是留给既不支持(add/remove)EventListener也不支持(attach/detach)Event的浏览器. 

性能优化 

对于上面的函数我们是运用"运行时"监测的.也就是每次绑定事件都需要进行分支监测.我们可以将其改为"运行前"就确定兼容函数.而不需要每次监测. 

这样我们就需要用一个DOM元素提前进行探测. 这里我们选用了document.documentElement. 为什么不用document.body呢? 因为document.documentElement在document没有ready的时候就已经存在. 而document.body没ready前是不存在的. 

这样函数就优化成 

<code class="language-javascript">var addListener, removeListener, 
/* test element */ 
docEl = document.documentElement; 
// addListener 
if (docEl.addEventListener) { 
/* if `addEventListener` exists on test element, define function to use `addEventListener` */ 
addListener = function (element, eventName, handler) { 
element.addEventListener(eventName, handler, false); 
}; 
} 
else if (docEl.attachEvent) { 
/* if `attachEvent` exists on test element, define function to use `attachEvent` */ 
addListener = function (element, eventName, handler) { 
element.attachEvent('on' + eventName, handler); 
}; 
} 
else { 
/* if neither methods exists on test element, define function to fallback strategy */ 
addListener = function (element, eventName, handler) { 
element['on' + eventName] = handler; 
}; 
} 
// removeListener 
if (docEl.removeEventListener) { 
removeListener = function (element, eventName, handler) { 
element.removeEventListener(eventName, handler, false); 
}; 
} 
else if (docEl.detachEvent) { 
removeListener = function (element, eventName, handler) { 
element.detachEvent('on' + eventName, handler); 
}; 
} 
else { 
removeListener = function (element, eventName, handler) { 
element['on' + eventName] = null; 
}; 
} </code>

这样就避免了每次绑定都需要判断. 

值得一提的是.上面的代码其实也是有两处硬伤. 除了代码量增多外, 还有一点就是使用了硬性编码推测.上面代码我们基本的意思就是断定.如果document.documentElement具备了add/remove方法.那么element就一定具备(虽然大多数情况如此).但这显然是不够安全. 

不安全的检测 

下面两个例子说明.在某些情况下这种检测不是足够安全的. 

<code class="language-javascript">// In Internet Explorer 
var xhr = new ActiveXObject('Microsoft.XMLHTTP'); 
if (xhr.open) { } // Error 
var element = document.createElement('p'); 
if (element.offsetParent) { } // Error </code>

如: 在IE7下 typeof xhr.open === 'unknown'. 详细可参考feature-detection 

所以我们提倡的检测方式是 

<code class="language-javascript">var isHostMethod = function (object, methodName) { 
var t = typeof object[methodName]; 
return ((t === 'function' || t === 'object') && !!object[methodName]) || t === 'unknown'; 
}; </code>

这样我们上面的优化函数.再次改进成这样

<code class="language-javascript">var addListener, docEl = document.documentElement; 
if (isHostMethod(docEl, 'addEventListener')) { 
/* ... */ 
} 
else if (isHostMethod(docEl, 'attachEvent')) { 
/* ... */ 
} 
else { 
/* ... */ 
} </code>

丢失的this指针 

this指针的处理.IE与w3c又出现了差异.在w3c下函数的指针是指向绑定该句柄的DOM元素. 而IE下却总是指向window. 

<code class="language-javascript">// IE 
document.body.attachEvent('onclick', function () { 
alert(this === window); // true 
alert(this === document.body); // false 
}); 
// W3C 
document.body.addEventListener('onclick', function () { 
alert(this === window); // false 
alert(this === document.body); // true 
}); </code>

这个问题修正起来也不算麻烦 

<code class="language-javascript">if (isHostMethod(docEl, 'addEventListener')) { 
/* ... */ 
} 
else if (isHostMethod(docEl, 'attachEvent')) { 
addListener = function (element, eventName, handler) { 
element.attachEvent('on' + eventName, function () { 
handler.call(element, window.event); 
}); 
}; 
} 
else { 
/* ... */ 
} </code>

我们只需要用一个包装函数.然后在内部将handler用call重新修正指针.其实大伙应该也看出了,这里还偷偷的修正了一个问题就是.IE下event不是通过第一个函数传递,而是遗留在全局.所以我们经常会写event = event || window.event这样的代码. 这里也一并做了修正. 

修正了这几个主要的问题.我们这个函数看起来似乎健壮了很多.我们可以暂停一下做下简单的测试, 测试三点

1. 各浏览器兼容 2. this指针指向兼容 3. event参数传递兼容. 

测试代码如下:

<code class="language-javascript">

<title> Event Test UseCase </title> 
<meta http-equiv="Content-Type" content="text/html; charset=gb2312"> 
 
 
<div id="odiv" style="width:200px;height:100px;background-color:red; text-align:center">娴</div></code>
声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn