DOM – Event Listener (bubble, capture, passive, custom event)
前言
老掉牙的东西, 主要是想写 passive, 随便也写一点 bubble, capture 和 custom event 吧.
Bubble
Dom 监听事件是会冒泡的. 什么意思 ?

上图有 2 个 box, parent box 嵌套 child box. 假设 parent 和 child 都有事件监听.
const parentBox = document.querySelector(".parent-box")!;
const childBox = document.querySelector(".child-box")!;
parentBox.addEventListener('click', () => console.log('clicked'));
childBox.addEventListener('click', () => console.log('clicked'));
请问, 我现在点击 child box. parent box 的事件会触发吗?
答案是会的. 因为事件会冒泡 (注: 它的意思不是你点击了 child 同时也点击了 parent 哦, 只是点击了 child, 然后 child 事件触发, 然后冒泡到了 parent)
冒泡可以被阻止. 哪个 child 被点击了也可以被判断出来
parentBox.addEventListener("click", (event) => {
console.log(event.currentTarget); // the listening element : parent
console.log(event.target); // the clicked element : could be child or parent
});
childBox.addEventListener("click", (event) => {
event.stopPropagation(); // 阻止冒泡
event.stopImmediatePropagation(); // 顺便讲一下这个, 它除了 stopPropagation 外, 当前 element 后续的 listener 也不会触发
});
假设 child stopPropagation 那么 parent 就不会触发了.
注1:
假设 child binding 了 2 个相同的 event(e.g. mousedown),只要其中一个 stop bubble 就够了。
如果 event 是不相同的,比如一个 mousedown 一个 mouseup,那么 stop bubble 只会影响有 stop buddle 的那一个。
注2:
有些 event 是不会 buddle 的,比如 mouseleave
Capture
首先事件触发的顺序是下面这样的

3 层 element. 其实是最外面开始触发的. 但是我们平常感觉好像只有 3,4,5 没有 1, 2 丫.
要触发 1, 2 就需要用到 capture 了.
上面 bubble 的例子多一层 grandparent

监听
const grandparentBox = document.querySelector(".grandparent-box")!;
const parentBox = document.querySelector(".parent-box")!;
const childBox = document.querySelector(".child-box")!;
grandparentBox.addEventListener("click", () => {
console.log(5);
});
parentBox.addEventListener("click", () => {
console.log(4);
});
childBox.addEventListener("click", () => {
console.log(3);
});
parentBox.addEventListener(
"click",
() => {
console.log(2);
},
{ capture: true }
);
grandparentBox.addEventListener(
"click",
() => {
console.log(1);
},
{ capture: true }
);
我们可以把监听的顺序倒过来写。最终 console.log 顺序依然是 1 2 3 4 5. 其原理就是上面画的图, 事件触发是从最外面开始的, 只是我们一般上不适用 capture 所以会误以为只有 3,4,5 里到外.
stopPropagation
grandparentBox.addEventListener(
"click",
(e) => {
console.log(1);
e.stopPropagation();
},
{ capture: true }
);
假如我在最外面 capture 就 stopPropagation 那么后续的 2,3,4,5 都不会触发了. 所以它的名字不叫 stopBubble. 因为 1,2 是 parent to child 并不是冒泡. 但是 stopPropagation 是一样可以阻止 "进入" 内层的.
使用场景
我好像只有在监听 document scroll 的时候会用到 capture.
removeEventListener
document.addEventListener('scroll', handleScroll, {
passive: true,
capture: true,
});
document.removeEventListener('scroll', handleScroll, { capture: true });
如果 add 有 capture, 那么 remove 也要声明是 capture 哦, 不然是匹配不到 remove 不掉的.
Passive
参考: Improving scroll performance with passive event listeners
passive 通常用来优化体验. 比如 wheel event.
wheel event 有可能会 preventDefault 去阻止游览器滚动. 所以游览器需要等待 wheel event 执行完了才可以去滚动页面.
但是呢, 如果 wheel event 跑了很多代码, 但是没有要 preventDefault 的话, 那么这个等待就很没有意义了. 用户会感觉滚动反应慢了半拍.
于是就有了 passive.
document.addEventListener("wheel", (e) => {
console.log("wheel");
e.preventDefault();
});
document.addEventListener("scroll", () => {
console.log("scroll");
});
效果

