本文首发于个人博客:www.wyb.plus
JS作为一门事件驱动型的语言,了解与事件有关的知识是十分必要的。
JS中与事件有关的概念非常多,本文尽量整理完善。
- 作者:王雨波
- qq:760478684
- 博客:www.wyb.plus
1. 事件的三要素
事件就是由用户或浏览器本身执行的操作。
事件的三要素包括:
事件源
,事件触发的动作
,事件处理程序
1.1 事件源
触发事件的元素叫做事件源。比如有一个button按钮绑定了一个点击事件,点击即会弹出一个弹窗,那么这个button按钮就是
事件源
1.2 事件的触发动作
事件的触发动作也叫事件的类型,上面的例子中,点击按钮的点击动作就是事件的触发动作。DOM3事件规定了以下几类事件:UI事件、焦点事件,鼠标事件、滚轮事件、文本事件、键盘事件、合成事件、变动事件
1.3 事件处理程序
响应某个事件的函数叫做
事件处理程序
。事件处理程序的名字以“on”开头,因此click事件的事件处理程序就是onclick。事件处理程序在下一节详细总结。
1.4 执行事件的步骤
- 获取元素
- 绑定事件
- 编写事件处理程序
2. 事件处理程序
事件处理程序大概有三种(排除IE),分别是HTML事件处理程序、DOM0级事件处理程序和DOM2级事件处理程序(注:没有DOM1级这个概念)
2.1 HTML事件处理程序
在HTML代码中调用函数执行就是
HTML事件处理程序
。在HTML中,某个元素支持的每种事件,都可以使用一个与相应事件处理程序同名的HTML特性来指定。比如:
<input type="button" onclick="alert('clicked!')">
这种方式
不推荐
使用,因为他会有两个问题:
- 触发事件时事件还没加载的顺序问题
- HTML与JS高度耦合的问题
2.2 DOM0级事件处理程序
DOM0级事件处理程序
是JavaScript指定事件处理程序的传统方式。这种方式是将一个函数赋值给一个事件处理程序属性。简单例子:
<body> <input type="button" name="btn" id="myBtn" value="点击"> <script> var mybtn = document.getElementById("myBtn"); mybtn.onclick = function() { alert(this.name) } </script> </body>
this
指向的是目标元素
,并且事件在冒泡阶段(冒泡阶段下节总结)被捕获。删除该事件处理程序的方法
mybtn.onclick = null
2.3 DOM2级事件处理程序
DOM2级事件定义了两个方法,用于添加和删除事件处理程序:
addEventListener()
和removeEventListener()
。所有DOM节点都包含这两个方法,并且他们都接收三个参数:要处理的事件名
,事件处理函数
,一个布尔值
。布尔值为false(默认)时表示在冒泡阶段调用事件处理函数,为true时表示在捕获阶段调用事件处理程序。一个简单例子:
<body> <input type="button" name="btn" id="myBtn" value="点击"> <script> var mybtn = document.getElementById("myBtn"); mybtn.addEventListener('click', function dd() { alert(this.type) }, false) </script> </body>
可以对
同一事件源
添加多个
DOM2级事件处理程序<script> var mybtn = document.getElementById("myBtn"); mybtn.addEventListener('click', function dd() { console.log(this.type); }, false) mybtn.addEventListener('click', function dd() { console.log(this.name); }, false) </script>
移除
DOM2级事件处理程序只能使用removeEventListener()
,并且传入的事件处理函数必须和传入相同
,所以,我们应该在添加事件时将事件处理函数先赋值给一个变量,这样才能保证移除时的函数相同<script> var mybtn = document.getElementById("myBtn"); function dd() { console.log(this.type); } mybtn.addEventListener('click', dd, false) mybtn.removeEventListener("click", dd, false) </script> //这样点击事件就不会被触发了,结果是什么也不会输出
最后,IE事件处理程序略略略过~~!
3. 批量绑定事件
3.1 批量绑定
补充批量绑定事件的知识,主要是复习一下
闭包
,因为对闭包的理解还谈不上深刻。首先来写一个批量绑定事件
<body> <ul> <li>文字按钮</li> <li>文字按钮</li> <li>文字按钮</li> <li>文字按钮</li> </ul> <script> var ps = document.getElementsByTagName("li") for (var i = 1; i < ps.length; i++) { ps[i].onclick = function() { console.log(i); } } </script> </body>
点击这四个li,出现4个4,并非1,2,3,4
这就是异步的影响(关于异步可以看我的的另一篇文章http://www.wyb.plus/index.php/archives/1317/),由于事件是异步的,所以for循环在每次执行后并不会立即把
i
传给ps,只有等到for循环结束了,才会把i
传给ps。由于for循环结束时i=4
,所以事件中的i
每次点击都为4。利用
IIFE
形成的闭包来解决异步造成的影响 >>>var ps = document.getElementsByTagName("li") for (var i = 0; i < ps.length; i++) { (function(m) { ps[m].onclick = function() { console.log(m); } })(i) };
除了
IIFE
之外还有一种方法,那就是强制添加属性
的方法:var ps = document.getElementsByTagName("li") for (var i = 0; i < ps.length; i++) { ps[i].id = i; //先编号 ps[i].onclick = function() { console.log("先编号"); console.log(this.id); } };
这种方法会在异步事件之前就将
i
赋值给ps
,在异步之前就将他们区分开了。
3.2 JS事件中的对应与排他
事件的
对应
>>>首先设计一个这样的结构:
我们要实现这样一个功能:当我点击上面一排的
li
时,下面一排对应的li
背景颜色被改变<script> var u1 = document.querySelectorAll(".u1>li") var u2 = document.querySelectorAll(".u2>li") var index = 0; for (var i = 0; i < u1.length; i++) { u1[i].index = i; u1[i].onclick = function() { u2[this.index].style.backgroundColor = "pink" } } </script>
这个功能的实现是因为这两个
ul
里面的结构相同,li
的索引相同,所以我们通过一个信号量去关联他们的索引,从而达到给A添加事件去改变B。
排他
>>>首先设计一个这样的结构:
要实现这样一个功能:当我点击一个
li
,它自己不变,其余的所有li
的背景颜色被改变<script> var u1 = document.querySelectorAll(".u1>li") var index = 0; for (var i = 0; i < u1.length; i++) { u1[i].index = i; u1[i].onclick = function() { this.style.backgroundColor = "cadetblue" for (var j = 0; j < u1.length; j++) { if (this.index !== j) { u1[j].style.backgroundColor = "pink" } } } } </script>
这就是一个最简单的排他的应用
4. 事件流
首先要明白:事件流,事件传播顺序,事件模型都是指的同一个东西 >>> 从页面中接收事件的顺序,也可以理解为事件在页面中传播的顺序。
IE和Netscape开发团队居然提出了两个截然相反的事件流概念。
- IE的事件流是 – 事件冒泡流
- 标准浏览器事件流是 – 事件捕获
- 当事件流在事件源身上时又叫目标阶段
所以事件流有三个阶段:捕获阶段–目标阶段–冒泡阶段
4.1 事件冒泡
事件冒泡:从事件源开始逐级向上传播到祖先节点。
4.2 事件捕获
事件捕获:从祖先节点逐级传播到事件源身上。
4.3 自定义监听阶段
通过DOM2级事件处理程序的addEventListener的第三个参数可以控制监听的阶段:false(默认)为冒泡阶段,true为捕获阶段
4.4 阻止默认事件和事件传播
JS中的默认行为是浏览器给我们提供的一些功能,例如:
- a标签,默认click事件,点击跳转
- contextmenu 右击弹出菜单
- submit提交表单等等
<a href="http://www.wyb.plus/" target="_blank">博客</a>
DOM中提供
preventDefault()
方法来取消事件默认行为,需要通过Event
对象调用此方法。var prevent = document.querySelector("#aa"); prevent.onclick = function(event) { event.preventDefault(); }
IE中需要使用 return false…(略略略)
阻止事件传播 >>>
DOM中提供
stopPropagation()
方法,但IE不支持。仍然使用Event
对象在事件函数中调用就行event.stopPropagation()
IE中提供的是,
cancelBubble
属性,默认为false,当它设置为true时,就是阻止事件冒泡,也是用Event
对象在事件函数中调用event.cancelBubble = true;
5. Event对象
5.1 Event是什么
Event
对象代表事件的状态。这个状态里面包含了这个事件触发时的所有细节。
Event
是JS的一个系统内置对象,平时无法使用,当有事件被触发后才会生成这个对象。查看一下这个对象上的一些属性:
var prevent = document.querySelector("#aa"); prevent.onclick = function(event) { console.log(event); }
除了一些位置类的属性外,比较重要的属性有:
type:事件类型
target:事件的目标节点
button:0,1,2 >>> 分别对应左键,滚轮,右键
bubbles:表示事件是否冒泡
cancelable:true表示停止冒泡,这就是IE中停止冒泡的方法
currentTarget:表示事件处理程序当前正在处理事件的那个元素,与this指向一致
eventPhase:调用事件处理程序的阶段 >>> 0表示捕获,1表示处于目标,2表示冒泡。这也是事件流的传播顺序。
5.2 怎么用
要注意只有在事件发生的过程中,
Event
对象才生效。在调用事件处理函数的时候,标准浏览器是传给这个事件处理函数一个实际参数
Event
对象。但是在IE6,7,8是给window对象绑定一个当前的Event
属性。具体点说:
在IE中,
Event
是一个全局变量,不存在作用域问题。谁触发了事件,那再事件绑定的函数中,你可以直接使用Event
的属性做任何操作。在标准浏览器中,每个事件绑定的处理函数中都会默认传入一个形参event,而且是在第一个形参的位置,并且我们不需要去写这个形参
举个例子:比如我将上面的例子中的事件处理函数的第一个参数由event改成hehe,没有传入event
var prevent = document.querySelector("#aa"); prevent.onclick = function(hehe) { console.log(event); }
直接打印event,结果仍然是原来的那个对象,所以我们不需要去写这个形参。
6. 事件代理
6.1 概述
事件代理也叫事件委托。
JavaScript高级程序设计上讲:事件委托就是利用事件事件冒泡,只指定一个事件处理程序,就可以管理一类型的所有事件。
仔细揣摩这个取快递的例子:一个寝室的同学都卖了东西,现在要在某一天取快递。现在由两种方案,一种是每个人都去菜鸟驿站等着,然后自己取自己的快递;另一种是同学们让寝室的室长代取,室长取了之后一一分发快递。为了提高效率,当然是选择第二种方案。第二种方案有一个优势:那就是不管寝室里有多少人,也不管后面还会不会增加人,室长都能帮忙取快递。
这里面还有两层意思 >>>
第一层:不管寝室的室长自己有没有快递,他都要取快递,即室长对应的DOM节点是有事件的。
第二层:寝室来了新室友,室长也是要帮新室友取快递的,即程序中新添加的DOM节点也是有事件的。
6.2 为什么要用委托
通过上面的例子我们也能够想到,很多人一起去快递会造成菜鸟驿站的拥堵,取快递的过程就会变慢,如果我们只派一个同学去取,那么就能提高很多的效率,节约很多的性能 。(关于js操作DOM浪费性能这点,可以参考这篇《JS当中DOM操作成本到底高在哪儿?》https://blog.csdn.net/qq_44607694/article/details/105520967)
6.3 事件委托的原理
事件委托是利用事件冒泡的原理来实现的。事件冒泡上文已经讲了,再举个例子:页面上有这样的一个节点树
div>ul>li
,当我们给最里面的li
绑定一个点击事件的时候,同时ul
也被点击了,然后是div
也被点击了。所以我们可以通过给ul
添加一个事件来代理里面的所有li
的事件
6.4 实现方法
实现一个功能:点击
li
输出对应的文本内容一般方法:不使用代理,批量绑定事件
<body> <ul class="uull"> <li>111</li> <li>222</li> <li>333</li> </ul> </body> <script> var oUl = document.querySelector(".uull") var aLi = oUl.querySelectorAll("li") for (var i = 0; i < aLi.length; i++) { aLi[i].onclick = function() { console.log(this.innerText); } } </script>
使用事件委托:
<script> var oUl = document.querySelector(".uull") var aLi = oUl.querySelectorAll("li") oUl.onclick = function() { console.log(event.target.innerText); } </script>
这样写有一点点缺陷,那就是如果我只想事件代理的效果就像直接给节点绑定事件的效果一样怎么办,比如说这有点击
li
才会触发。那么就需要用到上面讲的Event
对象的target
属性了。由与
Event
对象是有兼容性问题的,所以这里兼容一下var oUl = document.querySelector(".uull") var aLi = oUl.querySelectorAll("li") oUl.onclick = function(e) { var e = e || window.event var target = e.target || e.srcElement if (target.nodeName.toLowerCase() == "li") { console.log(target.innerText); } }
6.5新增节点的代理
我们使用了事件代理了,现在新增一个子节点,看看是否有事件
<ul class="uull"> <li>111</li> <li>222</li> <li>333</li> <li>444</li> </ul>
仍然使用上面事件代理的方法
var oUl = document.querySelector(".uull") var aLi = oUl.querySelectorAll("li") oUl.onclick = function(e) { var e = e || window.event var target = e.target || e.srcElement if (target.nodeName.toLowerCase() == "li") { console.log(target.innerText); } }
嗯,完美 !
7. 参考资料
–《JavaScript事件(事件类型、事件目标、事件处理程序、事件对象、事件流)》
– 作者:@77
–来源:CSDN
– 《JavaScript中的Event事件对象详解》
– 作者:沐枫自然
– 来源:CSDN
– 《JS中事件的核心概念》
– 作者:Lin_Dan_Dan
– 来源:CSDN
– 《js中的事件委托或是事件代理详解》
– 作者:凌云之翼
– 来源:博客园