本篇博文解决的问题:
1、给父元素注册点击事件,特定子元素响应该点击事件;——2.2 事件委托
2、给父元素注册点击事件,点击父元素,所有子元素响应/不响应该点击事件;——2.3 事件冒泡
3、给子元素和父元素注册点击事件,点击子元素,自己有点击事件的父元素响应/不响应自己的点击事件。——2.3 事件冒泡
4、给多个子元素和父元素注册点击事件,点击子元素,只响应子元素的点击事件。——2.2 事件委托 2.3 事件冒泡
一 示例
要求:根据以下代码,在<script>标签中添加代码,删除一条数据。
下面代码中可以看出添加的代码必须使用js的原生方式,DOM获取元素的方法有getElementById、getElementsByName、getElementsByTagName和getElementByClassName。
若根据id来获取元素并添加点击事件,由于只有父元素具有id,因此点击事件添加在父元素上,子元素“删除”进行响应,则需要用到事件委托:通过父元素给子元素注册点击事件。
提供的代码为:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<!--code here-->
<title>demo</title>
<style>
* {
padding: 0;
margin: 0;
}
.head, li div {
display: inline-block;
width: 70px;
text-align: center;
}
li .id, li .sex, .id, .sex {
width: 15px;
}
li .name, .name {
width: 40px;
}
li .tel, .tel {
width: 90px;
}
li .del, .del {
width: 15px;
}
ul {
list-style: none;
}
.user-delete {
cursor: pointer;
}
</style>
</head>
<body>
<div id="J_container">
<div class="record-head">
<div class="head id">序号</div><div class="head name">姓名</div><div class="head sex">性别</div><div class="head tel">电话号码</div><div class="head province">省份</div><div class="head">操作</div>
</div>
<ul id="J_List">
<li><div class="id">1</div><div class="name">张三</div><div class="sex">男</div><div class="tel">13788888888</div><div class="province">北京</div><div class="user-delete">删除</div></li>
<li><div class="id">2</div><div class="name">李四</div><div class="sex">女</div><div class="tel">13788887777</div><div class="province">上海</div><div class="user-delete">删除</div></li>
<li><div class="id">3</div><div class="name">王五</div><div class="sex">男</div><div class="tel">13788889999</div><div class="province">天津</div><div class="user-delete">删除</div></li>
</ul>
</div>
<script>
// your code here
</script>
</body>
</html>
效果为:
script中的代码为:
//方法1,直接获取class,为删除的div添加点击事件
var a = document.getElementsByClassName("user-delete");
for(var i = 0, len = a.length; i < len; i++) {
a[i].addEventListener("click", function(e) {
this.parentNode.remove();
})
}
//方法2:点击事件添加在ul上,使用事件委托
var deletef = function() {
this.del = function(li) {
li.remove();
}
}
var delli = new deletef();
var parent = document.getElementById("J_List");
parent.addEventListener("click", function(e) {
if (e.target.className === "user-delete") {
delli.del(e.target.parentNode);
}
},false);
二 相关方法解释
在上面script中涉及到的方法有:
1、DOM获取元素的方法
返回一个匹配特定ID的元素,不存在则返回null。
一般情况下认为这是一个唯一值,因此如果页面上有多个相同id的元素,DOM只会解析第一个元素,页面只会显示第一个元素,则也只能获取第一个元素。
根据给定的“name”返回一个在HTML document的节点列表集合。
name是元素的name属性的值。包含添加了name自定义属性的元素。
在IE和Opera中,该方法还会返回id为指定值的元素,所以最好不要为元素的name和id赋同样的值。
返回一个包括所有给定标签名称的元素的HTML集合,这个文件结构都会被搜索,包括根结点。
返回的HTML集合是动态的,即它可以自动更新自己来保持和DOM树的同步而不用再次调用。
使用方法:
var elements = document.getElementsByTagName(name);
name是一个代表元素的名称的字符串。
返回一个包含了所有指定类名的子元素的类数组对象。当在document对象上调用时,会搜索真个DOM文档,包括根结点。
在任意元素上调用此方法将返回以当前元素为根结点,所有指定类名的子元素。
2、DOM事件委托
事件委托,即把一个元素的响应事件的函数委托给另一个元素。
一般情况下,把一个或一组元素的事件委托给它的父元素或者更外层元素上,因此真正绑定事件的是外层元素,当事件响应到需要绑定的元素上时,会通过事件冒泡机制触发到它的外层元素的绑定事件上,然后在外层元素上进行执行。
重新来看上面的代码:
var parent = document.getElementById("J_List");
parent.addEventListener("click", function(e) {
if (e.target.className === "user-delete") {
//真正要执行的代码
}
},false);
实际上要进行click事件响应的是li的子元素“删除”这个div,但是在代码中可以看到事件绑定给了ul元素,e为点击的元素,即所绑定的外层元素,target元素是在外层元素之下具体被点击的元素,通过判断target的属性来进行匹配,此处是通过class来判断找到“删除”这个div。
注意,代码中少了兼容性处理:
var parent = document.getElementById("J_List");
parent.addEventListener("click", function(e) {
//兼容性处理
var event = e || window.event;
var target = event.target || event.srcElement;
if (target.className === "user-delete") {
//真正要执行的代码
}
},false);
更具体和详细的解释等可以看JavaScript事件委托详解。
3、DOM事件捕获和事件冒泡
上面提到了事件冒泡,它具体是什么呢?
事件冒泡:当鼠标点击或者触发DOM事件时,浏览器会从内向外进行事件传播,直到根节点。即点击了父元素,如果子元素通过事件冒泡方式注册了对应的事件,会先触发子元素绑定的事件。
事件捕获:当鼠标点击或者触发DOM事件时,浏览器会从根节点开始由外到内进行事件传播。即点击了子元素,如果父元素通过事件捕获方式注册了对应的事件,会先触发父元素绑定的事件。
可以看到这两个的事件的响应方式正好相反,它们的行为都是事件传播。
DOM事件流存在三个阶段:事件捕获阶段、处于目标阶段、事件冒泡阶段。
DOM标准事件流的触发先后顺序为:先捕获再冒泡。
事件冒泡
先来看一个例子:
首先,给上面的代码中的每个li添加一个id
<li id = "1">code</li>
<li id = "2">code</li>
<li id = "3">code</li>
然后给body、ul和id为1的li添加点击事件:
var parent = document.getElementById("J_List");
var child = document.getElementById("1");
document.body.addEventListener("click", function(e){
alert("body");
}, false);
parent.addEventListener("click", function(e){
alert("parent");
}, false);
child.addEventListener("click", function(e){
alert("child");
}, false);
点击第一个li,可以看到运行结果为:
点击子元素后,事件触发顺序是从内到外的:child-parent-body。虽然只点击了子元素,但它的所有有点击事件的父元素全都响应了,这就是事件冒泡。
可以这样理解:虽然只点击了子元素li1,但li1在parent里面,parent又在body里面,把它们都当成有压感的东西,压了最上面的,那下面的也应该有感觉,有了感觉就要有相应的反应,这个反应就是响应自己的点击事件。
如果我不想要父元素和body有反应怎么办呢?这就需要阻止事件传播。修改child的代码:
child.addEventListener("click", function(e){
alert("child");
//停止事件传播
e.stopPropagation();
}, false);
运行结果:
在上面的操作中,点击了两次,第一次点击li1,第二次点击li3,可以看到弹出的结果就不同:
点击li1:li1有点击事件,且阻止了事件的传播,因此只会弹出child;
点击li3:li3没有点击事件,进行事件传播,传播到ul,检测到有点击事件,弹出parent,再进行传播,传播到body,检测到有点击事件,弹出body。
由于冒泡是从内到外,因此阻止冒泡只能阻止该元素的事件向外传递,所以点击li1会阻止事件传递。
而点击元素的子元素,由于子元素没有阻止事件的传播,因此它都会冒泡到最外层元素,所以点击li3后外层元素都会响应。
所以如果不想点击子元素后父元素的点击事件进行响应,就需要使该父元素的子元素阻止冒泡。则如果给li3增加一个监听事件,点击li3就不会有任何反应。
var li3 = document.getElementById("3");
li3.addEventListener("click", function(e){
e.stopPropagation();
}, false);
那么,如果父元素注册了点击事件,其中的多个子元素也注册了点击事件,我希望不论点击哪一个子元素,父元素的点击事件都不响应,应该怎么做呢?
有两个方法:
方法1:子元素的点击事件写在自己的元素上,在每个点击事件里添加阻止冒泡事件的语句;
方法2:使用事件委托,将所有的点击事件写在父元素上,使用条件语句进行判断,在最后写上阻止冒泡事件的语句。
因此,如果部分子元素需要父元素响应自己的点击事件、部分元素不需要父元素响应自己的点击事件,也对应以下方法:
方法1:子元素的点击事件写在自己的元素上,根据需要决定是否添加添加阻止冒泡事件的语句;
方法2:使用事件委托,将不需要父元素响应的点击事件写在父元素上,使用条件语句进行判断,在最后写上阻止冒泡事件的语句。
方法3:与方法2相反,不推荐。
事件捕获
修改上面的代码:
//在parent后添加事件传播,表示是事件传播
parent.addEventListener("click", function(e){
alert("parent事件传播");
}, false);
//添加parent的事件捕获,注意false变为了true
parent.addEventListener("click", function(e){
alert("parent事件捕获");
}, true);
运行结果:
可以看到执行顺序为:parent事件捕获-child-parent事件传播-body。
父元素通过事件捕获的方式注册了click事件,根据DOM标准事件流的触发顺序,这个click事件在事件捕获阶段就会触发,然后到了目标阶段,即事件源,然后再进行事件传播,而parent也用冒泡方式注册了click事件,所以又会触发冒泡事件,最后冒泡到根节点。
根据上面的代码可以看到,冒泡和捕获在代码上的区别是监听事件的第三个参数,第三个参数是可选参数,默认为false,即事件冒泡,当设置为true时为事件捕获。具体使用方式看EventTarget.addEventListener()。