javascript打造跨浏览器事件处理机制[Blue-Dream出品]

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

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

复制代码 代码如下:

element.addEventListener(eventName, listener, useCapture);
element.removeEventListener(eventName, listener, useCapture);

EventTarget接口通常实现自Node或Window接口.也就是所谓的DOM元素.
那么比如window也就可以通过addEventListener来添加监听.
复制代码 代码如下:

function loadHandler() {
console.log('the page is loaded!');
}
window.addEventListener('load', loadHandler, false);

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

如果我们活在完美世界.那么估计事件函数就此结束了.
但情况并非如此.由于IE独树一帜.通过MSDHTML DOM定义了attachEvent和detachEvent两个函数取代了addEventListener和removeEventListener.
恰恰函数间又存在着很多的差异性,使整个事件机制变得异常复杂.
所以我们要做的事情其实就转移成了.处理IE浏览器和w3c标准之间对于事件处理的差异性.

在IE下添加监听和移除监听可以这样写
复制代码 代码如下:

function loadHandler() {
alert('the page is loaded!');
}
window.attachEvent('onload', loadHandler); // 添加监听
window.detachEvent('onload', loadHandler); // 移除监听

从表象看来,我们可以看出IE与w3c的两处差异:
1. 事件前面多了个"on"前缀.
2. 去除了useCapture第三个参数.
其实真正的差异远远不止这些.等我们后面会继续分析.那么对于现在这两处差异我们很容易就可以抽象出一个公用的函数
复制代码 代码如下:

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;
}
}

上面函数有两处需要注意一下就是:
1. 第一个分支最好先测定w3c标准. 因为IE也渐渐向标准靠近. 第二个分支监测IE.
2. 第三个分支是留给既不支持(add/remove)EventListener也不支持(attach/detach)Event的浏览器.

性能优化
对于上面的函数我们是运用"运行时"监测的.也就是每次绑定事件都需要进行分支监测.我们可以将其改为"运行前"就确定兼容函数.而不需要每次监测.
这样我们就需要用一个DOM元素提前进行探测. 这里我们选用了document.documentElement. 为什么不用document.body呢? 因为document.documentElement在document没有ready的时候就已经存在. 而document.body没ready前是不存在的.
这样函数就优化成
复制代码 代码如下:

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;
};
}

这样就避免了每次绑定都需要判断.
值得一提的是.上面的代码其实也是有两处硬伤. 除了代码量增多外, 还有一点就是使用了硬性编码推测.上面代码我们基本的意思就是断定.如果document.documentElement具备了add/remove方法.那么element就一定具备(虽然大多数情况如此).但这显然是不够安全.
不安全的检测
下面两个例子说明.在某些情况下这种检测不是足够安全的.
复制代码 代码如下:

// In Internet Explorer
var xhr = new ActiveXObject('Microsoft.XMLHTTP');
if (xhr.open) { } // Error
var element = document.createElement('p');
if (element.offsetParent) { } // Error

如: 在IE7下 typeof xhr.open === 'unknown'. 详细可参考feature-detection
所以我们提倡的检测方式是
复制代码 代码如下:

var isHostMethod = function (object, methodName) {
var t = typeof object[methodName];
return ((t === 'function' || t === 'object') && !!object[methodName]) || t === 'unknown';
};

这样我们上面的优化函数.再次改进成这样
复制代码 代码如下:

var addListener, docEl = document.documentElement;
if (isHostMethod(docEl, 'addEventListener')) {
/* ... */
}
else if (isHostMethod(docEl, 'attachEvent')) {
/* ... */
}
else {
/* ... */
}

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

// 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
});

这个问题修正起来也不算麻烦
复制代码 代码如下:

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 {
/* ... */
}

我们只需要用一个包装函数.然后在内部将handler用call重新修正指针.其实大伙应该也看出了,这里还偷偷的修正了一个问题就是.IE下event不是通过第一个函数传递,而是遗留在全局.所以我们经常会写event = event || window.event这样的代码. 这里也一并做了修正.
修正了这几个主要的问题.我们这个函数看起来似乎健壮了很多.我们可以暂停一下做下简单的测试, 测试三点
1. 各浏览器兼容 2. this指针指向兼容 3. event参数传递兼容.

测试代码如下:

[Ctrl+A 全选 注:如需引入外部Js需刷新才能执行]

我们只需这样调用方法:
复制代码 代码如下:

addListener(o, 'click', function(event) {
this.style.backgroundColor = 'blue';
alert((event.target || event.srcElement).innerHTML);
});

可见'click' , this, event 都做到了浏览器一致性. 这样是不是我们就万事大吉了?
其实这只是万里长征的第一步.由于IE浏览器下和谐的内存泄露,使我们的事件机制要考虑的比上面复杂的多.
看下我们上面的一处修正this指针的代码
element.attachEvent('on' + eventName, function () {
handler.call(element, window.event);
});
element --> handler --> element 很容易的形成了个循环引用. 在IE下就内存泄露了.
解除循环引用
解决内存泄露的方法就是切断循环引用. 也就是将handler --> eleme