wheel by defualt 就是 passive 的. passive 的意思就是你不能 preventDefault. 于是游览器就可以直接去滚动页面而不需要等待 wheel event 结束 (因为你不可能阻止游览器滚动了)
当加上 passive 后, scroll 就被 prevent 掉了
document.addEventListener(
"wheel",
(e) => {
console.log("wheel");
e.preventDefault(); // 有效果了
},
{ passive: false }
);
效果

页面不会滚动, scroll event 也不会触发了.
Custom Event
自定义 event 是用来扩展游览器 build-in event 的. 比如游览器有 click, dblclick 但是没有 tripleclick.
那我们就可以自己扩展一个出来.
监听和触发长这样:
document.addEventListener('tripleclick', () => console.log('Hello World'));
document.dispatchEvent(new CustomEvent('tripleclick'));
要什么时候触发 tripleclick 逻辑就由我们来掌控实现了. 比如监听 click event 在一个时间内触发 3 次, 那么就 dispatch tripleclick
Custom Event 默认是不冒泡的
document.dispatchEvent(new CustomEvent('tripleclick', { bubbles: true }));
composed
composed 是用在阻止 event 跨越 Shadow DOM 的。

container 被设置了一个 Shadow DOM。
h1.addEventListener('click', () => {
h1.dispatchEvent(new CustomEvent('test', { bubbles: true, composed: false }));
});
h1.addEventListener('test', () => {
console.log('h1 ok');
});
parent.addEventListener('test', () => {
console.log('parent ok');
});
container.addEventListener('test', () => {
console.log('container ok');
});
document.body.addEventListener('test', () => {
console.log('body ok');
});

h1 dispatch event,composed: false。container 和 body 收不到,因为在 Shadow DOM 外,h1 和 parent 就 ok。
passing data
event 需要能 passing data, 比如 Keyboard event 传递 key, mouse event 传递坐标.
通过 detail 属性来传值
document.addEventListener('statechange', event => {
// get data from detail
console.log('value', event.detail.value);
});
setTimeout(() => {
document.dispatchEvent(
new CustomEvent('statechange', {
// 通过 detail passing data
detail: {
value: 'Hello World',
},
bubbles: true,
})
);
}, 2000);
想看 TypeScript 版本, 移步这里
移除 element 会自动移除事件监听?
首先,要搞清楚一件事 -- element 对象不一定要插入到 DOM Tree (document) 里面。
const button = document.createElement('button');
button.textContent = 'click me';
button.addEventListener('click', () => console.log('click'));
setInterval(() => {
button.click();
}, 1000);
上面单纯用 JavaScript 创建了一个 button 对象,然后添加了 event listener 并且模拟 click 触发了这个 event。
完全跟 DOM Tree 无关。
接着我们把它插入 document,然后再移除
document.body.appendChild(button);
setTimeout(() => {
button.remove();
}, 3000);
效果

