DOM的事件传递机制 您所在的位置:网站首页 js冒泡事件和捕获事件技巧分析图解 DOM的事件传递机制

DOM的事件传递机制

2023-10-14 23:50| 来源: 网络整理| 查看: 265

DOM的事件传递机制 —— 捕获与冒泡

在用react写form表单的时候,看到代码里面有一句e.preventdafault(),因为不知道这个函数什么意思,就查了一下,才发现和Event事件有关系,并且也逃不掉事件的捕获与冒泡。为此,借此机会顺便了解学习了一些相关知识。

文章参考: https://blog.techbridge.cc/2017/07/15/javascript-event-propagation/https://yeungkc.com/2019/06/14/native-js-event/#preventDefault Demo Document google.com

其DOM树基本结构如下:

avatar

在Event中,一个有三个阶段(Phase),分别是捕获、目标和冒泡。

而在Event的参数当中,也有参数eventPhase是用来专门表示这三个事件的,通过这个参数我们就可以知道事件处于哪个阶段了。

// Webside: // https://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-interface // PhaseType const unsigned short CAPTURING_PHASE = 1; const unsigned short AT_TARGET = 2; const unsigned short BUBBLING_PHASE = 3;

DOM在事件传递中,可以概括为三个阶段:

首先是从根节点出发,到达目标节点,若此过程中触发了事件,则被称为捕获(CAPTURING)事件;当节点到达定义点击事件的目标节点时,此时则处于目标(TARGET)阶段。而在目标节点触发事件后,最后就会一路往上回到根节点,此时若有事件发生,则是冒泡(BUBBLING)事件。

附(W3C讲Event flow的图):

avatar

小总结:

先捕获后冒泡

如何决定当前处于捕获阶段还是冒泡阶段?

通过addEventListener这个函数定义事件的类型和监听阶段。

target.addEventListener(type, listener[, useCapture]);

其中,这个函数的第三个参数就是用来表示监听的阶段, 若true则表示处于捕获阶段;false表示冒泡阶段。

默认是冒泡阶段。

实际案例 // DOMEvent.js const get = (id) => document.getElementById(id); const list = get('list') const listItem = get('list-item') const listItemLink = get('list-item-link') // list的捕获 list.addEventListener('click', (e) => { console.log('我是list的捕获事件') }, true) // list的冒泡 list.addEventListener('click', (e) => { console.log('我是list的冒泡事件') }, false) // listItem的捕获 listItem.addEventListener('click', (e) => { console.log('我是listItem的捕获事件') }, true) // listItem的冒泡 listItem.addEventListener('click', (e) => { console.log('我是listItem的冒泡事件') }, false) // listItemLink的捕获 listItemLink.addEventListener('click', (e) => { console.log('我是listItemLink的捕获事件') }, true) // listItemLink的冒泡 listItemLink.addEventListener('click', (e) => { console.log('我是listItemLink的冒泡事件') }, false)

通过Chrome开发者工具,可以看到结果如下:

DOMEvent.js:16 我是list的捕获事件,phase value: 1 DOMEvent.js:26 我是listItem的捕获事件,phase value: 1 DOMEvent.js:36 我是listItemLink的捕获事件,phase value: 2 DOMEvent.js:41 我是listItemLink的冒泡事件,phase value: 2 DOMEvent.js:31 我是listItem的冒泡事件,phase value: 3 DOMEvent.js:21 我是list的冒泡事件,phase value: 3

由此可以看出和刚刚的结论是符合的,先进行捕获事件,随后到达target,最后就从下往上走。但这里需要的是:

在目标事件这里,不管addEventListenter的第三个参数是true还是false,都没有先捕获后冒泡的说法,既这里会按顺序执行。

下面看看我们将顺序倒转之后代码和结果:

// DOMEvent.js const get = (id) => document.getElementById(id); const list = get('list') const listItem = get('list-item') const listItemLink = get('list-item-link') // list的冒泡 list.addEventListener('click', (e) => { console.log('我是list的冒泡事件,phase value:', e.eventPhase) }, false) // list的捕获 list.addEventListener('click', (e) => { console.log('我是list的捕获事件,phase value:', e.eventPhase) }, true) // listItem的冒泡 listItem.addEventListener('click', (e) => { console.log('我是listItem的冒泡事件,phase value:', e.eventPhase) }, false) // listItem的捕获 listItem.addEventListener('click', (e) => { console.log('我是listItem的捕获事件,phase value:', e.eventPhase) }, true) // listItemLink的冒泡 listItemLink.addEventListener('click', (e) => { console.log('我是listItemLink的冒泡事件,phase value:', e.eventPhase) }, false) // listItemLink的捕获 listItemLink.addEventListener('click', (e) => { console.log('我是listItemLink的捕获事件,phase value:', e.eventPhase) }, true)

结果:

