addEventListener的第三个参数和你不知道的性能优化

options对象,可选

  • capture可选

    一个布尔值,表示 listener是在冒泡阶段还是捕获阶段执行, 默认为false, 即在冒泡阶段执行

  • once 可选

    一个布尔值,表示 listener 在添加之后最多只调用一次。如果为 truelistener 会在其被调用之后自动移除。

  • passive可选

    一个布尔值,设置为 true 时,表示 listener 永远不会调用 preventDefault()

  • signal 可选

    可以传入AbortSignal对象的signal, AbortSignalabort() 方法被调用时,监听器会被移除。

代码示例
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>addEventListener Options Example</title>
  <style>
    body {
      margin: 0;
      padding: 0;
      font-family: Arial, sans-serif;
    }
    .box {
      width: 200px;
      height: 200px;
      background-color: lightblue;
      margin: 20px;
    }
  </style>
</head>
<body>
  <div class="box"></div>
  <button id="abortButton">Abort</button>
  <script>
    const box = document.querySelector('.box');
    const abortButton = document.querySelector('#abortButton');

    // 使用 capture 选项
    box.addEventListener('click', (event) => {
      console.log('Capture phase: Clicked on box');
    }, { capture: true });

    // 使用 once 选项
    box.addEventListener('click', (event) => {
      console.log('Once: Clicked on box');
    }, { once: true });

    // 使用 passive 选项
    box.addEventListener('touchstart', (event) => {
      console.log('Passive: Touch started on box');
      // event.preventDefault(); // 这行代码不会阻止默认行为
    }, { passive: true });

    // 使用 signal 选项
    const controller = new AbortController();
    const signal = controller.signal;

    box.addEventListener('click', (event) => {
      console.log('Signal: Clicked on box');
    }, { signal });

    abortButton.addEventListener('click', () => {
      controller.abort();
      console.log('Aborted: Listener removed');
    });
  </script>
</body>
</html>

关于使用passive属性的性能优化

passive可选

一个布尔值,设置为 true 时,表示 listener 永远不会调用 preventDefault()

event.preventDefault的作用
JavaScript 中,有许多事件会自动地触发浏览器的某些行为, 比如
  • 点击a标签,跳转到href的url
  • input框按下键盘字符自动输入

event.preventDefault的作用就是取消这些默认行为

非快速滚动区域

非快速滚动区域(non-fast scrollable region)是浏览器在处理页面合成时的一个重要概念。当一个页面被合成时,合成线程会将那些注册了事件监听器的区域标记为“非快速滚动区域”。这意味着,如果用户在这些区域触发事件,合成线程需要将事件发送给主线程来处理,而不是直接合成新的帧。

在这里插入图片描述

具体来说,当事件发生在非快速滚动区域时,合成线程会执行以下操作:

  1. 发送事件到主线程:合成线程会将事件发送给主线程,由主线程调用相关事件的回调函数。
  2. 等待主线程处理:合成线程会等待主线程处理完事件后再继续合成新的帧。

这种机制会导致以下几种情况:

  • 监听器函数执行时间过长:如果事件处理函数执行时间过长,会导致新的帧出现时间过晚,给用户带来卡顿的感觉。
  • 执行event.preventDefault:如果事件处理函数中调用了event.preventDefault,通知合成线程停止合成新的帧,页面也就没有滑动的效果。
  • 事件发生在其他区域:如果事件发生在非快速滚动区域之外,合成线程就无须等待主线程的处理,直接合成一个新的帧,使页面产生滑动的效果。

为了避免这种情况,开发者可以在事件监听器中传递passive: true选项。这个选项告诉浏览器,即使事件处理函数中调用了event.preventDefault,合成线程也可以继续合成新的帧,而不需要等待主线程的响应。这样可以确保页面的流畅滚动效果。

引用自 深入了解现代网络浏览器(第 4 部分) | Blog | Chrome for Developers