从头到尾,事件监听和发布都不受插入和移除 DOM Tree 影响。
结论:事件是绑定在 element 对象上,这个对象是否插入 DOM Tree 不是重点,只有当这个 element 没用被任何地方引用,被垃圾回收的同时,它的事件才会一起被带走,其它情况我们要自己 remove event listener。
DOM – Event Listener (bubble, capture, passive, custom event)的更多相关文章
- Added non-passive event listener to a scroll-blocking 'touchmove' event. Consider marking event handler as 'passive' to make the page more responsive
Vue控制台警告: Added non-passive event listener to a scroll-blocking 'touchmove' event. Consider markin ...
- vue中,解决chrome下,的warning, Added non-passive event listener to a scroll-blocking ‘mousewheel‘ event 问题
写项目的时候,Chrome 提醒: [Violation] Added non-passive event listener to a scroll-blocking 'mousewheel' eve ...
- 解决vue中滚轮事件报错 Added non-passive event listener to a scroll-blocking 'mousewheel' event.告警
参考:https://www.jianshu.com/p/23850d4cade8 参考:让页面滑动流畅得飞起的新特性:Passive Event Listeners
- [Intervention] Unable to preventDefault inside passive event listener due to target being treated as passive. See https://www.chromestatus.com/features/5093566007214080
相信如果用谷歌浏览器做移动端页面的时候 用touch事件的时候应该遇到过这个东东吧 documet.addEventListener("touchstart",function() ...
- Unable to preventDefault inside passive event listener due to target being treated as passive
Unable to preventDefault inside passive event listener due to target being treated as passive 今天在做项目 ...
- Unable to preventDefault inside passive event listener
最近做项目经常在 chrome 的控制台看到如下提示: Unable to preventDefault inside passive event listener due to target bei ...
- 滑动时候警告:Unable to preventDefault inside passive event listener
1 前言 在制作2048时,需要在手机端添加滑动检测事件,然后发现控制台有警告,如下: main2048.js:218 [Intervention] Unable to preventDefault ...
- 移动端页面滑动时候警告:Unable to preventDefault inside passive event listener due to target being treated as passive.
移动端项目中,在滚动的时候,会报出以下提示: [Intervention] Unable to preventDefault inside passive event listener due to ...
- [Intervention] Unable to preventDefault inside passive event listener due to target being treated as passive.
1.滑动时候警告[Intervention] Unable to preventDefault inside passive event listener due to target being tr ...
- IScroll Unable to preventDefault inside passive event listener due to target being treated as passive
最近两天企业微信下IScroll突然无法滚动了,特别慢,之前好好的,发现主要是有红色的Unable to preventDefault inside passive event listener du ...
随机推荐
- 【java深入学习第1章】深入探究 MyBatis-Spring 中 SqlSession 的原理与应用
前言 在使用 MyBatis 进行持久层开发时,通常会与 Spring 框架集成,以便更好地管理事务和依赖注入.在 MyBatis-Spring 集成中,SqlSession 是一个非常重要的概念.本 ...
- EasyBPM进销存之物料管理
本文是EasyBPM平台实现进销存系列中的一篇,主要讲述物料的相关的管理. 在ERP系统中,"物料"一词有着广泛的含义,它是所有产品.半成品.在制品.原材料.配套件.协作件.易耗品 ...
- mysql 删除数据表报错 表删除时 Cannot delete or update a parent row: a foreign key constraint fails 异常处理
mysql 删除数据表报错 表删除时 Cannot delete or update a parent row: a foreign key constraint fails 异常处理 MySQL报错 ...
- C# 开发技巧 轻松监控方法执行耗时
前言 MethodTimer.Fody 是一个功能强大的库,可以用于测量 .NET 应用程序中的方法的执行时间.允许你在不修改代码的情况下,自动地测量和记录方法的执行时间. 这个工具是基于.NET的 ...
- 概述C#中各种类型集合的特点
在C#中,集合是用于存储和操作一组数据项的数据结构.这些集合通常位于 System.Collections 和 System.Collections.Generic 命名空间中.下面我将概述C#中几种 ...
- postfix&dovecot搭建邮件服务器
本篇参考 https://blog.51cto.com/5001660/2377785和小翔博客https://www.liuyixiang.com/post/113927.html. 邮件发送和接受 ...
- 在Python中使用sqlalchemy来操作数据库的几个小总结
在探索使用 FastAPI, SQLAlchemy, Pydantic,Redis, JWT 构建的项目的时候,其中数据库访问采用SQLAlchemy,并采用异步方式.数据库操作和控制器操作,采用基类 ...
- GPG公钥的删除与注销
参考: 如何在 Gitee 上使用 GPG 我们通过在本地主机保存GPG私钥,然后在Gitee或Github上保存GPG公钥的方式来实现对git的commit和tag操作的签名. 当GPG公私秘钥对作 ...
- 并行化强化学习 —— 初探 —— 并行reinforce算法的尝试 (上篇:强化学习在多仿真环境下单步交互并行化设计的可行性)
强化学习由于难收敛所以训练周期较长,同时由于强化学习在训练过程中起训练数据一般都为实时生成的,因此在训练的同时算法还需要生成待训练的数据,强化学习算法的基本架构可以视作下图:(取自:深度学习中使用Te ...
- Ubuntu Firefox浏览器播放视频报错,提示“需要安装所需的视频编码器”——解决方法:安装视频解码器
给电脑重新做了一个Ubuntu的系统,安装系统的时候没有选择安装第三方软件,结果开机进系统打开firefox浏览器看个电影报错,提示"需要安装所需的视频编码器",效果如下: 解决方 ...