作者:小土豆
博客园:www.cnblogs.com/HouJiao/
掘金:juejin.im/user/243617…
简介
javascript中的事件,可以理解就是在HTML文档或者浏览器中发生的一种交互操作。
本篇文章会从以下几个点去详细介绍javascript中的事件:
事件处理程序
事件流机制
事件对象
实践应用—事件委托
事件处理程序
事件处理程序指的是 处理事件的函数
,也可以称其为事件侦听器
。
所以了解事件的第一步就是如何去添加或者删除事件处理程序,下面会总结几种常见的事件处理程序的添加/删除方法。
HTML事件处理程序
HTML事件处理程序,即直接将事件处理程序添加在HTML元素中
。一般在代码中比较常见,直接看示例。
<button onclick="submit()">提交</button>
<script type="text/javascript">
function submit(){
//事件处理逻辑
}
</script>
这种直接在HTML中指定事件处理程序会有一个比较明显的缺点:有可能存在用户在点击按钮时,事件处理函数submit还未解析到,此时就会报错。
DOM0级事件处理程序
DOM0级规定的事件处理程序是在javascript中通过给事件处理程序定义一个函数
实现的。
<button id="submit">提交</button>
<script type="text/javascript">
var submitEle = document.getElementById('submit');
submitEle.onclick = function submit(){
//事件处理逻辑
}
</script>
这种方式可以为button元素添加多个事件处理函数,只不过只有最后一个会生效。
移除事件的方式也比较简单,即将事件处理程序的值设置为null。
<button id="submit">提交</button>
<script type="text/javascript">
var submitEle = document.getElementById('submit');
submitEle.onclick = function submit(){
//事件处理逻辑
}
// 移除事件
submitELe.onclick = null;
</script>
DOM2级事件处理程序
DOM2级事件中为处理事件新增了两个api,一个是添加事件api:addEventListenter
;一个是移除事件api:removeEventListener
。
这个两个api都可以接收三个参数: eventName 事件名称 fn 事件处理函数 booleanVal 一个布尔值,默认为false。用于指示事件处理函数在那个阶段执行(这个参数的作用会在事件流机制中说明)。 api使用示例:
<button id="submit">提交</button>
<script type="text/javascript">
var submitEle = document.getElementById('submit');
function submit(){
//事件处理逻辑
}
// 添加事件
submitEle.addEventListener('click',submit);
// 移除事件
submitEle.removeEventListener('click',submit);
</script>
关于这两个api的使用,我们需要注意几点。
首先第一点就是使用addEventListener
可以添加多个事件处理程序,而且事件处理程序会按照代码添加顺序依次执行。
第二点是使用addEventListener
添加的事件处理程序,只能通过removeEventListener
来移除。
第三个需要注意的是使用addEventListener
添加事件处理程序时,如果传递的第二个参数fn是一个匿名函数,则无法使用removeEventListener
去移除。
对于第三个点中提及到的匿名函数,这个写一个简单的示例。
<button id="submit">提交</button>
<script type="text/javascript">
var submitEle = document.getElementById('submit');
// 添加事件
submitEle.addEventListener('click',function(){
console.log("这是一个匿名函数");
});
// 移除事件:这种情况下使用removeEventListener是无法移除前面添加的事件处理程序
submitEle.removeEventListener('click',function(){
});
</script>
IE事件处理程序
对于IE8以及更早版本的IE浏览器来说,它不支持addEventListener、removeEventListener
这两个api。
它支持和这两个类似的api分别为:attachEvent
和deleteEvent
。
这两个api也需要接收两个参数:
eventName 事件名称
fn 事件处理函数
那话不多说,写一个示例。
<button id="submit">提交</button>
<script type="text/javascript">
var submitEle = document.getElementById('submit');
function submit(){
//事件处理逻辑
}
// 添加事件
submitEle.attachEvent('onclick',submit);
// 移除事件
submitEle.deleteEvent('onclick',submit);
</script>
使用这两个api的时候也有几个注意的地方。
第一点是api的eventName参数传递,注意是onclick
而不是click
。
第二点就是使用attachEvent可以添加多个事件处理程
序,而且事件处理程序会按照代码添加的相反
顺序依次执行。
第三点是使用attachEvent添加的事件处理程序,只能通过deleteEvent来移除。
第四个需要注意的是使用attachEvent添加事件处理程序时,如果传递的第二个参数fn是一个匿名函数,则无法使用deleteEvent去移除。
其中最后两点和add/removeEventListener
这两个api用法一致;和add/removeEventListener
这两个api用法有区别的就是第一点和第二点,已经用红色标记出来,使用的时候需要注意。
注意:IE11目前已经不支持attachEvent和deleteEvent,直接在脚本中调用方法会报错
。
通用事件处理程序
前面介绍了多种不同添加/移除事件处理程序的方式。
考虑到浏览器的兼容性,我们需要有一个通用的事件处理程序,以便我们进行快速的项目开发。
因此,下面就写一个简单的通用事件处理程序
。
var EventUtils = {
//添加事件
addEvent: function(element, eventName, fn){
if(element.addEventListener){
element.addEventListener(eventName, fn, false);
}else if(element.attachEvent){
element.attachEvent('on' + eventName, fn)
}else{
element.eventName = fn;
}
},
//移除事件
removeEvent: function(element, eventName, fn){
if(element.removeEventListener){
element.removeEventListener(eventName, fn, false);
}else if (element.deleteEvent){
element.deleteEvent('on' + eventName, fn);
}else{
element.eventName = null;
}
}
};
这个事件处理程序比较简单,后面内容总结完成后会将这段代码进行补充完善。
事件流机制
对于事件流专业的解释为:页面接收事件的顺序
。
那对于页面接收事件的顺序,分为两种,分别是:事件冒泡(event bubbling)
和事件捕获(event capturing)
。
事件冒泡
事件冒泡最早是IE提出来的事件流顺序。那么它指的是事件从子元素向父元素传播
。
现在我们有这样一段html文档:
当我们点击触发button元素的click事件后,会依次触发button元素、p元素、div元素、body元素和html元素的click事件。
事件捕获和事件冒泡是相反的顺序,即
事件由父元素向子元素传播
。
那还是前面的那段html文档。
对于捕获型的事件流,当我们触发了button的click事件后,会依次触发html元素、body元素、div元素、p元素和button元素的clcik事件。
前面DOM2级的事件处理程序中,我们介绍了两个api:
addEventListener
和removeEventListener
。
这两个api的接收的第三个boolean参数
就是用来指定事件处理程序在那个阶段执行
。
其中false
值,即指定事件在冒泡阶段执行;相反指定true
值,表示事件在捕获阶段执行。
而对于IE提供的两个api:attachEvent
和deleteEvent
,它们并不支持第三个参数,因此IE8以前的版本只支持事件冒泡的机制。
事件对象
当我们触发html文档中元素的某个事件时,事件处理程序的内部就会有一个事件对象event产生,这个对象会包含和事件相关的一些信息。
下面说的事件对象仅限于DOM0级和DOM2级事件处理程序,IE会单独说明。
属性/方法 | 类型 | 说明 |
---|---|---|
event.type | String | 被触发事件的类型。 比如触发button的click事件,那event.type的值就为"click"。 |
event.target | Element | 本次事件中的目标元素。 因为事件流机制的存在,当点击button时,会按照不同的顺序触发其他元素的事件,在这个过程中,被点击的button元素就是事件中的目标元素 |
event.currentTarget | Element | 本次事件中,当前正在处理的元素。 按照事件冒泡或者捕获的顺序处理到那个元素的事件,那个元素就是当前正在处理的元素。 |
event.cancelable | Boolean | 表示是否可以取消事件的默认行为。 true:表示可以取消事件的默认行为。 |
event.preventDefault() | Function | 调用该方法可以取消事件的默认行为,但是前提是event.cancelable的值为true。 |
event.bubbles | Boolean | 调用该方法可以取消事件的默认行为,但是前提是event.cancelable的值为true。 |
event.stopPropagation() | Function | 调用该方法可以取消事件的下一步冒泡,但前提是event.bubbles的值为true。 |
关于事件对象event的信息,我们一个一个来实践一下。 |
event.type
<button id='btn'>点击</button>
<script>
var btn = document.getElementById('btn');
btn.onclick = function(){
//事件处理程序
console.log(event.type);
}
</script>
event.target和event.currentTarget
<body>
<div id='box'>
<p id='pbox'>
<button id='btn'>点击</button>
</p>
</div>
</body>
<script>
var btn = document.getElementById('btn');
btn.onclick = function(){
//事件处理程序
console.log("btn click");
console.log("event.target:");
console.log(event.target);
console.log("event.currentTarget");
console.log(event.currentTarget);
}
var box = document.getElementById('box');
box.onclick = function(){
//事件处理程序
console.log("box click")
console.log("event.target:");
console.log(event.target);
console.log("event.currentTarget");
console.log(event.currentTarget);
}
</script>
可以看到使用DOM0级方式添加的事件处理程序,默认的事件流机制是事件冒泡,即事件触发顺序有子元素button传播到父元素div。
那这个操作中,我们点击的目标元素是button
,因此event.target
一直是button
元素。
而event.currentTarget
随着事件触发的顺序一直在变化,触发到那个元素的事件,那个元素就是event.currentTarget
。
event.cancelable和event.preventDefault
前面我们说在event.cancelable
为true的前提下,可以调用event.preventDefault
来阻止事件的默认行为。
那什么是事件的默认行为呢?
比如:
浏览器中单击右键,出现浏览器默认的菜单栏选项;
点击a标签会发生跳转行为;
form表单中点击提交按钮会提交表单数据;
接下来写一个示例。
<a id='link' href='https://www.baidu.com'>跳转</a>
<script>
var link = document.getElementById('link');
link.onclick = function(){
//事件处理程序
console.log("link click");
console.log(event.cancelable);
event.preventDefault();
}
</script>
在这个示例中,正常情况下点击"跳转"会跳转到百度首页,但是我们在a标签的onclick
事件中调用了event.preventDefault()
方法阻止了a标签默认的跳转行为。
所以我们点击"跳转"后,只会在控制台打印"link click"和event.cancelable
的值,页面并不会发生跳转行为。
event.bubbles和event.Propagation
前面第2小节中关于event.target
和event.currentTarget
的示例,因为默认的事件冒泡机制,导致click事件从button元素传播到了父元素div。
那现在我们在事件处理程序中调用event.Propagation
方法就可以阻止事件冒泡
。
<body>
<div id='box'>
<p id='pbox'>
<button id='btn'>点击</button>
</p>
</div>
</body>
<script>
var btn = document.getElementById('btn');
btn.onclick = function(){
//事件处理程序
console.log("btn click");
console.log(event.bubbles);
event.stopPropagation();
}
var box = document.getElementById('box');
box.onclick = function(){
//事件处理程序
console.log("box click")
}
</script>
从结果可以看到,调用event.stopPropagation()后,父元素的click方法已经成功被阻止。
那这里事件对象的信息基本就总结完了,上面的示例均使用DOM0级的事件处理程序实现的,那实际上对于DOM2级的事件处理程序也是适用的
。所以DOM2事件处理程序就不写在示例。
IE中的事件对象
IE中的事件对象和DOM0、DOM2中的事件对象还是有很大区别的。
它包含的属性和方法如下:
属性/方法 | 类型 | 说明 |
---|---|---|
event.type | String | 被触发事件的类型。 比如触发button的click事件,那event.type的值就为"click"。 |
event.cancelBubble | Boolean | 设置事件是否冒泡。 默认值为false,将其设置为true就可以取消事件冒泡。 |
event.returnValue | Element | 设置事件的默认行为。 默认值为true,将其设置为false就可以取消事件的默认行为。 |
event.srcElement | Element | 本次事件中的目标元素。 (同DOM1、DOM2级中的target) |
IE事件对象的这些属性和方法就不代码演示了,也比较简单。 |
只是IE中使用事件对象时,需要注意下面的几点。 (下面示例的演示,不同版本的结果使用的是IE浏览器自带的仿真工具进行的)
首先第一点是使用DOM0级添加事件处理程序时,事件处理程序内部的事件对象event是作为window的一个属性存在的
。
<button id='btn'>点击</button>
<script>
var btn = document.getElementById('btn');
btn.onclick = function(){
alert(window.event);
}
</script>
第二点就是在IE事件处理程序内部的this不一定等于目标元素
,因此在事件处理程序内部,最好用event.srcElement来代替this
。
先测试一下DOM0级事件处理程序的结果。
<button id='btn'>点击</button>
<script>
var btn = document.getElementById('btn');
btn.onclick = function(){
alert(this);
// IE8-IE11 打印 [object HTMLButtonElement]
alert(event.srcElement);
// IE8-IE11 打印 [object HTMLButtonElement]
}
再测试一下attachEvent
和deleteEvent
这两个api(IE11已经不支持这两个api了,前面已经提过)
<button id='btn'>点击</button>
<script>
var btn = document.getElementById('btn');
btn.attachEvent('onclick',function(){
alert(this);
// IE8-IE10 结果为 [object Window]
alert(event.srcElement);
// IE8-IE10 结果为 [object HTMLButtonElement]
})
</script>
关于事件对象这里就介绍完了,一整个关于IE浏览器的测试代码写下来还是有些难受。
到这里,有关javascript中的事件内容基本总结完了,这里呢,我们需要把前面那个通用的事件处理程序完善一下。
<script>
var EventUtils = {
// 添加事件
addEvent: function(element, eventName, fn){
if(element.addEventListener){
element.addEventListener(eventName, fn, false);
}else if(element.attachEvent){
element.attachEvent('on' + eventName, fn)
}else{
element.eventName = fn;
}
},
// 移除事件
removeEvent: function(element, eventName, fn){
if(element.removeEventListener){
element.removeEventListener(eventName, fn, false);
}else if (element.deleteEvent){
element.deleteEvent('on' + eventName, fn);
}else{
element.eventName = null;
}
},
// 获取事件对象
getEvent: function(event){
return event?event:window.event;
},
// 获取事件类型
getEventType: function(event){
return event.type;
},
// 获取被执行事件的目标元素
getEventTarget: function(event){
return event.target | event.srcElement;
},
// 禁用元素的默认行为
preventDefault: function(event){
if(event.preventDefault){
event.preventDefault();
}else{ //IE8以及更低版本
event.returnValue = false;
}
},
// 阻止元素冒泡
stopPropagation: function(event){
if(event.stopPropagation){
event.stopPropagation();
}else{ //IE8以及更s低版本
event.cancelable = true;
}
}
};
</script>
实践应用-事件委托
最后这一部分,主要是针对事件冒泡的一个应用
。
我们假设有这样一个场景:有一个商品列表,点击其中某一个商品,弹框显示商品标题。同时用户可以在商品列表添加商品。
现在我们实现一下这个需求。
<html>
<head>
<meta charset="utf-8"/>
<title>事件委托</title>
<style>
li{
cursor: pointer;
padding: 20px;
}
</style>
</head>
<body>
<h1>事件委托</h1>
<div class='box'>
<button onclick="add()">添加商品</button>
<div class='list'>
<h3>商品列表</h3>
<ul>
<li>商品1</li>
<li>商品2</li>
<li>商品3</li>
<li>商品4</li>
</ul>
</div>
</div>
<script type="text/javascript">
// 遍历商品列表添加点击事件
var liEle = document.getElementsByTagName('li');
for(var i = 0; i<liEle.length; i++){
var element = liEle[i];
element.addEventListener('click', function(){
alert(this.innerHTML);
})
}
// 添加商品
function add(){
var ulEle = document.getElementsByTagName('ul')[0];
var liLength = document.getElementsByTagName('li').length;
var newLiEle = document.createElement('li');
newLiEle.innerHTML = "商品"+(liLength+1);
ulEle.appendChild(newLiEle);
}
</script>
</body>
</html>
看一下浏览器效果。
可以看到,循环li元素添加的点击事件均可以正常执行,而点击【添加商品】按钮添加的商品5,点击之后并没有click事件,因此没有弹窗显示。
这个需求实际上可以转化为这样的应用场景:事件添加个数不确
定。
那么根据前面提说的事件流
、事件冒泡
和事件对象
,我们很容易就能想到解决办法:将click事件添加到父元素ul元素上,利用事件冒泡和event.target实现弹窗。
这个办法我们被称为事件委托机制
。
下面我们代码实现一下(修改代码27-32行)。
<html>
<head>
<meta charset="utf-8"/>
<title>事件委托</title>
<style>
li{
cursor: pointer;
padding: 20px;
}
</style>
</head>
<body>
<h1>事件委托</h1>
<div class='box'>
<button onclick="add()">添加商品</button>
<div class='list'>
<h3>商品列表</h3>
<ul>
<li>商品1</li>
<li>商品2</li>
<li>商品3</li>
<li>商品4</li>
</ul>
</div>
</div>
<script type="text/javascript">
// 将click事件添加到父元素ul元素上,利用事件冒泡和event.target实现弹窗
var ulEle = document.getElementsByTagName('ul')[0];
ulEle.addEventListener('click', function(){
var target = event.target;
alert(target.innerHTML);
})
// 添加商品
function add(){
var ulEle = document.getElementsByTagName('ul')[0];
var liLength = document.getElementsByTagName('li').length;
var newLiEle = document.createElement('li');
newLiEle.innerHTML = "商品"+(liLength+1);
ulEle.appendChild(newLiEle);
}
</script>
</body>
</html>
总结
本篇文章主要参考了《javascript高级程序设计》这本书中的内容,示例均亲自实践过,若有不对的地方,欢迎和我讨论~
作者:小土豆
博客园:www.cnblogs.com/HouJiao/
掘金:juejin.im/user/243617…