利用事件委托代替for循环提高效率

190 阅读2分钟

在说到事件委托之前我觉得有必要对事件流进行一个回顾,了解事件的执行过程有助于加深对事件的理解。

事件流


image.png 如上图,事件被触发后有两个阶段:'捕获阶段'和'冒泡阶段'

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Document</title>
	<style type="text/css">
		
		div {
			width: 200px;
			height: 200px;
			background: orange;
		}
		
		div p {
			width: 100px;
			height: 100px;
			background: pink;
		}


	</style>
</head>
<body>
	
	
	<div>
		<p>鍛煉</p>
	</div>

	<script type="text/javascript">
			
		// 事件高级:
		// 事件流:
		const p = document.querySelector('p');
		const div = document.querySelector('div');

		p.addEventListener('click', function () {
			console.log('p');
		});

		div.addEventListener('click', function () {
			console.log('div');
		});

		document.addEventListener('click', function () {
			console.log('document');
		});


		// 事件源.addEventListener('事件源', 事件处理函数, true/false)


	</script>



</body>
</html>

录制_2022_04_23_18_42_08_832 00_00_00-00_00_30.gif 执行上述代码后发现,当单击事件触发时,其祖先元素的单击事件也【相继触发】,这是为啥呢? 当某一个元素被触发了之后,会先从上往下找,是谁干的这事儿,找到之后再从下往上冒泡,往上汇报,是这个元素干的事儿。这个找的过程叫做捕获,往上汇报的过程就是冒泡。

结论
  • addEventListener 第3个参数决定了事件是在捕获阶段触发还是在冒泡阶段触发
  • addEventListener 第3个参数为 true 表示捕获阶段触发,false 表示冒泡阶段触发,默认值为 false
  • 事件流只会在父子元素具有相同事件类型时才会产生影响
  • 绝大部分场景都采用默认的冒泡模式

阻止冒泡


阻止冒泡是为了防止事件的流动,保证事件只在当前元素执行,不再去影响上级元素。

<body>
	<div>
            <p>abc</p>
	</div>

	<script type="text/javascript">
		
		const p = document.querySelector('p');
		const div =  document.querySelector('div');

		p.addEventListener('click', function (e) {
			console.log('p');

			// 阻止冒泡:
			// 事件对象.stopPropagation();
			e.stopPropagation();

		});

		div.addEventListener('click', function () {
			console.log('div')
		});

	</script>

</body>
结论

事件对象中的ev.stopPropagation方法,专门用来阻止事件冒泡。

  • 鼠标经过事件
  1. mouseovermouseout 会有冒泡效果
  2. mouseentermouseleave 没有冒泡效果 (推荐)

事件委托

事件委托是利用事件流的特征解决一些现实开发需求的知识技巧,主要的作用是提升程序效率。大量的事件监听是比较耗费性能的,如下代码所示

<script>
  // 假设页面中有 10000 个 button 元素
  const buttons = document.querySelectorAll('table button');

  for(let i = 0; i <= buttons.length; i++) {
    // 为 10000 个 button 元素添加了事件
    buttons.addEventListener('click', function () {
      // 省略具体执行逻辑...
    })
  }
</script>

我们可以利用事件流的特征,对代码进行优化,给它的父元素监听点击事件,利用事件的冒泡会向上级元素冒泡这个特征进行优化。为了找到是哪个元素被触发的,需要用到事件对象e.target

<script>
  // 假设页面中有 10000 个 button 元素
  const buttons = document.querySelectorAll('table button')
  
  // 假设上述的 10000 个 buttom 元素共同的祖先元素是 table
  const parents = document.querySelector('table')
  parents.addEventListener('click', function (e) {
    // console.log(e.target);
    // 只有 button 元素才会真正去执行逻辑
    if(e.target.tagName === 'BUTTON') {
      // 执行的逻辑
    }
  })
</script>

其他事件

  • 页面加载事件,事件名:load
window.addEventListener('load', function() {
    // xxxxx
})
  • 元素滚动事件, 滚动条滚动触发
window.addEventListener('scroll', function() {
    // xxxxx
})
  • 页面尺寸事件
window.addEventListener('resize', function() {
    // xxxxx
})

元素尺寸与位置

获取元素的自身宽高、包含元素自身设置的宽高、padding、border

offsetWidthoffsetHeight获取出来的是数值,方便计算

注意: 获取的是可视宽高, 如果盒子是隐藏的,获取的结果是0