浏览器发展到第四代时(IE4及 Netscape Communicator 4),浏览器开发团队遇到一个问题:页面的哪一个部分会拥有某个特定的事件?能够想象在一张纸上的一组同心圆,若是把手指放在圆心上,那么你的手指指向的不是一个圆,而是纸上的全部圆。即在点击一个按钮时,不只点击了按钮,也点击了整个页面。html
事件流描述的是从页面中接收事件的顺序。不过IE 和 Netscape 开发团队提出的想法差很少彻底相反。前端
IE的事件流是事件冒泡流,而 Netscape Communicator 的事件流是事件捕获流。程序员
IE的事件流叫作事件冒泡(event bubbling),即事件开始时是由最具体的元素接收,而后逐级向上传播到较为不具体的节点。浏览器
备注:全部现代的浏览器都支持事件冒泡,可是在具体实现上还有一些差异。IE5.5 以及更早的版本中的事件冒泡会跳过 html 元素,即从body直接到 document。而IE九、Firefox、Chrome和Safari则将事件一直冒泡到window对象。
函数
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>
最近在搞一个和前端程序员相关的公号,除了技术分享以外,也增长了对于职业发展、生活记录之类的文章,欢迎你们关注,一块儿聊天、吐槽,一块儿努力工做,认真生活!