前言

老掉牙的东西, 主要是想写 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)的更多相关文章

  1. 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 ...

  2. 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 ...

  3. 解决vue中滚轮事件报错 Added non-passive event listener to a scroll-blocking 'mousewheel' event.告警

    参考:https://www.jianshu.com/p/23850d4cade8 参考:让页面滑动流畅得飞起的新特性:Passive Event Listeners

  4. [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() ...

  5. 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 今天在做项目 ...

  6. Unable to preventDefault inside passive event listener

    最近做项目经常在 chrome 的控制台看到如下提示: Unable to preventDefault inside passive event listener due to target bei ...

  7. 滑动时候警告:Unable to preventDefault inside passive event listener

    1 前言 在制作2048时,需要在手机端添加滑动检测事件,然后发现控制台有警告,如下: main2048.js:218 [Intervention] Unable to preventDefault ...

  8. 移动端页面滑动时候警告:Unable to preventDefault inside passive event listener due to target being treated as passive.

    移动端项目中,在滚动的时候,会报出以下提示: [Intervention] Unable to preventDefault inside passive event listener due to ...

  9. [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 ...

  10. IScroll Unable to preventDefault inside passive event listener due to target being treated as passive

    最近两天企业微信下IScroll突然无法滚动了,特别慢,之前好好的,发现主要是有红色的Unable to preventDefault inside passive event listener du ...

随机推荐

  1. UE Spline 样条网格体组件添加碰撞

    最近做的一个功能是通过Spline 生成管道模型. 如下图所示: 遇到的一个问题是需要给生成的管路加上碰撞.其中需要两个重要的步骤: 设置SplineMeshComponent的碰撞预设 找到&quo ...

  2. 图表绘制之RepeatNode的妙用

    图表绘制之RepeatNode的妙用 前言 最近接到许多大屏项目,其中有一个智慧大楼的项目,大致是由3d场景+数据图表组成,需要能监控实时数据.安防 监控.出入统计以及消防安全等功能如下图 但是在开发 ...

  3. Memcache 与 Memcached 的区别

    Memcached 从0.2.0开始,要求PHP版本>=5.2.0,Memcache 要求PHP版本>=4.3. Memcached 最后发布时间为2018-12-24,Memcache ...

  4. [oeasy]python0081_[趣味拓展]ESC键进化历史_键盘演化过程_ANSI_控制序列_转义序列_CSI

    光标位置 回忆上次内容 上次了解了 新的转义模式 \033 逃逸控制字符 escape 这个字符 让字符串 退出标准输出流 进行控制信息的设置 可以设置 光标输出的位置       ​   添加图片注 ...

  5. EF6/EFCore Code-First Timestamp SQL Server

    EF 6和EF Core都包含TimeStamp数据注解特性.它只能用在实体的byte数组类型的属性上,并且只能用在一个byte数组类型的属性上.然后在数据库中,创建timestamp数据类型的列,在 ...

  6. Django 安全之跨站点请求伪造(CSRF)保护

    Django 安全之跨站点请求伪造(CSRF)保护 by:授客 QQ:1033553122 测试环境 Win7 Django 1.11   跨站点请求伪造(CSRF)保护 中间件配置 默认的CSRF中 ...

  7. Kmesh v0.4发布!迈向大规模 Sidecarless 服务网格

    本文分享自华为云社区<Kmesh v0.4发布!迈向大规模 Sidecarless 服务网格>,作者: 云容器大未来. 近日 Kmesh 发布了 v0.4.0 版本,感谢社区的贡献者在两个 ...

  8. selenium获取验证码截图

    获取验证码截图代码: 获取验证码代码: #!/user/bin/env python3 # -*- coding: utf-8 -*- import requests from selenium im ...

  9. C语言中的断言函数assert

    简介 assert 是 C 语言中的一个宏,用于在程序运行时进行条件检查,主要用于调试目的.它在 <assert.h> 头文件中定义,用于验证程序中的假设条件是否成立,如果不成立,程序将打 ...

  10. 【Vue】09 Webpack Part5 Vue组件化开发

    [Vue组件文件打包:Vue-Loader] 复制之前上一个项目 然后在我们的src目录中创建App.vue文件 这个文件就是Vue的模块文件 [建议下载IDEA的Vue.js插件] Vue的模块分为 ...