在Dom Level1中没有定义事件模型,在Dom Level2中定义了事件模型一个较小的子集,在Dom Level3(2004)中事件才被完整定义。
由于IE还没有对于Dom Level1的完整支持,所以当前的浏览器主要存在IE和DOM两种不同的事件模型。这直接导致了事件处理成为JS跨浏览器编程中差异最大的部分。还好除 了IE之外,Mozilla FireFox、Opera、Safari都统一实现了Dom Level2定义的事件模型。
本文尝试总结关于事件处理的所有相关要点,包括添加删除事件、得到事件对象、得到触发事件的对象、捕获和冒泡事件流、终止事件流、阻止某个事件的默认行为等等。
一、添加事件的方式:
1.写在HTML元素标签内:
<div onclick="clk()" id="testDiv">click me!</div>
<script>
function clk(){
alert(1);
}
</script>
2.JS得到页面元素后直接指定:
<script>
var oDiv = document.getElementById("testDiv");
oDiv.onclick = clk;
//或者
oDiv.onclick = function(){
alert(1);
}
</script>
3.JS为一个元素的一个事件添加多个处理函数:
<script>
var oDiv = document.getElementById("testDiv");
//for IE
oDiv.attachEvent("onclick",clk);
oDiv.detachEvent("onclick",clk);
//for DOM(FireFox,Opera..)
oDiv.addEventListner("click",clk,false)
oDiv.removeEventListner("click",clk,false)
</script>
注意:
1.对于Dom中增删事件处理函数的第三个boolean参数稍后解释,一般情况下false就好了。
2.在第2、3种方法中,指定事件处理函数为clk而不能写成clk(),任何方法在任何位置其后加上()表示对这个方法的一次运行。这也造成了对于添加的事件处理方法无法添加参数,只能是一个无参数调用。
3.第3种方法中,IE的事件名称一"on"开头如"onclick",而Dom的事件类型没有如"click"。
4. 另外强调一点,使用document.getElementById("testDiv");这样的语句最好要在页面文档完全载入之后 (window.onload内,后续方法掉用内),至少要在这个Div的定义之下,浏览器对于页面文件的解释顺序这里不详细叙述。只是强调一下严重性, 例如这段代码,<div id="testDiv"><script>var oDiv = document.getElementById("testDiv");</script></div>对于IE来讲是致命 的,很可能你会得到一个系统进程错误,然后所有IE窗口消失。
二、如何得到事件对象
对于IE:事件对象是全局对象,window.event就可以得到这个对象
对于Dom:事件根据添加事件处理函数的方法不同而略有区别。
相对上面提到的第2,3中添加事件的方法,浏览器会隐式自动把event作为事件处理函数传给处理函数,通过arguments[0]可以得到。
而 对于第1种方法需要这样写<div onclick="clk(event)" id="testDiv">click me!</div>需要将event作为clk的一个参数传回来,为了和其他事件添加保持一致处理方便,建议将其作为第一个参数。
这样我们就可以通过一句话来处理浏览器差异:
var oEvent = window.event?window.event:arguments[0];
三、如何得到触发事件的元素
当我们获得事件对象oEvent之后我们希望得到触发这个事件的页面元素
对于IE:oEvent.srcElement
对于DOM:oEvent.target
同样写成一句话:oSrc = oEvent.srcElement?oEvent.srcElement:oEvent.target;
虽然很简单,这里还是需要有些细节需要特别注意的:
对于这样的一段HTML<div onclick="clk(event)" id="outerDiv"><div id="innerDiv">testSrc</div></div>
虽然onclick事件是attach到outerDiv上的,但是当你点击文字"testSrc"触发事件并通过上面那句话得到的oSrc并不是outerDiv,而是实际触发的对象。
更特别的是,对于实际触发事件的对象在IE和DOM两种模型下还有所不同,因为Dom标准中文本节点可以触发事件的而IE不可以,所以在IE下oSrc是 innerDiv(oSrc.nodeType==1),而在Dom下oSrc是内容为"testDiv"的文本节点(oSrc.nodeType== 3)。
这会造成一定的困扰,当你的目标想要得到outerDiv时,建议使用这种写法:<div onclick="clk(event,this)" id="outerDiv"><div id="innerDiv">testSrc</div></div>这样,你甚至可以不去理会event对象,而事件处 理函数传入的this即是指向outerDiv的指针。
当然这只是针对第1种添加事件的方法而言,而针对不可以传入参数的第2,3种添加事件方法则没有什么好办法,一直向上查找parentNode直到找到为止吧。
这里可以看出三种添加事件的方法是各有短长的,根据实际情况来选择应用吧。
四、事件对象还包含哪些信息
这块儿的东西我最不喜欢写,东西挺多浏览器差异大我记住的很少。
把我用比较多的列出来,而且基本上这些各个浏览器是一致的。
oEvent.type 事件类型 click、mouseover等等
oEvent.clientX oEvent.clientY 鼠标事件触发的坐标(相对于浏览器窗口)
oEvent.screenX oEvent.screenY 鼠标时间触发的坐标(相对于屏幕)
oEvent.keyCode 键盘事件按键代码
oEvent.altKey oEvent.ctrlKey oEvent.shiftKey 事件发生时alt、ctrl、shift键是否按下
如果您想全面了解事件一共有多少种,每种是做什么的,每个事件内部包含哪些对象,请查看帮助手册或者这位仁兄的文章。
五、事件流
只有事件是不够的,一个点击触发的事件有多个,当你点击了一个链接,相当于同时点击了他所在的div,也相当于点击了整个body。
事件流即各个事件触发的顺序,这又是IE和Dom有着很大差异的地方。
从一个测试页面开始吧:
<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01//EN' 'http://www.w3.org/TR/html4/strict.dtd'>
<html>
<head>
<style>.bd{border:1px orange solid}</style>
<SCRIPT>
function addEvent = function(o,c,h){
if(o.attachEvent){
o.attachEvent('on'+c,h);
}else{
o.addEventListener(c,h,false);
}
return true;
};
function bol(){
addEvent(document.getElementById("di"),"click",ai);
addEvent(document.getElementById("dm"),"click",am);
addEvent(document.getElementById("do"),"click",ao);
}
function ai(){
alert("i")
oe=window.event?window.event:arguments[0];
/*try{
oe.cancelBubble = true;
}catch(e){
oe.stopPropagation();
}*/
ot = oe.target?oe.target:oe.srcElement;
alert(ot.id)
}
function am(){
alert("m")
oe=window.event?window.event:arguments[0];
ot = oe.target?oe.target:oe.srcElement;
alert(ot.id)
}
function ao(){
alert("o")
oe=window.event?window.event:arguments[0];
ot = oe.target?oe.target:oe.srcElement;
alert(ot.id)
}
</SCRIPT>
</head>
<body onload="bol()">
<div id="do" class=bd>
<div id="dm">
<div id="di" class=bd style="margin:50px">
aa
</div>
</div>
</div>
</body>
</html>
对于IE事件的触发是自下而上单向的冒泡型的事件流,页面里先触发di->dm->do->...
对于Dom的事件模型是自上而下有自下而上的称为捕获型的事件流,页面里触发顺序为...->do->dm->di->(txt)->(txt)->di->dm->do...
Dom 事件模型中事件可以定义在捕获阶段也可以定义在冒泡阶段,这个就是由addEventListener方法的第三个参数决定的,true代表在捕获阶段触 发,false代表在冒泡阶段触发。大家可以尝试把页面的addEventListener方法第三个参数改成true看下ie和firefox的不同效 果。
需要强调的是Dom事件模型中只有前面提到的第三种添加事件的方法可以增删捕获阶段事件处理函数,前两种方法都默认指定冒泡阶段的事件, 所以大家在之前开发中很少会注意到这些差异。而且既然IE没有支持,基本上没有存在只支持Firefox网页的必要,所以捕获阶段的事件只能说看上去不 错。
从这个测试页面我们还可以得到几点结论:
1.id==dm的div,在页面上并没有展现,边框都没,但是点击他的子节点仍然会触发他的onclick事件。
2.addEvent方法是一个简易的去除增删事件处理函数差异的方法,我们可以开发一些服务方法来统一事件处理。
3. 事件流的描述中我使用了"...",代表继续向上或向下触发的元素事件,这个也是比较麻烦的地方,IE的不同版本都不同,有div->body- >document的还有div->html->body->document的,只要记住不要给html元素添加事件处理函数 就好了。
六、停止事件流
事件流存在,势必需要一种方式能够让事件流打住,阻止继续冒泡。
在IE中:oEvent.cancelBubble = true;
在Dom中:oEvent.stopPropagation();
把上面的测试页注掉的代码打开就可以看到效果
七、阻止事件默认动作
一个动作的触发事件的同时往往带有其目的,比如按键代表输入字符,点击链接代表去链接的地址等等。
事件发生在这些默认动作执行前,希望检查只有满足一定要求才进行默认动作,这需要一种方式能够阻止事件默认动作。
有两种方式
第1种写在JS中
对于IE:oEvent.returnValue=false;
对于Dom:oEvent.preventDefault();
第2种方式针对前面介绍的第1种添加事件的方式:
<a href="http://yahoo.cn" onclick="return clk()">yahoo</a>
<script>
function clk(){
if(Math.random()>0.5){
return false;
}else{
return true;
}
}
</script>
在事件处理函数调用前加了return,而根据事件处理函数return值来决定是否进行默认动作。
另外,很多网页这么写onclick="javascript:clk()";这样的做没有必要,因为不支持js的浏览器同样不会认,支持js的浏览器不用写也一样。想来可能和vbscript有关吧,谁非要在一个页面用两种script呢。
八、JS模拟点击
IE下面:
oDiv=document.getElementById("testDiv");
oDiv.click();