浏览器发展到第四代时(IE4及 Netscape Communicator 4),浏览器开发团队遇到一个问题:页面的哪一个部分会拥有某个特定的事件?能够想象在一张纸上的一组同心圆,若是把手指放在圆心上,那么你的手指指向的不是一个圆,而是纸上的全部圆。即在点击一个按钮时,不只点击了按钮,也点击了整个页面。html
事件流描述的是从页面中接收事件的顺序。不过IE 和 Netscape 开发团队提出的想法差很少彻底相反。浏览器
IE的事件流是事件冒泡流,而 Netscape Communicator 的事件流是事件捕获流。bash
IE的事件流叫作事件冒泡(event bubbling),即事件开始时是由最具体的元素接收,而后逐级向上传播到较为不具体的节点。函数
备注:全部现代的浏览器都支持事件冒泡,可是在具体实现上还有一些差异。IE5.5 以及更早的版本中的事件冒泡会跳过 html 元素,即从body直接到 document。而IE九、Firefox、Chrome和Safari则将事件一直冒泡到window对象。
ui
Netscape Communicator 团队提出的另外一种事件流叫作事件捕获。思想是不太具体的节点应该更早的接收到事件,而最具体的节点应该最后接收到事件。this
事件捕获的用意在于事件到达预约目标以前捕获它。spa
虽然 事件捕获 是Netscape Communicator 惟一支持的事件流模型,可是IE九、Firefox、Opera、Chrome和Safari目前也都支持这种事件流模型。code
尽管“DOM2级事件”规范要求事件应该从document对象开始传播,可是这些浏览器都是从window对象开始捕获事件的。orm
因为老版本的浏览器不支持,所以不多有人使用事件捕获。htm
“DOM2级事件”规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。首先发生的是事件捕获,这为截获事件提供了机会。而后是实际的目标接收到事件。最后一个阶段是冒泡阶段,能够在这个阶段对事件做出响应。
以下:
<input type="button" value="click" onclick="alert('hello')" />
复制代码
经过JavaScript指定事件处理程序的传统方式,就是将一个函数赋值给一个事件处理程序属性。这种为事件处理程序赋值的方法是在第四代Web浏览器中出现的,并且至今仍然为全部现代浏览器支持。一是简单,二是具备跨浏览器优点。
每一个元素都有本身的事件处理程序属性,这些属性一般所有小写,例如:onclick。将这种属性的值设置为一个函数,就能够制定事件处理程序。
以下例:
var btn = document.getElementById("myBtn");
btn.onclick = function(){
alert(this.id); // "myBtn"
}
btn.onclick = null; // 删除事件处理程序
复制代码
以这种方式添加的事件处理程序会在事件流的冒泡阶段被处理。
“DOM2级事件”定义了两个方法,用于处理指定和删除事件处理程序的操做:addEventListener()和removeEventListener()。都接受三个参数:要处理的事件名、做为事件处理程序的函数和一个布尔值。最后这个布尔值参数若是是true,表示在捕获阶段调用事件处理程序;若是是false,表示在冒泡阶段调用事件处理程序。默认是 false。
使用DOM2级方法添加事件处理程序的主要好处是能够添加多个事件处理程序。
var btn = document.getElementById("myBtn");
btn.addEventListener("click",function(){alert(this.id)}) // this 指的是元素自己
btn.addEventListener("click",function(){alert("hello word!"))})
复制代码
经过 addEventListener 添加的事件,只能经过 removeEventListener 移除掉,移除时的参数须要和传入的参数相同。这也意味着传入的匿名参数没法移除。所以尽可能避免使用匿名函数。
大多数状况下,都是把事件处理程序添加到事件流的冒泡阶段,这样能够最大限度的兼容各类浏览器。万不得已的时候,再添加到捕获阶段。
IE实现了与DOM中相似的两个方法,attachEvent() 和 detachEvent()。这两个方法接收两个参数,由于只支持冒泡。 与addEventListener 和 removeEventListener 不一样的是,接受的第一个参数,必须带on。如单击事件,为"onclick";还有添加多个事件的时候,此方法按照添加的顺序反向执行。
此方法与DOM0级方法的主要区别是在于事件处理程序的做用域。此方法的事件处理程序会在全局做用域中运行,其中的this为window
由于不一样浏览器对于事件的处理不同,因此能够手写一些事件兼容方法。以下:
var EventUtil = {
// 添加事件处理程序
addHandler: function(element, type, handler) {
if (element.addEventListener) { // DOM2级 事件处理程序,this 指向元素自己。按照添加的顺序正向执行
element.addEventListener(type, handler, false);
} else if (element.attachEvent) { // IE 事件处理程序,this 指向 window。按照添加的顺序反向执行
element.attachEvent("on" + type, handler);
} else { // DOM0级 事件处理程序。只能绑定一个事件处理程序
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;
}
},
// 获取 event 对象。window.event 为 IE 浏览器的获取方式
getEvent: function(event) {
return event ? event : window.event;
},
// 获取event的target。 event.srcElement 只对老版本的 IE 浏览器有效
getTarget: function(event) {
return event.target || event.srcElement;
},
// 取消事件的默认行为
preventDefault: function(event) {
if (event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false; // IE
}
},
// 阻止事件冒泡
stopPropagation: function(event) {
if (event.stopPropagation) {
event.stopPropagation();
} else {
event.cancelBubble = true; // IE
}
}
}
复制代码
备注:事件处理程序的做用域是根据指定他的方式肯定的,因此不能认为this始终等于时间目标。仍是使用 srcElement 保险。
以下例:
<button id="myBtn">event</button>
<script>
var btn = document.getElementById('myBtn');
btn.onclick = function (){
alert(window.event.srcElement === this); // true
}
btn.attachEvent('onclick',function(event){
alert(event.srcElement === this) // false
alert(this === window) // true
})
</script>
复制代码