本篇博文解决的问题:
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获取元素的方法
1)getElementById
返回一个匹配特定ID的元素,不存在则返回null。
一般情况下认为这是一个唯一值,因此如果页面上有多个相同id的元素,DOM只会解析第一个元素,页面只会显示第一个元素,则也只能获取第一个元素。
2)getElementsByName
根据给定的“name”返回一个在HTML document的节点列表集合。
name是元素的name属性的值。包含添加了name自定义属性的元素。
在IE和Opera中,该方法还会返回id为指定值的元素,所以最好不要为元素的name和id赋同样的值。
3)getElementsByTagName
返回一个包括所有给定标签名称的元素的HTML集合,这个文件结构都会被搜索,包括根结点。
返回的HTML集合是动态的,即它可以自动更新自己来保持和DOM树的同步而不用再次调用。
使用方法:
var elements = document.getElementsByTagName(name);
name是一个代表元素的名称的字符串。
4)getElementByClassName
返回一个包含了所有指定类名的子元素的类数组对象。当在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()。