本页内容版权归属为原作者,如有侵犯您的权益,请通知我们删除。
例1: 复制代码 代码如下: script type="text/javascript" language="JavaScript" var startTime = new Date(); var endTime=startTime.getTime()+10*60*1000; var g_blinkswitch = 0; var g_blinktitle = document.title; function getRemainTime(){ var nowTime = new Date(); var nM

js获取元素外链样式的方法 - 2015-04-07 11:04:12

本文实例讲述了js获取元素外链样式的方法。分享给大家供大家参考。具体分析如下: 一般给元素设置行内样式,如div id="div1"/div。如要获取它的样式,即可document.getElementById("div1").style.width来获取或设置。但是如果样式是在外链link中的或者是页面的非行内样式,就获取不到了。 在标准浏览器中可以通过window.getComputedStyll(obj,null)[property]来获取外链样式,但是在ie浏览器中则是通过obj.currentS
利用javascript 写一个在页面点击加减按钮实现数字的累加。 简略的html大概如此。看得懂就好不要在意这些细节啊 input type="button" value="+" onclick="jia(this)" /label class="num"0/labelinput type="button" value="-" onclick="jian(this)" / 样子是这样的 javascript 代码如下 script type="text/javascript"function jia(a
Author: shaoyun Email: shaoyun (at) yeah.net Date: 2010-03-10 02:03 Blog: http://shaoyun.cnblogs.com/ 用Jquery实现,原始代码只支持IE,这里我改了一下,我的代码里面有三个版本的实现 第一个是通过读取XML构建,支持IE/firefox,chrome不支持,有兴趣的可以将读取XML的部分改成AJAX的方式,这样chrome支持就不成问题 第二个是采用Json数据格式构建,是我的第二次尝试改进 第三个与第
这是一个令人兴奋的小结,因为在这个小结中你终于能够看到你的第一个Demo的运行效果。 1.使用Egret工具运行游戏 运行Egret项目,我们需要一个已运行的HTTP服务器。在前面安装Egret的教程中,我们已经为大家推荐了一款HTTP服务器。现在我们来看一下如何使用我们egret提供的最简单的HTTP服务器来运行我们的项目。 和前面的教程一样,我们首相在终端中定位我们的项目,使用 cd 命令。 然后我们执行一个简单的命令来启动Egret的HTTP服务器,命令如下: egret startserver H
复制代码 代码如下: %@ page language="java" import="java.util.*" pageEncoding="UTF-8"% html head style type="text/css" *{ text-align: center; font-size: 12px; } table{ border-collapse: collapse; width: 40%; } table tr td{ border: red solid 1px; line-height:20px; }

jquery选择器使用详解 - 2015-04-07 11:04:09

jQuery 的选择器可谓之强大无比,这里简单地总结一下常用的元素查找方法 $("#myELement") 选择id值等于myElement的元素,id值不能重复在文档中只能有一个id值是myElement所以得到的是唯一的元素 $("div") 选择所有的div标签元素,返回div元素数组 $(".myClass") 选择使用myClass类的css的所有元素 $("*") 选择文档中的所有的元素,可以运用多种的选择方式进行联合选择:例如$("#myELement,div,.myclass") 层叠选择
JS脚本引用 复制代码 代码如下: script src="/scripts/Jquery.autocomplete/jquery.autocomplete.js" type="text/javascript"/script 样式引用 复制代码 代码如下: style type="text/css" media="all" @import url("/scripts/Jquery.autocomplete/css/jquery.autocomplete.css"); /style JS代码 复制代码 代码如

Dom在ajax技术中的作用说明 - 2015-04-07 11:04:09

今天我们来讲一下Dom在ajax技术中的作用.当你使用ajax向服务端发出请求,并返回数据到客户端时.你是怎么 将这些返回的数据显示到网页上的?毫无疑问的你使用的是Dom,利用Dom的各个指令.来向html中添加你想要显示的内容.例如:getElementById也或是getElementsByTagName查找元素 innerHTML显示内容?还是appendChild添加元素?当然创建一个不存在的元素也是可以的createElement可以轻松实现. 如果你想要创建够酷够炫的ajax效果.这些指令你都不

浅析Node.js查找字符串功能 - 2015-04-07 11:04:04

需求如下: 整个目录下大概有40几M,文件无数,由于时间久了, 记不清那个字符串具体在哪个文件,于是。强大,亮瞎双眼的Node.js闪亮登场: windows下安装Node.js和安装普通软件毫无差别,装完后打开Node.js的快捷方式,或者直接cmd,你懂的。 创建findString.js var path = require("path");var fs = require("fs");var filePath = process.argv[2];var lookingForString = pro