前言
今天我们来聊一聊JS 的事件机制:众所周知,JavaScript 是一个事件驱动的语言,当浏览器载入网页开始读取后,虽然马上会读取JavaScript 事件相关的代码,但是必须要等到「事件」被触发(如使用者点击、按下键盘等)后,才会再进行对应代码段的执行。换句话说,html里面的DOM元素一直在等待事件的触发。
举个例子:电话如果没有人打电话过来,我们是不会动的,当别人打电话过来的时候,我们才会去接那个电话。电话响了(事件被触发) -> 接电话(去做对应的事)
接下来让我们来探讨一下DOM事件级别
DOM事件
DOM有4次版本更新,与DOM版本变更,产生了3种不同的DOM事件 :DOM 0级事件处理,DOM 2级事件处理和DOM 3级事件处理。由于DOM 1级中没有事件的相关内容,所以没有DOM 1级事件。
DOM0级事件
<input onclick="alert('hello')"/>
onclick(鼠标点击)就是一个DOM0级事件,但是现在已经不提倡用了,因为html和js杂糅在一起,耦合度太高,不利于维护,因此现在已经不建议用此方式来绑定事件。
DOM2级事件
DOM2级事件模型是对DOM0级事件的重大改进,提供了更强大、更灵活的事件处理机制,是现代Web开发中事件处理的标准方式。Dom 2级事件是通过 addEventListener 绑定的事件。DOM2级事件支持事件捕获和事件冒泡(捕获,目标,冒泡)同时更精确的事件控制(可以指定在捕获阶段还是冒泡阶段处理)。
Dom 2级事件有三个参数:第一个参数是事件名(如click);第二个参数是事件处理程序函数;第三个参数如果是true的话表示在捕获阶段调用,为false的话表示在冒泡阶段调用。捕获阶段和冒泡阶段在下一节具体介绍。
DOM事件流
事件流(Event Flow)指的就是网页接收事件的顺序,事件流有两种机制:
- 事件捕获(Event Capturing)
- 事件冒泡(Event Bubbling)
当一个事件发生后,会在子元素和父元素之间传播(propagation)。这种传播分成三个阶段:
- 捕获阶段:事件从window对象自上而下向目标节点传播的阶段;
- 目标阶段:真正的目标节点正在处理事件的阶段;
- 冒泡阶段:事件从目标节点自下而上向window对象传播的阶段。
接下来我们来看一下事件捕获和事件冒泡。
事件捕获
事件捕获是从根节点(通常是window 或者 document 开始),逐级向下遍历 DOM 树,直到到达触发事件的目标元素(target)
事件冒泡
事件冒泡是从目标元素(target)开始,逐级向上传播到根节点(window)。
接下来让我们来看一个例子,它会让你更加深入的了解事件捕获和事件冒泡
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JS DOM2 事件机制</title>
<style>
#parent {
width: 200px;
height: 200px;
background-color: blue;
}
#child {
width: 100px;
height: 100px;
background-color: red;
}
</style>
</head>
<body>
<div id="parent" >
<div id="child">
</div>
</div>
<script>
document.getElementById('parent')
.addEventListener('click',function(event){
console.log('父元素clicked')
},false)
document.getElementById('child')
.addEventListener('click',function(event){
console.log('子元素clicked')
},true)
</script>
</body>
</html>
让我们来一步步分析一下:
- 1
document.getElementById('parent') .addEventListener('click',function(event){ console.log('父元素clicked') },false)
parent节点注册了一个点击事件监听的事件,useCapture 的值为false(也就是默认值),也就是在冒泡阶段触发监听。
- 2
document.getElementById('child') .addEventListener('click',function(event){ console.log('子元素clicked') },true)
child节点注册了一个点击事件监听的事件,useCapture 的值为true,也就是在捕获阶段触发
-
3 事件流的顺序
-
捕获阶段(Capture Phase)
- 事件从
document向下传播到#child。 - 由于
#child设置了useCapture: true,它的监听器会触发。 - 输出:
子元素clicked。
- 事件从
-
目标阶段(Target Phase)
- 事件到达
#child(目标元素),但这里没有额外的监听器。
- 事件到达
-
冒泡阶段(Bubble Phase)
- 事件从
#child向上冒泡到#parent。 - 由于
#parent设置了useCapture: false(默认冒泡阶段),它的监听器会触发。 - 输出:
父元素clicked。
- 事件从
当我们点击子元素的时候:
总结
在实际开发中,我们可以根据具体场景选择合适的事件处理方式。例如,对于静态元素较多的页面,可以使用传统的事件绑定方式;对于动态元素较多的页面,使用事件委托可以提高性能。同时,在 React 项目中,要充分利用合成事件的特性,避免不必要的性能开销。