DOMEvent.js:21 我是list的捕获事件,phase value: 1 DOMEvent.js:31 我是listItem的捕获事件,phase value: 1 DOMEvent.js:36 我是listItemLink的冒泡事件,phase value: 2 DOMEvent.js:41 我是listItemLink的捕获事件,phase value: 2 DOMEvent.js:26 我是listItem的冒泡事件,phase value: 3 DOMEvent.js:16 我是list的冒泡事件,phase value: 3

可以看到listItemLink部分会按照代码顺序执行。

因此,最后得出我们结论

先捕获,后冒泡当事件传递到target时,无捕获冒泡之分 stopPropagation、prenventDefault、return false stopPropagation()

当你想要取消事件传递时,就可以选择使用这个函数。函数加在哪里,事件就会在哪里停止传递。

const get = (id) => document.getElementById(id); const list = get('list') const listItem = get('list-item') const listItemLink = get('list-item-link') // list的捕获 list.addEventListener('click', (e) => { console.log('我是list的捕获事件,phase value:', e.eventPhase) }, true) // list的冒泡 list.addEventListener('click', (e) => { console.log('我是list的冒泡事件,phase value:', e.eventPhase) }, false) // listItem的捕获 listItem.addEventListener('click', (e) => { // e.stopPropagation() console.log('我是listItem的捕获事件,phase value:', e.eventPhase) }, true) // listItem的冒泡 listItem.addEventListener('click', (e) => { // e.preventDefault() // 链接不跳转 e.stopPropagation() // 跳转还在,但不进行冒泡了 console.log('我是listItem的冒泡事件,phase value:', e.eventPhase) }, false) // listItemLink的捕获 listItemLink.addEventListener('click', (e) => { console.log('我是listItemLink的捕获事件,phase value:', e.eventPhase) }, true) // listItemLink的冒泡 listItemLink.addEventListener('click', (e) => { console.log('我是listItemLink的冒泡事件,phase value:', e.eventPhase) }, false)

结果如下:

DOMEvent.js:16 我是list的捕获事件,phase value: 1 DOMEvent.js:27 我是listItem的捕获事件,phase value: 1 DOMEvent.js:39 我是listItemLink的捕获事件,phase value: 2 DOMEvent.js:44 我是listItemLink的冒泡事件,phase value: 2 DOMEvent.js:34 我是listItem的冒泡事件,phase value: 3

可以看到,当我在listItem的冒泡过程调用了这个函数之后,事件就没有再继续传递下去了。

但是这里需要注意,这里说的事件传递被停止,是指事件不会被传递到下一个节点,若在同一个节点中不止一个listener,还是会被执行的。

例如:在上述代码中添加这段代码:

// listItem 冒泡事件2 listItem.addEventListener('click', (e) => { console.log('我是listItem的冒泡事件2 ,phase value:', e.eventPhase) })

最终结果则是:

DOMEvent.js:16 我是list的捕获事件,phase value: 1 DOMEvent.js:27 我是listItem的捕获事件,phase value: 1 DOMEvent.js:44 我是listItemLink的捕获事件,phase value: 2 DOMEvent.js:49 我是listItemLink的冒泡事件,phase value: 2 DOMEvent.js:34 我是listItem的冒泡事件,phase value: 3 DOMEvent.js:39 我是listItem的冒泡事件2 ,phase value: 3

可以看到,另一个listener的监听事件还是会被正常执行。

假如你需要同一层级的其它listener也不会被执行,可以改用e.stopImmediatePropagation();这样,其余listener也会被禁止。

prenventDefault()

它的作用是取消浏览器的预设行为,但是并不会对事件传递造成影响。但是一旦开始了prebentDefault()函数,在它往下传递的事件当中也会有效果。

例如:在listItem中添加prenventDefault(),可以看到此时a链接已经不能正常跳转了,但是,此时Chrome的输出栏里仍然可以看到这样的结果:

DOMEvent.js:16 我是list的捕获事件,phase value: 1 DOMEvent.js:27 我是listItem的捕获事件,phase value: 1 DOMEvent.js:44 我是listItemLink的捕获事件,phase value: 2 DOMEvent.js:49 我是listItemLink的冒泡事件,phase value: 2 DOMEvent.js:34 我是listItem的冒泡事件,phase value: 3 DOMEvent.js:21 我是list的冒泡事件,phase value: 3

但是这里我有一个疑问 —— 就是我是在listItem的冒泡阶段停止这个事件的,为什么还是不能跳转呢?

按理 #list (捕获) -> #listItem(捕获) -> listItemLink(捕获) -> listItemLink(冒泡) -> listItem(冒泡) -> list(冒泡),按这样的顺序进行,那么不应该也可以进行跳转吗?

return false

它可以看作前面两个函数的综合,当你调用它时,它既可以阻止传递同时还可以阻止默认行为。它在内部实际进行了三件事:

event.preventDefault();event.preventDefault();停止回调函数执行并立即返回。


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

      专题文章
        CopyRight 2018-2019 实验室设备网 版权所有