事件流
事件流描述的是元素接收事件的顺序。
- 事件冒泡:事件从最具体/最内层的元素开始接受,向上传递至最外层的元素节点(document),IE的事件流。
- 事件捕获:最外层的节点最先接收到事件,逐层传递到最内层的节点。
DOM2级事件流
DOM2级规定事件流包括三个阶段:事件捕获阶段、处于目标阶段、事件冒泡阶段。处于目标阶段是指实际的事件目标接收到事件,再后续的事件处理阶段,它被看作是事件冒泡的一部分。
事件处理程序
HTML事件处理程序
通过指定HTML的特性,如onclick属性来为元素指定事件。在事件冒泡阶段被处理。
<input type="button" value="Click Me" onclick="alert('Clicked')" />
//此时,函数内部,this指向目标元素
<script type="text/javascript">
function showMessage(){
alert("Hello world!");
}
</script>
<input type="button" value="Click Me" onclick="showMessage()" />
缺点
- 加载时差问题:html元素出现时,可能js代码还未加载,此时如果用户执行了事件,则会报错。解决方法为,再执行函数中添加try…catch语句,捕获错误;
- JavaScript代码与HTML紧密耦合;
- 在不同的浏览器中的作用域链可能不同。
DOM0级事件处理程序
将函数赋值给一个事件处理程序属性。在事件冒泡阶段被处理。
var btn = document.getElementById("myBtn");
//函数中的this指向绑定的元素。
btn.onclick = function(){
alert(this.id);
};
//删除事件的方法
btn.onclick = null;
优点
- 被所有浏览器支持,具有跨浏览器优势。
缺点
- 为事件处理程序属性指定两个函数时,后添加的事件处理方法会覆盖前面添加的。
DOM2级事件处理程序
定义了addEventListener()和removeEventListener()两个方法,来添加或删除事件处理程序。接受三个参数:
- 要处理的事件名,如, ‘click’
- 作为事件处理程序的函数
- 在捕获阶段(true)还是冒泡阶段(false)调用事件处理程序
btn.addEventListener("click", function(){
//函数的作用域与元素的作用域一致
alert("Hello world!");
}, false);
注意
- 通过addEventListener()添加的方法只能通过removeEventListener()来移除。移除时,传入的三个参数必须一致。如果添加时第二个参数使用的是匿名函数,则无法移除。
优点
- 可以为同一元素的同一事件添加多个事件处理程序,按照事件的添加顺序触发。
缺点
- 不兼容IE9以下的浏览器,IE9及以后的可兼容。
IE事件处理程序
IE9以下不兼容DOM2级事件处理程序,但它实现了两个类似的方法:attachEvent()和detachEvent()。接受两个参数:
- 事件处理程序的名称,如 “onclick”
- 事件处理程序函数
btn.attachEvent("onclick", function(){
alert("Clicked");
});
注意
- attachEvent添加的事件只能在事件冒泡阶段执行
- attachEvent的第一参数为”onclick”,而addEventListener()的第一个参数为”click”
- attachEvent绑定的函数在全局作用域中运行,函数中的this指向window
- 可以通关过attachEvent为元素添加多个事件处理程序,但会按照与添加顺序相反的顺序执行
跨浏览器的事件处理程序
综合上述各事件处理程序的优缺点,实现跨浏览器的事件处理方法:
var EventUtil = {
//添加事件
addHandler: function(element, type, handler){
if (element.addEventListener){
element.addEventListener(type, handler, false);
} else if (element.attachEvent){
element.attachEvent("on" + type, handler);
} else {
element["on" + type] = handler;
}
},
//移除事件
removeHandler: function(element, type, handler){
if (element.removeEventListener){
element.removeEventListener(type, handler, false);
} else if (element.detachEvent){
element.detachEvent("on" + type, handler);
} else {
element["on" + type] = null;
}
}
};
事件对象
在触发DOM元素的某个事件时,会产生一个事件对象event,作为参数传递给事件响应函数。
DOM中的事件对象
使用DOM0级和DOM2级添加事件时,都会传递事件对象。该对象的主要属性如下:
属性/方法 | 说明 |
---|---|
type | 被触发的事件类型,如”click” |
target | 事件目标,如button元素 |
currentTarget | 事件处理程序当前正在处理事件的元素,该事件可能是从target元素冒泡传递过来的 |
preventDefault() | 阻止事件的默认行为 |
stopPropagation() | 阻止事件进一步捕获或冒泡 |
eventPhase | 调用事件的结算,1表示捕获,2表示处于目标,3表示冒泡 |
cancelable | 是否可以取消事件的默认行为,只有为true时才能调用preventDefault()函数 |
IE中的事件对象
IE中的事件对象取决于指定事件处理程序的方法。
如果使用DOM0级的方法只当事件处理程序,那么event对象时window对象的一个属性:
btn.onclick = function(){
var event = window.event;
alert(event.type);//'click'
}
如果使用attachEvent的方法绑定事件处理程序,则会传递一个event对象,此时,window.event与传入的event指向同一个对象,两者都可以使用。
IE中的event对象具有如下属性:
属性/方法 | 描述 |
---|---|
cancelBubble | 默认为false,设置为true时可以取消冒泡,相当于DOM中的stopPropagation()方法 |
returnValue | 默认为true,设置为false时可以取消默认行为,相当于DOM中的preventDefault()方法 |
srcElement | 事件目标,同DOM中的target |
type | 事件类型 |
跨浏览器的事件对象
var EventUtil = {
//添加事件处理程序
addHandler: function(element, type, handler){
//...
},
//获取事件对象
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;
}
},
//移除事件
removeHandler: function(element, type, handler){
//...
},
//阻止事件冒泡
stopPropagation: function(event){
if (event.stopPropagation){
event.stopPropagation();
} else {
event.cancelBubble = true;
}
}
};
事件代理
事件代理,又叫做事件委托,即利用浏览器的事件冒泡机制,只用指定一个事件,就可以管理某一类型的所有事件的方法。事件代理可以用于解决文档中事件处理程序过多的问题。
举个栗子,假如我们有以下HTML代码,我们需要实现的功能是在单机每个列表项的时候,弹出每个列表项中的文本内容。
<ul id="myLinks">
<li id="goSomewhere">Go somewhere</li>
<li id="doSomething">Do something</li>
<li id="sayHi">Say hi</li>
</ul>
按照传统的方法,我们需要为上述每个<li>
标签都添加一个单击事件。设想在列表项很多的情况下,这种方法是很不可取的,我们将会使用很多的代码用于添加事件处理程序。
而利用事件代理的思路是,我们只需要在<ul>
标签上添加一个事件处理程序就好,由于<li>
标签是<ul>
的子标签,所有发生在<li>
标签上的事件都可以冒泡到<ul>
标签上,被<ul>
标签捕获,并通过事件的event对象判断事件的target,并针对不同的target进行不同的处理。利用事件代理机制可以使用更少的时间,和空间来处理事件处理程序。