使用passive进行滚动性能优化的代码示例
<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<title>Scroll Jank Demo</title>
		<style>
			div {
				height: 3000px;
				background: linear-gradient(70deg, blue, pink);
			}
		</style>
	</head>

	<body>
		<div></div>

		<script>
			function simulateJank(duration) {
				const startTime = Date.now();
				while (Date.now() < startTime + duration) {}
			}

			document.addEventListener(
				"wheel",
				(event) => {
					if (Math.random() < 0.8) {
						console.log("Jank simulation start ---------");
						simulateJank(500);
						console.log("Jank simulation end ---------");
						console.log("\n");
					}
				},
				{ passive: false }
			);
		</script>
	</body>
</html>

这段代码中, 监听鼠标滚轮的滚动事件, 在滚动时,会有80%的概率执行一段500ms的长任务, 给我们的感觉就是滑动时的卡顿

如果把passive设置为``false`,会发现滚动的过程中没有卡顿的感觉了

具体原因就是上面非快速滚动区域部分的介绍

合成线程也可以继续合成新的帧,而不需要等待主线程的响应。这样可以确保页面的流畅滚动效果。

为什么监听scroll事件不卡顿, 监听wheel时间就卡顿

一些自己的疑惑解答, 为什么把wheel事件改为scroll事件就不卡顿了

事件触发频率
  • scroll 事件:通常在用户滚动页面时触发,触发频率较低,通常在滚动停止时触发一次。
  • wheel 事件:在用户滚动鼠标滚轮时触发,触发频率较高,尤其是在快速滚动时,可能会连续触发多次。
事件处理机制
  • scroll 事件:通常在滚动停止时触发,处理时间较长不会对滚动体验产生太大影响。
  • wheel 事件:由于触发频率高,如果在事件处理函数中执行耗时操作,会阻塞主线程,导致页面卡顿。
浏览器优化
  • 现代浏览器对 scroll 事件进行了优化,即使不使用 passive 选项,也能提供相对流畅的滚动体验。
  • 对于 wheel 事件,浏览器没有类似的优化,因此在处理耗时操作时更容易出现卡顿。
scroll和wheel的适用场景
scroll
  • 回到顶部按钮:当用户滚动到页面的某个位置时,显示“回到顶部”按钮,点击后平滑滚动到页面顶部。

    const backToTopButton = document.getElementById('backToTop');
    window.addEventListener('scroll', () => {
      if (window.scrollY > 300) {
        backToTopButton.style.display = 'block';
      } else {
        backToTopButton.style.display = 'none';
      }
    });
    backToTopButton.addEventListener('click', () => {
      window.scrollTo({ top: 0, behavior: 'smooth' });
    });
    
  • 无限加载:在内容较长的页面(如社交媒体或新闻网站)中,当用户滚动到页面底部时,自动加载更多内容。

    window.addEventListener('scroll', () => {
      const { scrollTop, scrollHeight, clientHeight } = document.documentElement;
      if (scrollTop + clientHeight >= scrollHeight) {
        loadMoreContent(); // 加载更多内容的函数
      }
    });
    
wheel
  • 缩放元素:在图片库中,用户可以通过滚动鼠标滚轮来缩放图片
  • 检测滚动方向
<think>我们被问到关于addEventListener方法的参数。根据MDN文档,addEventListener方法有三个参数:事件类型、事件处理函数、以及一个可选的选项对象或布尔值(用于指定事件捕获或冒泡阶段处理)。 具体参数如下: 1. type: 表示事件类型的字符串(如'click'、'keydown'等) 2. listener: 事件触发时执行的回调函数,该函数会接收到一个Event对象作为参数 3. options: 可选,可以是一个对象或布尔值。如果是一个对象,可以包含以下属性: - capture: 布尔值,表示在捕获阶段触发事件(默认为false,即冒泡阶段) - once: 布尔值,表示事件只触发一次,然后自动移除 - passive: 布尔值,表示事件处理函数会调用preventDefault()(常用于优化滚动性能) 如果是一个布尔值,则等同于指定capture属性。 另外,在旧版本的浏览器中,第三个参数也可以是一个布尔值(表示是否在捕获阶段处理),但现代浏览器支持使用对象来提供更多选项。 下面我们将用代码示例详细说明。</think>### `addEventListener` 方法的参数详解 `addEventListener` 是 JavaScript 中用于处理 DOM 事件的核心方法,完整的语法为: ```javascript target.addEventListener(type, listener[, options]); target.addEventListener(type, listener[, useCapture]); ``` #### 1. **必需参数** | 参数 | 类型 | 描述 | 示例 | |------|------|------|------| | `type` | string | 事件类型(包含 "on" 前缀) | `'click'`, `'keydown'` | | `listener` | function | 事件触发时执行的回调函数 | `(event) => { ... }` | #### 2. **可选参数(二选一)** ##### 选项 A: `useCapture`(布尔值) ```javascript element.addEventListener('click', handler, true); // 捕获阶段 element.addEventListener('click', handler, false); // 冒泡阶段(默认) ``` - `true`: 事件在**捕获阶段**处理 - `false`: 事件在**冒泡阶段**处理(默认值) ##### 选项 B: `options`(配置对象) ```javascript element.addEventListener('scroll', handler, { capture: true, once: true, passive: true, signal: abortController.signal }); ``` | 属性 | 类型 | 默认值 | 描述 | |------|------|--------|------| | `capture` | boolean | `false` | 是否在捕获阶段处理 | | `once` | boolean | `false` | 是否只触发一次后自动移除 | | `passive` | boolean | `false` | 是否调用 `preventDefault()` | | `signal` | AbortSignal | - | 用于取消事件的信号 | --- ### 代码示例与解释 #### 基本用法 ```html <button id="myBtn">点击我</button> <script> const button = document.getElementById('myBtn'); // 1. 基本事件监听 button.addEventListener('click', (event) => { console.log('按钮被点击', event.target); }); // 2. 使用 useCapture document.body.addEventListener('click', () => { console.log('捕获阶段 - body'); }, true); // 捕获阶段处理 // 3. 使用 options 对象 button.addEventListener('click', function handleClick() { console.log('只触发一次'); }, { once: true, // 只触发一次 passive: true // 调用 preventDefault() }); </script> ``` #### 高级用法:AbortSignal ```javascript const controller = new AbortController(); button.addEventListener('click', (event) => { console.log('可取消的事件'); }, { signal: controller.signal }); // 5秒后取消事件监听 setTimeout(() => { controller.abort(); // 移除事件监听 console.log('事件监听已移除'); }, 5000); ``` --- ### 参数详解说明 1. **`listener` 回调函数** - 接收一个 `Event` 对象作为参数 - 函数中的 `this` 指向绑定事件的元素 ```javascript button.addEventListener('click', function(event) { console.log(this === button); // true console.log(event.target); // <button> 元素 }); ``` 2. **`passive` 性能优化** - 特别适用于 `touch` `wheel` 事件 - 告诉浏览器会调用 `event.preventDefault()` ```javascript window.addEventListener('wheel', (e) => { // 即使这里没有调用 preventDefault() // 浏览器也会知道需要等待检查 }, { passive: true }); ``` 3. **`once` 一次性事件** - 自动移除监听器,避免内存泄漏 ```javascript loginButton.addEventListener('click', processLogin, { once: true // 登录后自动移除 }); ``` --- ### 注意事项 1. **匿名函数无法移除**: ```javascript // 错误:无法移除 element.addEventListener('click', () => console.log('clicked')); // 正确:使用具名函数 function handleClick() {...} element.addEventListener('click', handleClick); element.removeEventListener('click', handleClick); ``` 2. **事件顺序**: ```javascript // 捕获阶段:从外向内 parent.addEventListener('click', handler1, true); // 冒泡阶段:从内向外(默认) child.addEventListener('click', handler2); ``` 3. **兼容性**: - `options` 对象在 IE 中支持 - 现代浏览器都支持 `passive` 选项
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值