A、事件冒泡与事件捕获
当我们在Web页面单击某一个元素的时候,比如某个p元素。仔细想想,我们单击的不仅仅是这一个p元素,一同被单击的还有以该p为圆心的同心圆元素,比如元素的父,外层body、body的父元素html还有外层的document。事件在这些嵌套的元素之间的传播称为事件流。
1、事件冒泡
IE的事件流称为事件冒泡,事件从最具体的元素开始,逐级向上传播。我们使用DOM0添加的事件处理程序就是在事件冒泡阶段被处理的。例如:
window.onload = bubblingHandle; function bubblingHandle() { //内层p处理程序 document.getElementById("inner").onmousedown = function() { alert("inner p"); } //外层p处理程序 document.getElementById("outer").onmousedown = function() { alert("outerp"); } document.onmousedown = function() { alert("document"); } } -->
当点击内层的白色p时,会依次显示:
inner pouter pdocument
事件捕获
网景提出的事件流称为事件捕获,其与IE几乎相反。事件首先由最不具体的元素接收,然后逐级向具体节点传播。
B、DOM0级事件处理
事件,由WEB页面中发生的一些特定行为触发。比如在某个页面元素上按下鼠标左键,按下键盘某个按键,某对象获得或丢失焦点时均会触发对应的事件。JavaScript和HTML的交互就是通过事件来实现的。我们使用事件侦听器对事件进行“注册”,事件发生时便执行相应的代码。
DOM0级事件处理程序以其简单、跨浏览器支持的特点,至今仍为所有浏览器支持。
通过DOM0级方法指定事件处理程序事件处理程序中的this通过DOM0级方法删除事件处理程序
通过DOM0级方法指定事件处理程序
通过DOM0级方法指定事件处理程序方法很简单,首先取得一个要操作元素的引用,然后接将一个函数赋值给该元素的对应事件处理程序属性
。(每个元素包括window和document都拥有自己的事件处理程序属性
。)注意,这种方法添加的事件处理程序将在事件流的冒泡阶段被处理。
有关事件处理程序属性,有以下几点需要说明:
1、事件处理程序属性全部小写,以”on”开头,后面跟事件类型:
onclick //单击鼠标onload //图像或页面载入完成onmouseover //将鼠标移动到某元素上面onmousemove //移动鼠标onfocus //对象获得焦点
2、每个元素如img、a、input、form包括window和document都拥有自己的事件处理程序属性。如:
document.getElementById("btn1").onclick //btn1上单击鼠标document.getElementById("img1").onmouseover //鼠标移动到img1document.getElementById("img1").onmerror //img1图像无法载入
接下来,给事件处理程序属性赋值即可完成事件处理程序方法的指定。例如,当鼠标移动到”img1”上时,弹出对话框”This is a nice pic!”:
var pic1 = document.getElementById("img1");pic1.onmouseover = function() { alert("This is a nice pic!");};
特别注意:如果以上代码处于文档的底部,在页面刚刚加载时,我们将鼠标移动到img1上面。有可能由于代码尚未执行,不会弹出我们设定的对话框!如今,这个延迟已经十分短暂。
事件处理程序中的this
通过DOM0级方法指定的事件处理程序,属于元素方法
。因此,我们在事件处理程序中的this引用的是该元素!通过以下例子来说明:
...//省略
通过DOM0级方法删除事件处理程序
要删除事件处理程序,只需要将对应的事件处理程序属性设置为null即可:
pic1.onmouseover = null;
C、DOM2级事件处理
1、addEventListener与removeEventListener
目前,几乎所有的浏览器都支持DOM0事件模型,但鼓励开发人员使用新的DOM2模型。DOM2模型与DOM0有两个显著区别:
1、DOM2不依赖事件处理程序属性2、可以同时对对象的同一事件注册多个处理程序,它们按照注册顺序依次执行。
DOM2定义了2个方法:
addEventListener() //指定事件处理程序removeEventListener() //删除事件处理程序
所有DOM节点有包含这两个方法,这两个方法用法如下,它们都接收3个参数,第1个为要处理事件名(不含on),第2个事件处理函数,第3个布尔变量:
例如我们为按钮btn1的单击事件添加2个事件处理程序,事件处理程序在事件冒泡阶段被处理:
...
当单击btn1按钮时,会依次弹出对话框:
handle1!handle2!
我们可以用removeEventListener()方法来删除我们刚才指定的事件处理程序,注意参数要保持一致:
btn1.removeEventListener("click", handle2, false);
此时单击btn1按钮,只会显示handle1!。
要特别注意的是,如果我们使用匿名函数指定事件处理程序,便无法使用removeEventListener()方法删除事件处理程序:
btn1.addEventListener("click", function(){ alert("click!"); }, false); btn1.removeEventListener("click", function(){ alert("click!"); }, false); //无法取消!
这样是无法取消以上指定的事件处理程序的!因为上面addEventListener和removeEventListener中的2个事件处理函数虽然代码相同,实质上是2个不同的函数引用。
另外,强调一点,以上两个函数的第一个参数(要处理的事件名)是没有on前缀的。这一点和IE不同,后面会说明。
tips: IE9, Firefox, Safari, Chrome以及Opera均支持DOM2级事件处理程序。
DOM2事件处理程序中的this
DOM2事件处理程序和DOM0相同,它们的this都在其依附的元素作用域中运行。this的指代参考DOM0的示例。这里之所以要特别指出DOM2的this,是为了和IE事件处理程序进行区分。IE中事件处理程序this与事件指定方式有关。
D、IE事件处理程序及跨浏览器支持
attachEvent()与detachEvent()
IE并没有提供对W3C事件模型的支持,其实现了2个和DOM2模型类似的方法:
attachEvent()detachEvent()
这两个方法只接收2个参数:事件名称以及事件处理函数。由于IE8及更早版本只支持事件冒泡,这两个方法添加的事件处理程序会在事件冒泡阶段被执行。
和DOM2不同的是:
1、IE事件处理方法运行作用域为全局作用域,this指代window;2、第一个参数事件名以on为前缀;3、当为同一对象的相同事件指定多个处理程序时,执行顺序和DOM2相反,IE中以添加它们的相反顺序执行。
例如:
...
执行结果:
handle2!truehandle1!true
跨浏览器支持
虽然可以使用屏蔽浏览器差异的JS库,实际上,我们自己编写一个跨浏览器兼容的事件处理代码并不是一件困难的事情,同时更有利于我们对原生JavaScript的学习理解。我们使用一个习惯上称为EventUtil的对象来进行跨浏览器事件处理:
var EventUtil = { addEventHandler : function(element, eventType, handler) { if(element.addEventListener){ element.addEventListener(eventType, handler, flase); } else if(element.attachEvent) { element.attachEvent("on" + eventType, handler); } else { element["on" + eventType] = handler; } }, removeEventHandler : function(element, eventType, handler) { if(element.aremoveEventListener){ element.addEventListener(eventType, handler, flase); } else if(element.detachEvent) { element.attachEvent("on" + eventType, handler); } else { element["on" + eventType] = null; } }}
为了保证事件处理代码能够在大多数浏览器中一致地运行,我们这里只关注冒泡阶段。以上代码使用浏览器能力检测,首先检测是否支持DOM2级方法addEventListener和removeEventListener,如果支持则使用该方法;如果不支持该方法,检测是否是IE8级更早版本的attachEvent或detachEvent方法,若支持则使用该方法;如果对以上2种方法都不支持,则使用DOM0级方法。要注意,DOM0级对每个事件只能指定一个事件处理程序。
以上对象使用示例如下:
var btn1 = document.getElementById("btn1");var handle1 = function() { alert("handle1!" + "\n" + (this === window));};var handle2 = function() { alert("handle2!"+ "\n" + (this === window));};EventUtil.addEventHandler(btn1, "click", handler1);EventUtil.addEventHandler(btn1, "click", handler2);EventUtil.removeEventHandler(btn1, "click", handler2);
E、事件对象
在触发某个事件时,会产生一个event对象。该对象中包含与事件有关的信息。例如触发事件的元素、事件的类型、与特定事件相关的如鼠标位置信息等。
1、DOM事件对象2、IE事件对象与跨浏览器事件对象
1、DOM事件对象
不论使用DOM0级还是DOM2级方法指定事件处理程序,事件触发时都会自动将一个event对象传入事件处理程序,例如:
var btn1 = document.getElementById("btn1");btn1.onmouseover = function(evnt) { alert(evnt.type);}var handle = function(evnt) { alert(evnt.type);};btn1.addEventListener("click", handle, false);
以上是一个简单的event对象的示例。event对象中的type属性是一个只读字符串属性,其中包含着事件的类型。例如我们上例中的click和onmouseover。event对象中包含有大量的有关事件的属性和方法(例如event.stopPropagation()方法可用于停止事件在捕获或者冒泡阶段的继续传播,preventDefault()方法会取消阻止事件的行)在此就不一一列举了。其中常用的如下:
属性/方法 | 值类型 | 读写 | 描述 |
---|---|---|---|
currentTarget | Element | readonly | 事件处理程序当前正在处理的元素 |
target | Element | readonly | 事件的目标 |
type | String | readonly | 触发事件的类型 |
preventDefault | Function | readonly | 取消事件默认行为,如链接的默认行为就是被单击时跳转到href指定的url |
stopPropagation | Function | readonly | 取消事件进一步冒泡或捕获 |
2、IE事件对象与跨浏览器事件对象IE事件对象
在IE中,当使用DOM0级指定事件处理程序时,event对象被认为是window的一个属性,例如获取鼠标点击坐标的代码:
var mouseLoc = function() { var loc = "x: " + window.event.screenX + "\n" + "y: " + window.event.screenY; alert(loc);};
当使用attachEvent()方法指定事件处理程序时,event对象会被作为参数传入事件处理程序,我们将以上的代码重写:
var mouseLoc = function(event) { var loc = "x: " + event.screenX + "\n" + "y: " + event.screenY; alert(loc);};btn1.attachEvent("onclick", mouseLoc);
IE中event对象的相关属性方法:
属性/方法 | 值类型 | 读写 | 描述 |
---|---|---|---|
cancelBubble | Boolean | read/write | 默认为false,置为true时取消事件冒泡(同DOM中stopPropagation) |
returnValue | Boolean | read/write | 默认为true,设为false取消事件默认行为(同DOM中preventDefault) |
srcElement | Element | readonly | 事件目标 |
type | String | readonly | 事件类型 |
跨浏览器事件对象
解决跨浏览器问题的思路是一贯的,我们可以对浏览器进行能力检测,这里我们对上面的EventUtil对象进行扩展,对我们学习原生JS,这是一个很漂亮的对象:
var EventUtil = { addEventHandler : function(element, eventType, handler) { if(element.addEventListener){ element.addEventListener(eventType, handler, flase); } else if(element.attachEvent) { element.attachEvent("on" + eventType, handler); } else { element["on" + eventType] = handler; } }, removeEventHandler : function(element, eventType, handler) { if(element.aremoveEventListener){ element.addEventListener(eventType, handler, flase); } else if(element.detachEvent) { element.attachEvent("on" + eventType, handler); } else { element["on" + eventType] = null; } }, getEvent: function (event) { return event ? event : window.event; }, 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.cancelBubbles = true; } }, getRelatedTarget: function (event) { if (event.relatedTarger) { return event.relatedTarget; } else if (event.toElement) { return event.toElement; } else if (event.fromElement) { return event.fromElement; } else { return null; } }}