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 ...
随机推荐
- 推荐几款个人喜欢的IDEA开发工具主题【更舒适的开发】
IDEA,全称 IntelliJ IDEA ,是Java语言的集成开发环境,IDEA在业界被公认为是最好的 java 开发工具之一,尤其在智能 代码助手.代码自动提示.重构.J2EE支持. Ant . ...
- 解决004--Loading local data is disabled; this must be enabled on both the client and server sides问题及解决
因为下载了SQLyog的ultimate版本,现在就可以导入外部的数据了.有着之前使用insert into插入语句来添加近50条有着大概10个字段的记录的经历之后,本着能够导入现成的数据就导入的想法 ...
- Mysql函数1-IFNULL
IFNULL函数用于判断参数值是null时则返回指定内容. 原本 select goods_base_name,goods_id from goods where goods_id in (6,7,8 ...
- Python编写html文件
背景:部门需要发送周报.月报,每次都需要去数据库导出数据整理统计发送给领导,人工操作显得繁琐且费时间. 1.可以定时用python将数据库查询数据结果写成html文件,达到浏览器访问的效果,定时发送给 ...
- 【Binary】XShell6 无法使用的解决办法
感谢博主的解决方案: https://www.cnblogs.com/pinkpolk/articles/13554445.html 首先需要安装VsCode,并且安装一个[Hex Editor]的插 ...
- 【Vue】Re01 理论概念和入门上手
一.Vue概述 什么是渐进式?1.把Vue作应用的一部分嵌套项目中2.如果完全抛弃其他组件和框架,Vue又具有丰富的生态和库莱支持3.Core + Router + VueX 满足项目绝大多数的需求- ...
- 【Layui】05 进度条 Progress
文档地址: https://www.layui.com/doc/element/progress.html 演示案例: <div class="layui-progress" ...
- Jupyter Lab和Jupyter Notebook的区别
JupyterLab与Jupyter Notebook:详细比较 简介 Jupyter Notebook是一个开源的Web应用程序,允许用户创建和共享包含实时代码.方程.可视化和解释性文本的文档.Ju ...
- 智能机器人(双足机器人、四足机器人、人形机器人humanoid)与自动驾驶技术/FSD(Full Self-Drive)“完全自动驾驶”在技术领域的相关性?
前文: https://www.cnblogs.com/devilmaycry812839668/p/18079439 前文中已经说了,对于能力强大的机器人公司来说,软件和AI技术并不是难点,真正的难 ...
- PEP 703作者给出的一种no-GIL的实现——python3.9的nogil版本
PEP 703的内容是什么,意义又是什么呢? 可以说python的官方接受的no-GIL提议的PEP就是PEP 703给出的,如果GIL帧的从python中移除那么可以说对整个python生态圈将有着 ...