在JS事件循环中,我们接触了很多JS自己触发的事件。但是当我们在网页上进行某些类型的交互时,也会触发事件,比如在某些内容上的点击、鼠标经过某个特定元素或按下键盘上的某些按键。当一个节点产生一个事件时,该事件会在元素结点与根节点之间按特定的顺序传播,路径所经过的节点都会收到该事件,这个传播过程称为DOM事件流。
什么是事件流
事件流描述的就是从页面中接收事件的顺序。而早期的IE和Netscape提出了完全相反的事件流概念,IE事件流是事件冒泡,而Netscape的事件流就是事件捕获。
事件冒泡事件捕获
- 事件冒泡:从目标触发的元素逐级向上传播,直到window对象
- 事件捕获:从document逐级向下传播到目标元素
事件流包括三个阶段:
- 事件捕获阶段
- 处于目标阶段
- 事件冒泡阶段
DOM事件处理
而DOM事件处理分为4个级别:DOM0级事件处理,DOM1级事件处理,DOM2级事件处理和DOM3级事件处理。
其中DOM1级事件处理标准中并没有定义相关的内容,所以没有所谓的DOM1事件处理;DOM3级事件在DOM2级事件的基础上添加了更多的事件类型。
1、 DOM0:(只支持冒泡阶段)
- 内联模型(行内绑定),将函数名直接作为html标签中属性的属性值。
<div onclick="btnClick()">click</div>
缺点:不符合w3c中关于内容与行为分离的基本规范
- 脚本模型(动态绑定),通过在JS中选中某个节点,然后给节点添加onclick属性。
var btn=document.getElementById("btn");
btn.onclick=function(){
console.log("hello");
}
缺点:第一个事件函数被第二个事件函数给覆盖掉,所以脚本模型的缺点是同一个节点只能添加一次同类型事件
2、DOM2:
- addEventListener() ---添加事件侦听器
- removeEventListener() ---删除事件侦听器
函数均有3个参数, 第一个参数是要处理的事件名 第二个参数是作为事件处理程序的函数 第三个参数是一个boolean值,默认false表示使用冒泡机制,true表示捕获机制。
执行顺序
- 先执行捕获阶段的处理程序,后执行冒泡阶段的处理程序
- 在触发的目标元素上不区分冒泡还是捕获,按绑定的顺序来执行。
阻止冒泡
stopPropagation函数,阻止程序冒泡。
事件委托
如果有多个DOM节点需要监听事件的情况下,给每个DOM绑定监听函数,会极大的影响页面的性能,因为我们通过事件委托来进行优化,事件委托利用的就是冒泡的原理。
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
<script>
var li_list = document.getElementsByTagName('li')
for(let index = 0;index<li_list.length;index++){
li_list[index].addEventListener('click', function(ev){
console.log(ev.currentTarget.innerHTML)
})
}
</script>
正常情况我们给每一个li都会绑定一个事件,但是如果这时候li是动态渲染的,数据又特别大的时候,每次渲染后(有新增的情况)我们还需要重新来绑定,又繁琐又耗性能;这时候我们可以将绑定事件委托到li的父级元素,即ul。
var ul_dom = document.getElementsByTagName('ul')
ul_dom[0].addEventListener('click', function(ev){
console.log(ev.target.innerHTML)
})
上面代码中我们使用了两种获取目标元素的方式,target和currentTarget,
- target返回触发事件的元素,不一定是绑定事件的元素
- currentTarget返回的是绑定事件的元素
优点:
- 提高性能:每一个函数都会占用内存空间,只需添加一个事件处理程序代理所有事件,所占用的内存空间更少。
- 动态监听:使用事件委托可以自动绑定动态添加的元素,即新增的节点不需要主动添加也可以一样具有和其他元素一样的事件。