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 ...
随机推荐
- 解决方案 | Windows 验证账号出现 0x80190001错误解决
一.问题描述 点击windows开始→账户→更改账户设置→验证,出现下面的错误. 二.解决方法 网上流行的是这个方法,https://blog.csdn.net/qq_36393978/article ...
- 拥抱未来:GPT-4将如何改变我们的世界
随着人工智能技术的迅猛发展,我们正迎来一个全新的智能时代.在这个时代的前沿,GPT-4作为开拓者和领航者,正在重新定义人机交互.创意创新和个性化服务的标准.无论是在商业领域.教育场景还是科研领域,GP ...
- oeasy 教您玩转linux 010304 图形界面 xfce
我们来回顾一下 上一部分我们都讲了什么? 讲了文件管理器和命令行终端互相交互 用命令nautilus在文件管理器打开某路径 这次我们来看看 图形用户界面(GUI)的情况 图形界面和发行版的关系 一个发 ...
- [oeasy]python0022_框架标题的制作_banner_结尾字符串_end
结尾字符串(end) 回忆上次内容 python3 的程序是一个 5.3M 的可执行文件 python3 里面存的是 cpu 指令 可以执行的那种 我们可以把指令对应的汇编找到 ...
- 寒假训练——vj题解
B - B M 算日期 M 是一位数学高手,今天他迎来了 Kita 的挑战.Kita 想让 BM 算出这几年内有多少个闰年. BM 觉得这问题实在太简单了,于是 Kita 加大了难度. 他先给出第一个 ...
- nacos配置&gateway配置服务发现一直报500
项目场景: 这两天不是一直在搞简化配置.使用公共配置.我的服务可以通过网关访问这几个任务嘛,也是不断地踩坑补知识才总算把这几个任务都搞好了,下面就是记录过程中遇到的问题. 使用公共配置 因为发现项目使 ...
- Windows/Linux上更新Nessus插件
破解版:http://ximcx.cn/post-151.html 官网文档: https://docs.tenable.com/sccv/Content/OfflineNessusPluginUpd ...
- 【转载】解决WSL中Debian显示中文乱码的问题
---------------- 版权声明:本文为CSDN博主「捕鲸叉」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明.原文链接:https://blog.csdn. ...
- Mongolia地区民间风俗的一些理解
声明:本文的内容为自己学习历史后的一些个人理解,其中内容的真实性并未考证. 总所周知,Mongolia地区有内外之分现在,但是以前均为我国领土,后来由于种种历史原因导致外Mongolia分离了出去,这 ...
- deepin国产操作系统 nvidia-docker2 的安装
====================================== 平时偶尔使用deepin系统,突然有个 nvidia-docker 的程序需要运行,平时工作都是在用Ubuntu,所以对d ...