javaScript事件系统详解
一个有情怀的猴子🐒!
-----------------------------------------------------------------------------------------
本文目录:
1、什么是javascript事件系统?(以及发展史简介)
2、事件流
3、事件处理程序
4、事件对象
5、事件类型
6、内存和性能
7、模拟事件
8、总结
------------------------------------------------------------------------------------
1、什么是事件系统?(以及发展史简介)
JavaScript和HTML之间的交互是通过事件来实现的。事件,就是文档或浏览器窗口之间发生的一些交互瞬间。可以使用侦听器(或处理程序)来监听事件,以便事情发生时执行相应的代码。
一个完整的事件系统,通常存在以下三个角色:
- 事件对象,用于储存事件的状态。
- 事件源对象,当前事件在操作的对象,如元素节点,文档对象,window对象,XMLHttpRequest对象等。
- 事件监听器,当一个事件源生成一个事件对象时,它会调用相应的回调函数进行操作。在IE中,事件对象恒为全局属性window.event的分身。
通俗点讲,事件源对象相当于”当事人“,事件监听器相当于”监护人“,事件对象相当于”事故详情“。一个事件可以理解为,当事人出了点事,至于什么事情(被打了,还是被抢了)都记录在事故详情里,监护人根据事故详情得做出点反应(回调函数)。
说起来好像是挺简单,但其实不然。
事件最早是在IE3和Netscape Navigator2中出现的,当时是作为分担服务器运算负载的一种手段。
到IE4 和Navigator4发布时,这两种浏览器都提供了相似但不相同的API,而且这些API并存且经历了好几个版本更新。
再后来,DOM2级规范开始尝试以一种符合逻辑的方式来标准化DOM事件。
(IE9、Firefox、Opera、Safari和Chrome全都已经实现了”DOM2级事件“模块的核心部分。IE8是最后一个仍然使用其专有事件系统的主要浏览器。)
后话: 浏览器的事件系统相对比较复杂。尽管所有主要浏览器已经实现了”DOM2级事件“,但这个规范本身并没有涵盖所有的事件类型。浏览器对象模型(BOM)也支持一些事件,这些事件与文档对象模型(DOM)事件之间的关系并不十分清晰,因为BOM事件长期没有规范可以遵循(HMTL5后来给了详细说明)。随着DOM3级的出现,增强后的DOM事件API变的更加繁琐。使用事件有时相对简单,有时则非常复杂,难易程度会因为你的需求而不同。不过,有关事件的一些核心概念是一定要理解的。
有关JavaScript事件系统的发展史(javascript事件系统的发展史)
----------------------------------------------------------------------
2、事件流
当浏览器发展到第四代时(IE4及Netscape Communicator4),浏览器开发团队遇到了一个很有意思的问题。如下图所示,当我们点击目标事件的时候,不仅点击了自身,也点击了自身的容器,甚至点击了整个页面。如果这些元素都绑定了点击事件,那事件的执行顺序应该是怎样的?(暂时可以忽略图中的文字性描述)
事件流描述的就是从页面中接受事件的顺序。但有意思的是,IE和Netscape团队提出了几乎完全相反的事件流概念。IE的事件流是事件冒泡流,而Netscape Communicator的事件流是事件捕获流。
2.1 事件冒泡流 与 事件捕获流
事件冒泡流:事件开始由最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到较为不具体的节点(文档)。(由内及外)
事件捕获流:由不太具体的节点更早接收到事件,而最具体的节点应该最后接收到事件。(由外及内)
注意:
1)、所有现代浏览器都支持事件冒泡,但在具体实现中略有差别。IE5.5及更早版本中事件冒泡会跳过<html>元素(从body直接跳到document)。IE9、Firefox、Chrome、和Safari则将事件一直冒泡到window对象。
2)、IE9、Firefox、Chrome、Opera、和Safari都支持事件捕获。尽管DOM标准要求事件应该从document对象开始传播,但这些浏览器都是从window对象开始捕获事件的。
3)、由于老版本浏览器不支持,很少有人使用事件捕获。建议使用事件冒泡。有特殊情况再使用捕获。
2.2 DOM2级事件流
“DOM2级事件”规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段、事件冒泡阶段。如图所示:
捕获阶段:实际目标(<div>元素)在捕获阶段不会接收事件,意思是事件从 [ document->html->body ] 后就停止了。【1、2、3】
目标阶段:事件在目标元素上发生。但事件处理被看作是冒泡阶段的一部分。
冒泡阶段:从目标元素开始处理事件,一直传播到文档。也就是 [ div->body->html->document ]【4、5、6、7】
注意:
1、“DOM2级事件”规范明确要求捕获阶段不会涉及实际目标的事件,但IE9、Chrome、Firefox、Safari和Opera9.5及更高版本都会在捕获阶段触发实际目标上的事件。结果,目标对象上的事件就会执行两次!
2、并非所有的事件都会有冒泡阶段。但所有的事件都会经过捕获阶段和处于目标阶段。eg:跳过冒泡阶段的事件:获得输入焦点的focus事件和失去输入焦点的blur事件
附一张自己画的图:(注意:处于目标阶段事件发生,但事件的处理属于冒泡阶段)
目标对象上的事件执行两次,实例代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> </head> <style> #outer{ position: absolute; width: 400px; height: 400px; top:0; left: 0; bottom:0; right: 0; margin: auto; background-color: deeppink; } #middle{ position: absolute; width: 300px; height:300px; top:50%; left: 50%; margin-left: -150px; margin-top: -150px; background-color: deepskyblue; } #inner{ position: absolute; width: 100px; height:100px; top:50%; left:50%; margin-left: -50px; margin-top: -50px;; background-color: darkgreen; text-align: center; line-height: 100px; color:white; } #outer,#middle,#inner{ border-radius:100%; } </style> <body> <div id="outer"> <div id="middle"> <div id="inner"> click me! </div> </div> </div> <script> var outerCircle= document.getElementById("outer"); var middleCircle= document.getElementById("middle"); var innerCircle= document.getElementById("inner"); /* 事件捕获阶段 */ outerCircle.addEventListener("click", function () { console.log("outerCircle的click事件在捕获阶段被触发"); },true); middleCircle.addEventListener("click", function () { console.log("middleCircle的click事件在捕获阶段被触发"); },true); /* 按照DOM2规范,应该不会触发该事件。但事实是触发了 */ innerCircle.addEventListener("click", function () { console.log("innerCircle的click事件在捕获阶段被触发"); },true); /* 处于目标阶段 ,事件的发生和处理,不过处理属于冒泡阶段 */ /* 事件冒泡阶段 */ innerCircle.addEventListener("click", function () { console.log("innerCircle的click事件在冒泡阶段被触发"); },false); middleCircle.addEventListener("click", function () { console.log("middleCircle的click事件在冒泡阶段被触发"); },false); outerCircle.addEventListener("click", function () { console.log("outerCircle的click事件在冒泡阶段被触发"); },false); </script> </body> </html>
3、事件处理程序
事件就是用户或浏览器自身执行的某种动作。如 click、load 和 mouseover、mousedown 等
响应某个事件的函数叫做事件处理程序(或事件侦听器)。
有时候也把为事件指定处理程序的方式叫做事件处理程序,不过概念无所谓了,理解就行。按照这个说法,click事件的事件处理程序是onclick,load事件的事件处理程序就是onload。为事件指定处理程序的方式有有好几种。如下图所示:
注意: 由于HTML事件处理程序中HTML和JavaScript紧密耦合,所以已被大多程序员摒弃
所谓跨浏览器事件处理程序,就是把HTML、DOM0、DOM2、IE的事件处理程序进行封装
3.1 HTML事件处理程序 addEventListener(type, listener[, useCapture]);
某个元素支持每种事件,都可以使用一个与相应事件处理程序同名的HTML特性来指定。这个特性的值应该是可以执行的JavaScript代码。
示例代码(3种):
<!-- 方法一 --> <input type="button" value="Click Me" onclick="alert('clicked!')" /> <!-- 方法二 如果单双引号不能叉开使用,必要时可以使用转义后的HTML语法字符 --> <input type="button" value="Click Me" onclick="alert("clicked!")" /> <!-- 方法三 --> <input type="button" value="Click Me" onclick="showMessage()" /> <script type="text/javascript"> function showMessage(){ alert('clicked!'); } </script>
其实事件每发生一次,就会创建一个封装着事件相关信息的函数,这个函数中有一个局部变量event,也就是事件对象(稍后介绍)。通过event变量,我们就可以直接访问事件对象,而不用自己定义,也不用从函数的参数列表中读取。同时,我们也可以通过这个事件对象获取目标元素。
获取目标元素3种方式,示例代码如下:
<!-- 方法一 IE9、Firefox、chrome、Opera、safari支持(IE8及其以下不支持) --> <input type="button" value="Click Me" onclick="console.log(event.target)" /> <!-- 方法二 主要是为了IE8以下兼容,同时其他高级浏览器也还支持 --> <input type="button" value="Click Me" onclick="console.log(event.srcElement)"/> <!-- 方法二 JavaScript中的this比较乱,如果不是很清楚,建议慎用--> <input type="button" value="Click Me" onclick="console.log(this)" />
HTML事件处理程序的缺点:
1、时差问题:用户可能在HTML元素一出现在页面上就触发事件,此时事件处理程序有可能尚不具备执行条件。解决办法,try-catch 。
2、耦合度问题:HTML代码与JavaScript代码紧密耦合。如果要更换事件处理程序,就要改动两个地方:HTML代码和JavaScript代码。
3.2 DOM0级事件处理程序
通过JavaScript指定事件处理程序,就是将一个函数赋值为一个事件处理程序属性(eg: 赋值给 onclick )。
以这种方式添加的事件,会在事件流的冒泡阶段被处理。
优点:所有浏览器支持,简单,跨浏览器支持
<input type="button" name="clicker" id="clicker" value="点击" /> <script type="text/javascript"> var clicker = document.getElementById("clicker"); clicker.onclick = function(){ console.log("点击了!"); } </script>
缺点:绑定事件不能累加,最后绑定的会覆盖之前的。(DOM2级事件处理程序解决了这个问题,稍后详解)
<input type="button" name="clicker" id="clicker" value="点击" /> <script type="text/javascript"> var clicker = document.getElementById("clicker"); clicker.onclick = function(){ aler("第一次点击!"); } clicker.onclick = function(){ alert("第二次点击!"); } </script>
只会弹出第二次点击,而不会显示第一次的,如下图所示:
也可以删除通过DOM0级方法指定的事件处理程序,就是将事件处理程序设置为null。
设置之后,再点击就不会有任何动作发生。方法如下:
clicker.onclick = null;
注意:
使用HTML事件处理程序指定的程序,可以被DOM0级事件处理程序覆盖,也可以以同样方式删除。
3.3 DOM2级事件处理程序
DOM2级事件定义了两个方法,用于处理和删除指定的事件处理程序。
添加事件:addEventListener()
/* * 参数意义 * target 目标元素 * type 表示监听事件类型的字符串。 * listener 事件的处理程序, * (listener 必须是一个实现了 EventListener 接口的对象或者函数.当所监听的事件类型触发时,会接收到一个事件通知对象(实现了 Event 接口的对象) * useCapture Boolean类型值,默认false,实现事件冒泡。若设置为true,实现事件捕获。 * */ target.addEventListener(type, listener[, useCapture]);
移除事件: removeEventListener().
/* * 参数意义 * target 目标元素 * type 一个字符串,表示需要移除的事件类型,如 "click"。 * listener 需要移除的 EventListener 函数(先前使用 addEventListener 方法定义的) * useCapture 指定需要移除的 EventListener 函数是否为事件捕获。如果无此参数,默认值为 false。 * */ target.removeEventListener(type, listener[, useCapture])
DOM2级事件处理程序的主要好处是可以添加多个事件处理程序。然后按顺序触发
<input type="button" name="btn" id="btn" value="button" /> <script type="text/javascript"> var btn = document.getElementById("btn"); btn.addEventListener("click", function(){ console.log('第一个注册事件执行了!'); }) btn.addEventListener("click", function(){ console.log('第二个注册事件执行了!'); }) </script>
通过addEventListener()添加的事件处理程序只能使用removeEventListener()来移除;
移除时传入的参数和添加时使用的参数相同。
这也意味着添加的匿名函数将无法移除。
<input type="button" name="btn" id="btn" value="button" /> <script type="text/javascript"> var btn = document.getElementById("btn"); btn.addEventListener("click", first, false) btn.addEventListener("click", function(){ console.log('第二个注册事件执行了!'); },false) //移除事件的参数与addEventListener时的参数相同 , btn.removeEventListener("click", first, false); //如果是匿名函数,将无法移除 btn.removeEventListener("click", function(){ console.log('第二个注册事件执行了!'); },false) function first(){ console.log('第一个注册事件执行了!'); } </script>
注意:
1、如果同一个监听事件分别为“事件捕获”和“事件冒泡”注册了一次,一共两次,这两次事件需要分别移除。两者不会互相干扰。
2、为最大限度的兼容各种浏览器,建议将事件处理程序添加到事件流的冒泡阶段
3、除非特殊需要,否则不建议在事件捕获阶段注册事件处理程序
3.3 IE事件处理程序
方法:注册事件处理程序 attachEvent() 、 移除事件处理程序 detachEvent()
语法:attached = target.attachEvent(eventNameWithOn, callback)
区别addEventListener:
1、attachEvent 是非标准的,addEventListener是标准的
2、attachEvent只有两个参数,第一参数是事件类型(带“on”的,比如click,要写成 “onclick”)、第二个参数写法与addEventListener相同,没有三个参数,IE8及以下版本也不支持事件捕获
3、事件处理程序中的作用域不同,也就是第二个参数中的this指向不同,DOM0、DOM2事件处理程序会在其元素的作用域内运行,this指向目标元素;而使用attachEvent()方法,事件处理程序会在全局作用域中运行,因此此处this指向window.
作用域示例代码 (IE8运行):
<input type="button" value="Click Me" id="clicker"/> <script type="text/javascript"> // 获取目标元素的引用 var clicker = document.getElementById("clicker"); // 注册事件处理程序 clicker.attachEvent("onclick", showThis); function showThis(){ alert(this); } </script>
3.4 跨浏览器事件处理程序
事件的绑定、移除
<input type="button" value="Click Me" id="clicker"/> <script type="text/javascript"> var clicker = document.getElementById("clicker"); // 兼容代码 var handing = { addEvent:function(target, type, listener, capture){ if (target.addEventListener) { target.addEventListener(type, listener, capture); }else if(target.attachEvent){ target.attachEvent("on" + type, listener); }else{ target["on" + type] = listener; } }, deleteEvent:function(target, type, listener, capture){ if (target.removeEventListener) { target.removeEventListener(type, listener, capture); } else if(target.detachEvent){ target.detachEvent("on" + type, listener); }else{ target["on" + type] = null; } } } // 注册事件处理程序,须放在兼容代码的下面 handing.addEvent(clicker, "click", showMessage, false); // 移除事件处理程序 handing.deleteEvent(clicker, "click", showMessage, false); function showMessage(){ alert(this); } </script>
IE8以下this作用域的问题?
http://www.51-n.com/t-4203-1-1.html
如果HTML事件处理程序、 DOM0级事件处理程序 和DOM2级事件处理程序同时存在??
答:HTML事件处理程序与DOM0级事件处理程序不能同时存在,会覆盖。
且DOM0级事件处理程序不能累积添加。只执行最后一个添加的事件处理程序
DOM2级事件程序不受HTML事件处理程序和DOM0级事件处理程序的影响。遵从先添加先执行的原则,可以累积添加事件
------------------------------------------------------------------------------------
尽量缩短篇幅: javaScript事件系统详解(二)之[事件处理程序]与[事件对象]
参考链接: