JavaScript 事件捕获冒泡与捕获详情


Posted in Javascript onNovember 11, 2021

一、事件流

JavaScript中,事件流指的是DOM事件流。

1、概念

事件的传播过程即DOM事件流。
事件对象在 DOM 中的传播过程,被称为“事件流”。
举个例子:开电脑这个事,首先你是不是得先找到你的电脑,然后找到你的开机键,最后用手按下开机键。完成开电脑这个事件。这整个流程叫做事件流。

2、DOM事件流

DOM事件,也是有一个流程的。从事件触发开始到事件响应是有三个阶段。

  • 事件捕获阶段
  • 处于目标阶段
  • 事件冒泡阶段

上面例子中,开电脑这个事件的过程就像JavaScript中的事件流,找开机键这个过程就是 事件捕获 的过程,你找到开机键后,然后用手按开机键,这个选择用手去按的过程就是 处于目标阶段 按下开机按钮,电脑开始开机这也就是 事件的冒泡。 顺序为先捕获再冒泡。

了解了事件源,让我们看看它的三个过程吧!

事件捕获:

注:由于事件捕获不被旧版本浏览器(IE8 及以下)支持,因此实际中通常在冒泡阶段触发事件处理程序。

事件捕获处于事件流的第一步,
DOM事件触发时(被触发DOM事件的这个元素被叫作事件源),浏览器会从根节点开始 由外到内 进行事件传播。即事件从文档的根节点流向目标对象节点。途中经过各个层次的DOM节点,最终到目标节点,完成事件捕获。

目标阶段:

当事件到达目标节点的,事件就进入了目标阶段。事件在目标节点上被触发。
就是事件传播到触发事件的最底层元素上。

事件冒泡:

事件冒泡与事件捕获顺序相反。事件捕获的顺序是从外到内,事件冒泡是从内到外。
当事件传播到了目标阶段后,处于目标阶段的元素就会将接收到的时间向上传播,就是顺着事件捕获的路径,反着传播一次,逐级的向上传播到该元素的祖先元素。直到window对象。

看一个例子,点击 box3 会将 box2 与 box1 的点击事件触发。

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <title>JavaScript 事件冒泡</title>
</head>
<style type="text/css">
    #box1 { background: blueviolet;}
    #box2 {background: aquamarine;}
    #box3 {background: tomato;}
    div { padding: 40px; margin: auto;}
</style>

<body>
    <div id="box1">
        <div id="box2">
            <div id="box3"></div>
        </div>
    </div>
    <script>
        window.onload = function () {
            const box1 = document.getElementById('box1')
            const box2 = document.getElementById('box2')
            const box3 = document.getElementById('box3')
            box1.onclick = sayBox1;
            box2.onclick = sayBox2;
            box3.onclick = sayBox3;
            function sayBox3() {
                console.log('你点了最里面的box');
            }
            function sayBox2() {
                console.log('你点了最中间的box');
            }
            function sayBox1() {
                console.log('你点了最外面的box');
            }
        }
    </script>
</body>

</html>

这个时候 click 捕获的传播顺序为:
window -> document -> <html> -> <body> -> <div #box1> -> <div #box2> -> <div #box3>
这个时候 click 冒泡的传播顺序为:
<div #box3> -> <div #box2> -> <div #box1> -> <body> -> <html> -> document -> window

JavaScript 事件捕获冒泡与捕获详情

现代浏览器都是从 window 对象开始捕获事件的,冒泡最后一站也是 window 对象。而 IE8 及以下浏览器,只会冒泡到 document 对象。
事件冒泡:是由元素的 HTML 结构决定,而不是由元素在页面中的位置决定,所以即便使用定位或浮动使元素脱离父元素的范围,单击元素时,其依然存在冒泡现象。

现在我们知道了事件流的三个阶段后,那我们可以利用这个特性做什么呢?

二、事件委托

设想这样一个场景,当你有一堆的<li>标签在一个<ul>标签下,需要给所有的<li>标签绑定onclick事件,这个问题我们可以用循环解决,但还有没有更简便的方式呢?
我们可以给这些<li>共同的父元素<ul>添加onclick事件,那么里面的任何一个<li>标签触发onclick事件时,都会通过冒泡机制,将onclick事件传播到<ul>上,进行处理。这个行为叫做事件委托, <li>利用事件冒泡将事件委托到<ul>上。
也可以利用事件捕获进行事件委托。用法是一样的,只是顺序反了。

<ul id="myUl">
    <li>item 1</li>
    <li>item 2</li>
    <li>item 3</li>
    ...
  </ul>

可能还是有点不好理解,简单来说,就是利用事件冒泡,将某个元素上的事件委托给他的父级。

举个生活中的例子,双十一快递到了,需要快递小哥送快递一般是挨家挨户送货上门,这样效率慢,小哥想了个办法,把一个小区的快递都放在小区里面的快递驿站,进行送快递的事件委托,小区的收件人能通过取件码去快递驿站领取到自己的快递。
在这里,快递小哥送快递就是一个事件,收件人就是响应事件的元素,驿站就相当于代理元素,收件人凭着收获码去驿站里面领快递就是事件执行中,代理元素判断当前响应的事件匹配该触发的具体事件。

可是这样做有什么好处呢?

1、事件委托的优点

事件委托有两个好处

  • 减少内存消耗
  • 动态绑定事件

减少内存消耗,优化页面性能

JavaScript中,每个事件处理程序都是对象,是对象就会占用页面内存,内存中的对象越多,页面的性能当然越差,而且DOM的操作是会导致浏览器对页面进行重排和重绘(这个不清楚的话,小伙伴可以了解页面的渲染过程),过多的DOM操作会影响页面的性能。性能优化主要思想之一就是为了最小化的重排和重绘也就是减少DOM操作。

在上面给<li>标签绑定onclick事件的例子中,使用事件委托就可以不用给每一个<li>绑定一个函数,只需要给<ul>绑定一次就可以了,当li的数量很多时,无疑能减少大量的内存消耗,节约效率。

动态绑定事件:

如果子元素不确定或者动态生成,可以通过监听父元素来取代监听子元素。
还是上面在<li>标签绑定onclick事件的例子中, 很多时候我们的这些<li>标签的数量并不是固定的,会根据用户的操作对一些<li>标签进行增删操作。在每次增加或删除标签都要重新对新增或删除元素绑定或解绑对应事件。

可以使用事件委托就可以不用给每一个<li>都要操作一遍,只需要给<ul>绑定一次就可以了,因为事件是绑定在<ul>上的, <li>元素是影响不到<ul>的 ,执行到<li>元素是在真正响应执行事件函数的过程中去匹配的,所以使用事件委托在动态绑定事件的情况下是可以减少很多重复工作的。

我们知道了事件委托的优点,那么该如何使用呢?

2、事件委托的使用

事件委托的使用需要用的addEventListener()方法,事件监听。
方法将指定的监听器注册到调用该函数的对象上,当该对象触发指定的事件时,指定的回调函数就会被执行。

用法:

element.addEventListener(eventType, function, useCapture);
参数 必/选填 描述
eventType 必填 指定事件的类型。
function 必填 指定事件触发后的回调函数。
useCapture 选填 指定事件是在捕获阶段执行还是在冒泡阶段执行。

第三个参数 useCapture 是个布尔类型,默认值为false

  • true - 表示事件在捕获阶段执行执行
  • false- 表示事件在冒泡阶段执行执行

看下面例子:

<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <title>JavaScript 事件委托</title>
</head>

<body>

  <ul>
    <li>item 1</li>
    <li>item 2</li>
    <li>item 3</li>
    <li>item 4</li>
  </ul>

  <script>
    const myUl = document.getElementsByTagName("ul")[0];

    myUl.addEventListener("click", myUlFn);

    function myUlFn(e) {
      if (e.target.tagName.toLowerCase() === 'li') { // 判断是否为所需要点击的元素
        console.log(`您点击了${e.target.innerText}`);
      }
    }

  </script>
</body>

</html>

注意:这是一般的事件委托方法,但是这种写法有问题,就是当_<li>_中还有子元素时,点击这个子元素就不会进行触发事件。这个问题是一个坑。

事件冒泡有时候确实很有用,但是有时候也讨人烦,当你不需要它的时候能不能取消掉呢?

三、禁止事件冒泡与捕获

注意:并不是所有事件都会冒泡,比如focus,blur,change,submit,reset,select等。

禁止冒泡和捕获可以用到方法stopPropagation()。
stopPropagation()起到阻止捕获和冒泡阶段中当前事件的进一步传播。
这是阻止事件的冒泡方法,进行冒泡,但是默认事件任然会执行,当你调用了这个方法后。
如果点击一个a标签,这个a标签会进行跳转。

使用起来也很简单,没有返回值也没有参数。

event.stopPropagation();

请看下面例子,这个例子实在上文事件冒泡例子基础上稍加修改得到的

<div id="box1">
        <div id="box2">
            <div id="box3"></div>
        </div>
    </div>
    <script>
        const box1 = document.getElementById('box1')
        const box2 = document.getElementById('box2')
        const box3 = document.getElementById('box3')
        box1.onclick = sayBox1;
        box2.onclick = sayBox2;
        box3.onclick = sayBox3;
        function sayBox3() {
            console.log('你点了最里面的box');
        }
        function sayBox2(e) {
            console.log('你点了最中间的box');
            e.stopPropagation(); //禁止事件捕获和冒泡
        }
        function sayBox1() {
            console.log('你点了最外面的box');
        }
    </script>

当事件冒泡到box2时调用了在函数sayBox2,调用了e.stopPropagation(); 就停止冒泡了。

到此这篇关于JavaScript 事件捕获冒泡与捕获详情的文章就介绍到这了,更多相关JavaScript事件捕获冒泡与捕获内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

四、参考文献

MDN中文版 https://developer.mozilla.org/zh-CN/
知乎 https://zhuanlan.zhihu.com/p/26536815

 

Javascript 相关文章推荐
JavaScript包装对象使用详解
Jul 09 Javascript
JavaScript Split()方法
Dec 18 Javascript
jquery form表单获取内容以及绑定数据
Feb 24 Javascript
使用伪命名空间封装保护独自创建的对象方法
Aug 04 Javascript
jquery仿ps颜色拾取功能
Mar 08 Javascript
Vue Spa切换页面时更改标题的实例代码
Jul 15 Javascript
vue数组对象排序的实现代码
Jun 20 Javascript
浅谈Vue.js路由管理器 Vue Router
Aug 16 Javascript
vue 项目build错误异常的解决方法
Apr 22 Javascript
layui数据表格跨行自动合并的例子
Sep 02 Javascript
layer.open 子页面弹出层向父页面传输数据的例子
Sep 26 Javascript
vue中组件通信详解(父子组件, 爷孙组件, 兄弟组件)
Jul 27 Javascript
JavaScript 定时器详情
Nov 11 #Javascript
使用javascript解析二维码的三种方式
Nov 11 #Javascript
实现一个简单得数据响应系统
Nov 11 #Javascript
JavaScript函数柯里化
Nov 07 #Javascript
JS数组去重详情
Nov 07 #Javascript
手写实现JS中的new
Nov 07 #Javascript
用JS写一个发布订阅模式
Nov 07 #Javascript
You might like
《魔兽争霸3》重制版究竟重制了什么?玩家:这么糊弄真的好吗?
2020/05/04 魔兽争霸
《雄兵连》系列首部大电影《烈阳天道》:可能是因为期望值太高了
2020/08/18 国漫
php 操作调试的方法
2012/07/12 PHP
使用cookie实现统计访问者登陆次数
2013/06/08 PHP
phpphp图片采集后按原路径保存图片示例
2014/02/18 PHP
php实现的错误处理封装类实例
2017/06/20 PHP
PHP进阶学习之命名空间基本用法分析
2019/06/18 PHP
javaScript对象和属性的创建方法
2007/01/15 Javascript
jquery 追加tr和删除tr示例代码
2013/09/12 Javascript
javascript同步服务器时间和同步倒计时小技巧
2015/09/24 Javascript
基于jQuery的左滑出现删除按钮的示例
2017/08/29 jQuery
vue 2.0项目中如何引入element-ui详解
2017/09/06 Javascript
layui点击导航栏刷新tab页的示例代码
2018/08/14 Javascript
vue和better-scroll实现列表左右联动效果详解
2019/04/29 Javascript
vue中typescript装饰器的使用方法超实用教程
2019/06/17 Javascript
解决layui table表单提示数据接口请求异常的问题
2019/09/24 Javascript
vue+axios实现post文件下载
2019/09/25 Javascript
React中使用UMEditor的方法示例
2019/12/27 Javascript
vue路由跳转传递参数的方式总结
2020/05/10 Javascript
[53:10]Secret vs Pain 2018国际邀请赛小组赛BO2 第一场 8.17
2018/08/20 DOTA
python采用django框架实现支付宝即时到帐接口
2016/05/17 Python
python 异常处理总结
2016/10/18 Python
用pyqt5 给按钮设置图标和css样式的方法
2019/06/24 Python
如何安装2019Pycharm最新版本(详细教程)
2019/09/26 Python
Python一行代码解决矩阵旋转的问题
2019/11/30 Python
Pytorch实现各种2d卷积示例
2019/12/30 Python
序列化Python对象的方法
2020/08/01 Python
如何用python写个模板引擎
2021/01/14 Python
HTML5的结构和语义(5):内嵌媒体
2008/10/17 HTML / CSS
英国计算机商店:Technextday
2019/12/28 全球购物
加拿大户外探险购物网站:SAIL
2020/06/27 全球购物
先进集体获奖感言
2014/02/13 职场文书
2014迎新年晚会策划方案
2014/02/23 职场文书
食品采购员岗位职责
2014/04/14 职场文书
四年级评语大全
2014/04/21 职场文书
保证书格式
2015/01/16 职场文书