JS中的事件顺序(事件捕获与冒泡)

问题

如果一个元素和它的祖先元素注册了同一类型的事件函数(例如点击等), 那么当事件发生时事件函数调用的顺序是什么呢?

比如, 考虑如下嵌套的元素:

-----------------------------------
| outer                           |
|   -------------------------     |
|   |inner                  |     |
|   -------------------------     |
|                                 |
-----------------------------------

两个元素都有onclick的处理函数. 如果用户点击了inner, innerouter上的事件处理函数都会被调用. 但谁先谁后呢?

两个模型

在刚刚过去的那些糟糕年代, Netscape和M$对此有不同的看法.

Netscape认为outer上的处理函数应该先被执行. 这被称作event capturing.

M$则认为inner上的处理函数具有执行优先权. 这被叫做event bubbling.

两种看法针锋相对

事件捕获(event capturing)

当使用事件捕获时

               | |
---------------| |-----------------
| outer        | |                |
|   -----------| |-----------     |
|   |inner     \ /          |     |
|   -------------------------     |
|        Event CAPTURING          |
-----------------------------------

outer上的事件处理器先触发, 然后是inner上的

事件冒泡(event bubbling)

               / ---------------| |-----------------
| outer        | |                |
|   -----------| |-----------     |
|   |inner     | |          |     |
|   -------------------------     |
|        Event BUBBLING           |
-----------------------------------

与事件捕获相反, 当使用事件冒泡时, inner上的事件处理器先被触发, 其后是outer上面的

W3C 模型

W3C标准则取其折中方案. W3C事件模型中发生的任何事件, 先(从其祖先元素window)开始一路向下捕获, 直到达到目标元素, 其后再次从目标元素开始冒泡.

          1. 先从上往下捕获
                  |
                 | |  / -----------------| |--| |-----------------
| outer          | |  | |                |
|   -------------| |--| |-----------     |
|   |   inner    \ /  | |          |     |
|   |                  |           |     |
|   |   2. 到达目标元素后从下往上冒泡|     |
|   --------------------------------     |
|        W3C event model                 |
------------------------------------------

而你作为开发者, 可以决定事件处理器是注册在捕获或者是冒泡阶段. 如果addEventListener的最后一个参数是true, 那么处理函数将在捕获阶段被触发; 否则(false), 会在冒泡阶段被触发.

例如如下的代码:

        var selector = document.querySelector.bind(document);
        selector(‘div.outer‘).addEventListener(‘click‘, (e) => {
            selector(‘p:first-of-type‘).textContent += ‘outer clicked! ‘
        }, true)
        selector(‘div.inner‘).addEventListener(‘click‘, (e) => {
            selector(‘p:first-of-type‘).textContent += ‘inner clicked! ‘
        }, false)
        document.addEventListener(‘click‘, (e) => {
            selector(‘p:first-of-type‘).textContent += ‘document clicked! ‘
        }, true)

当点击inner元素时, 如下事情发生了:

  1. 点击事件开始于捕获阶段. 在此阶段, 浏览器会在inner的所有祖先元素上查找点击事件处理函数(从window开始).
  2. 结果找到了2个, 分别在documentouter上面, 而且这两个事件处理函数的useCapture选项为true, 说明它们是被注册在捕获阶段的. 于是, documentouter的点击处理函数被执行了.
  3. 继续向下寻找, 直到达到inner元素本身. 捕获阶段就此结束. 此时进入冒泡阶段, inner上的事件处理器得到执行.
  4. 事件命中目标元素后开始向上冒泡, 一路查找是否有注册了冒泡阶段的祖先元素上的事件处理器. 由于没有找到, 因此什么也没发生.

最后的结果是:

如果我们把祖先元素的事件处理器注册在冒泡阶段的话(addEventListeneruseCapture选项为false):

        var selector = document.querySelector.bind(document);
        selector(‘div.outer‘).addEventListener(‘click‘, (e) => {
            selector(‘p:first-of-type‘).textContent += ‘outer clicked! ‘
            console.log(e);
        }, false)
        selector(‘div.inner‘).addEventListener(‘click‘, (e) => {
            selector(‘p:first-of-type‘).textContent += ‘inner clicked! ‘
            console.log(e);
        }, false)
        document.addEventListener(‘click‘, (e) => {
            selector(‘p:first-of-type‘).textContent += ‘document clicked! ‘
        }, false)

结果则是:

传统模型

element.onclick = function(){}

将被注册在冒泡阶段.

事件冒泡的应用

例如: 当点击时的默认函数

如果在document上注册一个点击函数:

document.addEventlistener(‘click‘, (e) => {}, false)

那么任何元素上的点击事件最后都会冒泡到这个事件处理器上并触发函数 - 除非前面的事件处理函数阻止了冒泡(e.stopPropogation(), 在这种情况下事件不会继续向上冒泡)

