DOM事件和事件委托(dom事件流是什么意思)
DOM事件
首先先看下代码:
<div class="grandfather"> <div class="father"> <div class="son"></div> word </div> </div> 即.grandfather>.father>.son 给三个div分别添加事件的监听fnYe/fnBa/fnEr复制代码
提问1:点击了谁?
点击文字,算不算点击儿子?
点击文字,算不算点击爸爸?
点击文字,算不算点击爷爷?
答案:都算
提问2:调用循序
点击文字,最先调用fnYe/fnBa/fnEr中的那一个函数?
答案:都行
2002年,w3c发布标准 文档名为DOM Level 2 Events Specification
规定浏览器应该同时支持两种调用顺序
首先按照grandfather->father->son
然后按照son->father->grandfather
从外向内找监听函数,叫做事件捕捉
从内向外找监听函数,叫做事件冒泡
开发者可以自己决定把fnYe放在捕捉阶段还是放在冒泡阶段
图示:
addEventListener
事件绑定API
IE5*:baba.attachEvent('onclick',fn)//冒泡 网景:baba.addEventListener('click',fn)//捕获 W3C:baba.addEventListener('click',fn,bool)复制代码
W3C: 如果bool不传或为falsy,就让fn走冒泡,即当浏览器在冒泡阶段发现baba有fn监听函数,就会调用fn,并提供事件信息。
如果bool为true就让fn走捕获,即当浏览器在捕获阶段发现baba有fn监听函数,就会调用fn,并且提供事件信息。
如下图:
target和currentTarget的区别
区别
e.target- 用户操作的元素 e.currentTarget-程序员监听的元素复制代码
举例
<div> <span>文字</span> </div>复制代码
当用户点击文字时
e.target是span
e.currentTarget是div
# e.stopPropagation():取消冒泡
e.stopPropagation()可打断冒泡,浏览器不再向上走,一般用于封装某些独立组件.
所有冒泡皆可取消,默认动作有的可以取消有的不能取消
如何查看事件默认动作
在MDN中搜索事件时 Cancelable为NO则无法默认动作
如下图:
事件委托
定义
JS里的事件委托:就是当事件触发时,把要做的事委托给父元素来处理。
再通俗点:就是自己的事不想干,叫它爸爸,甚至爷爷、甚至祖先来干。
作用
作用1:节约内存
作用2:能为之后新增的DOM元素依然添加事件
例子
<ul> <li>隔壁老王1</li> <li>隔壁老王2</li> <li>隔壁老王3</li> <li>隔壁老王4</li> <li>隔壁老王5</li> </ul> <script> //找到所有li标签 let liList = document.getElementsByTagName('li') //遍历每个li,并给每个li加点击事件 for(let i=0;i<liList.length;i++){ liList[i].onclick = function(){ console.log(this.innerHTML) } } </script>复制代码
代码如下
代码解析:
给5个li标签加了点击事件,当界面上点击li时,会打印它们各自li标签显示的内容。
出现的问题:
此时5个li,看起来每个li的点击事件触发时调用的都是同一个函数,即:
function(){ console.log(this.innerHTML); };复制代码
但其实并不是这样。每个li绑定的都是一个全新的函数,只不过每个函数的样子都一毛一样。
至此,我们可以得到结论,如果有5个li,那么就有5个函数会被创建在内存中占据空间,那如果有100个li呢?就会有100个长相一毛一样的函数在内存中常驻,对内存的开销是巨大的!
使用事件委托来写
let ul = document.getElementsByTagName('ul')[0]; ul.onclick = function(e){ e = e || window.event //e.target 表示被点击的那个li console.log(e.target.innerHTML) } 复制代码
利用事件冒泡的原理,把事件加在父元素(ul)身上!
给所有li添加点击事件,只要加到它们的父元素ul身上的根本原因是利用了事件冒泡。也即:无论点击哪个li,都会自动触发ul的点击事件,然后在ul里通过e.target能获得真正被点击的那个li,继而拿到它的innerHTML
小结:如果给一堆元素加事件,并且事件触发时执行的代码都差不多时,就可以把事件加在父元素身上啦!这样可以更节省内存空间哦!
但如果ul中还有其他子元素,但是只想给li加事件,就需要加一个判断事件源.代码如下
ul.onclick = function(e){ e = e || window.event //e.target 表示被点击的那个li if(e.target.nodeName.toLowerCase() === 'li'){ console.log(e.target.innerHTML) } }复制代码
例子2:新增元素没有绑定事件的问题
<button id="add">添加一个li</button> <ul> <li>隔壁老王1</li> <li>隔壁老王2</li> <li>隔壁老王3</li> <li>隔壁老王4</li> <li>隔壁老王5</li> </ul> <script> //找到所有li标签 let ul = document.getElementsByTagName("ul")[0]; let liList = document.getElementsByTagName("li"); console.log(liList); //遍历每个li, 并给每个li加点击事件; for (let i = 0; i < liList.length; i++) { liList[i].onclick = function () { console.log(this.innerHTML); }; } document.getElementById("add").onclick = function () { let newLi = document.createElement("li"); newLi.innerHTML = "我是新创建的li"; ul.appendChild(newLi); }; ```复制代码
普通写法,代码如下
剖析代码:
1.页面刚开始加载时就默认存在的5个li是有点击事件的,但是点击按钮创建出来的li没有点击事件。
2.因为上面的JS代码是在页面刚加载时执行的,在当时因为不可能去点击按钮,所以能找到的li标签只有默认那5个,因此你打印liList,发现也只有5个。
* 复制代码
3.总结:因此遍历liList给每个元素加点击事件时,只能给这5个li加到点击事件。如果后面再有li标签,也不拥有点击事件。
使用事件委托.代码如下
<script> //找到所有li标签 let ul = document.getElementsByTagName("ul")[0]; let liList = document.getElementsByTagName("li"); console.log(liList); ul.onclick = function (e) { e = e || window.event; //e.target 表示被点击的那个li if (e.target.nodeName.toLowerCase() === "li") { console.log(e.target.innerHTML); } }; document.getElementById("add").onclick = function () { let newLi = document.createElement("li"); newLi.innerHTML = "我是新创建的li"; ul.appendChild(newLi); };复制代码
总结
因为事件冒泡机制的存在,不管是原本有的li还是新创建的li,当事件触发时都会一级一级往上调用父元素的同名事件。因此,只要是点击的li标签,都会触发ul的点击事件,所以只要把事件加在ul身上就解决了不管新旧li标签都有点击事件的问题。
作者:huang99
链接:https://juejin.cn/post/7068955864186503204