注意: e.stopPropagation()只能阻止事件在冒泡阶段的向上传播. 如果被点击元素的祖先元素有注册在捕获阶段的事件处理器:

ancestorElem.addEventListner(‘click‘, (e) => {
    // do something...
    }, true)

那么该祖先元素上的事件处理器照样会在捕获阶段被触发.

因此, 你可以在document上设置这么一个处理函数, 当页面上的任何元素被点击时, 这个处理函数就被会触发. 一个实用的例子就是下拉菜单: 当点击文档上除下拉菜单本身时任意一处时, 下拉菜单会被隐藏.

在冒泡或者捕获阶段, e.currentTarget指向当前事件处理函数所附着的元素. 你也可以用事件处理函数内的this取而代之.

M$模型的麻烦

在M$模型中, 没有对e.currentTarget的支持, 更糟糕的是, this也不指向当前的HTML元素.

原文地址:https://www.cnblogs.com/jlfw/p/12581129.html

时间: 2024-08-21 12:10:25

JS中的事件顺序(事件捕获与冒泡)的相关文章

JS中一些常用的事件(笔记)

window.onload事件:当文档和其所有外部资源(如图片)完全加载并显示给用户时就会触发它. window.onload = function (){ //当加载完当前页面和其所有外部资源(如图片)后,执行这个函数 } window.onunload事件:当用户离开当前页面时会触发该事件 window.onunload = function (){ //离开该页面时执行该函数 } event对象:该对象代表了当前事件的状态,并且只有在事件发生的过程中才生效.对象中存放的是键盘按键的状态.鼠

js中的点击事件(click)的实现方式

<!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8"> <title>js中的点击事件(click)的实现方式</title> </head> <body> <!-- 第三种方式--> <button id="btn" onclick="threeFn()

javascript--函数的声明及调用/JS中代码执行顺序

[函数的声明及调用] 1.函数声明格式: function 函数名(参数1,参数2,参数3--){ //函数体 return 结果: } 函数调用的格式: 函数名(参数1的值,参数2的值,--): 事件调用:事件名=函数名(): 2.函数声明的几点强调: ① 函数的声明,必须符合小驼峰法则(首字母小写,之后每个单词首字母大写): ② 参数的列表,可以有参数,可以无参数.分别称为有参函数,无参函数: ③ 声明函数时的参数列表,称为"形参列表"(变量的名): 调用函数时的参数列表,称为&q

JS中的异步以及事件轮询机制

一.JS为何是单线程的? JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事.那么,为什么JavaScript不能有多个线程呢?这样能提高效率啊.(在JAVA和c#中的异步均是通过多线程实现的,没有循环队列一说,直接在子线程中完成相关的操作) JavaScript的单线程,与它的用途有关.作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM.这决定了它只能是单线程,否则会带来很复杂的同步问题.比如,假定JavaScript同时有两个线程,一个

js中 关于DOM的事件操作

一.JavaScript的组成 JavaScript基础分为三个部分: ECMAScript:JavaScript的语法标准.包括变量.表达式.运算符.函数.if语句.for语句等. DOM:文档对象模型,操作网页上的元素的API.比如让盒子移动.变色.轮播图等. BOM:浏览器对象模型,操作浏览器部分功能的API.比如让浏览器自动滚动. 二.事件 JS是以事件驱动为核心的一门语言. 事件的三要素 事件的三要素:事件源.事件.事件驱动程序. 比如,我用手去按开关,灯亮了.这件事情里,事件源是:手

JS中常用的一些事件

常用的事件 onclick     鼠标单击 ondblclick  鼠标双击 onkeyup   按下并释放键盘上的一个键时触发 onchange   文本内容或下拉菜单中的选项发生改变 onfocus    获得焦点,表示文本框等获得鼠标光标 onblur  失去焦点,表示文本框等失去鼠标光标 onmouseover 鼠标悬停,即鼠标停留在图片等的上方 onmouseout  鼠标移出,即离开图片等所在的区域 onload3     网页文档加载事件 onunload    关闭页面时 on

关于js中的表单事件

表单结构如下所示: <form > <input type="text" name="txt" id="txt" value="" /> <input type="submit" name="sub" id="sub" value="提交" /> <input type="button"

js中关于动态添加事件,不能使用循环变量的问题

在编写事件的时候,我们难免会遇到以下这种情况:<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Title</title></head><body> <ul> <li>内容一</li> <li>内容二</li> <li&g

Js中获取键盘的事件

使用方法: <script type="text/javascript" language=JavaScript charset="UTF-8"> document.onkeydown=function(event){ var e = event || window.event || arguments.callee.caller.arguments[0]; if(e && e.keyCode==27){ // 按 Esc //要做的事情