事件冒泡、事件捕获和事件委托

本文主要介绍了JavaScript中的事件委托。先解释了事件冒泡和事件捕获的机制,并通过示例帮助理解。接着阐述了事件流的标准实现和IE写法,以及event对象的使用。然后通过通俗例子说明事件委托,展示其实现方法和优势,还介绍了阻止事件冒泡的方法及用途,最后提及IE兼容问题和jQuery的事件监听方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

javascript里,事件委托是很重要的一个东西,事件委托依靠的就是事件冒泡和捕获的机制,我先来解释一下事件冒泡和事件捕获:

事件冒泡会从当前触发的事件目标一级一级往上传递,依次触发,直到document为止。意思就是说,假如用户单击了一个元素,该元素拥有一个click事件,那么同样的事件也将会被它的祖先触发,这个事件从该元素开始一直冒泡到DOM树的最上层,这一过程称为事件冒泡

在这里插入图片描述
事件捕获会从document开始触发,一级一级往下传递,依次触发,直到真正事件目标为止。事件捕获和事件冒泡是相反的,也就是说,当用户触发了一个事件的时候,这个事件是从DOM树的最上层开始触发一直到捕获到事件源.
在这里插入图片描述

这么说是不是很抽象,其实就像我敲击了一下键盘,我在敲击键盘的同时,我是不是也敲击了这台电脑,我写个例子大家就明白了:

	<!DOCTYPE html>
	<html>
	    <head>
	        <meta charset="UTF-8">
	        <title></title>
	    </head>
	    <style type="text/css">
	        #box1 { width: 300px; height: 300px; background: blueviolet; }
	        #box2 { width: 200px; height: 200px; background: aquamarine; }
	        #box3 { width: 100px; height: 100px; background: tomato; }
	        div { overflow: hidden; margin: 50px auto; }
	    </style>
	    <body>
	        <div id="box1">
	            <div id="box2">
	                <div id="box3"></div>
	            </div>
	        </div>
	        <script>
	            function sayBox3() {
	                console.log('你点了最里面的box');
	            }
	            function sayBox2() {
	                console.log('你点了最中间的box');
	            }
	            function sayBox1() {
	                console.log('你点了最外面的box');
	            }
	            // 事件监听,第三个参数是布尔值,默认false,false是事件冒泡,true是事件捕获
	            document.getElementById('box3').addEventListener('click', sayBox3, false);
	            document.getElementById('box2').addEventListener('click', sayBox2, false);
	            document.getElementById('box1').addEventListener('click', sayBox1, false);
	
	        </script>
	    </body>
	</html>

我们画了三个box,结构是父子关系,分别绑定了打印事件,现在我们来点击最中间的红色box:

在这里插入图片描述

我们发现,我们仅仅是点击了红色的box,但是绿色和紫色的box也被触发了打印事件,触犯顺序是 红色>绿色>紫色,这种现象就是事件冒泡了。
我们再试试事件捕获,把上面代码里监听事件的第三个参数改为true,然后点击红色的box:

在这里插入图片描述

我们还是只点击最中间的红色box,和上一次一样,也是三个box都触发了事件,但是顺序反过来了,紫色>绿色>红色,这种现象称为事件捕获。
通过上面的例子,应该很容易就理解了事件冒泡和事件捕获,我们平时都是默认冒泡的,冒泡是一直冒到document根文档为止。

事件流的写法以及实现方式

标准实现方式使用关键词addEventListener,假如你想要给某一个元素添加事件,现在就可以这样写

element.addEventListener(eventType, fn, false) 

dom对象.addEventListener(事件类型, 回调函数, 事件机制) 这里的事件类型表示你要使用哪种事件类型比如click, 回调函数里面写着触发此事件你要做什么事情, 事件机制分为冒泡和捕获,如果为false表示事件冒泡,为true表示事件捕获

IE写法
dom对象.attachEvent(eventType, fn) 第一个参数表示事件类型,第二个是回调.这种写法兼容IE, IE没有实现事件捕获,

关于event对象
在触发的事件的函数里面我们会接收到一个event对象,通过该对象我们需要的一些参数,比如说我们需要知道此事件作用到谁身上了,就可以通过event的属性target来获取到(IE暂且不谈),或者想阻止浏览器的默认行为可以通过方法preventDefault()来进行阻止.

	获得event对象兼容性写法 
	event || (event = window.event);
	获得target兼容型写法 
	event.target||event.srcElement
	阻止浏览器默认行为兼容性写法 
	event.preventDefault ? event.preventDefault() : (event.returnValue = false);
	阻止冒泡写法 
	event.stopPropagation ? event.stopPropagation() : (event.cancelBubble = true);

事件委托

参考:https://www.cnblogs.com/websmile/p/8807334.html

现在来谈谈事件委托,事件委托又称之为事件代理,我们通过一个通俗的例子来解释:

有三个同事预计会在周一收到快递,为了签收快递,有两种办法:1.三个人在公司门口等快递;2.委托给前台MM代为签收。现实当中,我们大都采用委托的方案(公司也不会容忍那么多员工站在门口就为了等快递)。前台MM收到快递后,她会判断收件人是谁,然后按照收件人的要求签收,甚至代为付款。这种方案还有一个优势,那就是即使公司里来了新员工(不管多少),前台MM也会在收到寄给新员工的快递后核实并代为签收(可以给暂时不存在的节点也绑定上事件)。

我们再举另一个例子:

现在有一个ul,ul里又有100个li,我想给这100个li都绑定一个点击事件,我们一般可以通过for循环来绑定,但是要是有1000个li呢? 为了提高效率和速度,所以我们这时可以采用事件委托,只给ul绑定一个事件,根据事件冒泡的规则,只要你点了ul里的每一个li,都会触发ul的绑定事件,我们在ul绑定事件的函数里通过一些判断,就可以给这100li都触发点击事件了。

具体怎么实现,看代码:

	// 这里不讲IE,结尾再说
	function clickLi() {
	    alert('你点击了li');
			}
	document.getElementById('isUl').addEventListener('click', function(event) {
	    // 每一个函数内都有一个event事件对象,它有一个target属性,指向事件源
	    var src = event.target;
	    // 我们判断如果target事件源的节点名字是li,那就执行这个函数
	    // target里面的属性是非常多的,id名、class名、节点名等等都可以取到
	    if(src.nodeName.toLowerCase() == 'li') {
	       clickLi() ;
	    }
	});

这样我们就通过给ul绑定一个点击事件,让所有的li都触发了函数。
那如果想给不同的li绑定不同的函数怎么办?
假设有3个li,我们先写3个不同的函数,再给3个li设置不同的id名,通过判断id名是不是就能给不同的li绑定不同的函数啦:

<body>
    <ul id="isUl">
        <li id="li01">1</li>
        <li id="li02">2</li>
        <li id="li03">3</li>
    </ul>
    <script>
        function clickLi01() {
            alert('你点击了第1个li');
        }
        function clickLi02() {
            alert('你点击了第2个li');
        }
        function clickLi03() {
            alert('你点击了第3个li');
        }
        document.getElementById('isUl').addEventListener('click', function(event) {
            var srcID = event.target.id;
            if(srcID == 'li01'){
                clickLi01();
            }else if(srcID == 'li02') {
                clickLi02();
            }else if(srcID == 'li03') {
                clickLi03();
            }
        });
    </script>
</body>

这就是所谓的事件委托,通过监听一个父元素,来给不同的子元素绑定事件,减少监听次数,从而提升速度。
那么,能不能阻止元素的事件冒泡呢,答案是可以的。
一开始那个例子,假如我们真的只想点击最里面的那个红色box,不想另外两个box的事件被触发,我们可以在给红色box绑定事件的函数里这么写:

		function sayBox3(event) {
		    // 阻止冒泡
		    event.stopPropagation();
		    console.log('你点了最里面的box');
		}
		document.getElementById('box3').addEventListener('click', sayBox3, false);

这样我们再点击红色的box,那就只会触发它本身的事件啦。
那阻止冒泡有没有实际用途呢?答案是有的,我们看这个例子:

在这里插入图片描述

这是一个模态框,现在的需求是当我们点击红色的按钮需要跳转页面,然后点击白色的对话框不需要任何反应,点其它任何地方就关闭这个模态框。
这里就需要用到阻止冒泡了,红色的按钮是白色对话框的子元素,白色对话框又是这整个模态框的子元素,我们给模态框加上一个点击事件关闭,然后给红色的按钮加上一个点击事件跳转,这时就产生了一个问题,只要点击白色的对话框,由于冒泡机制,这个模态框也会关闭,实际上我们并不想点击白色的对话框有任何反应,这时我们就给这个白色的对话框绑定一个点击事件,函数里写上event.stopPropagation();,这样就OK了。
关于IE
老版本的IE存在兼容问题,根本不支持addEventListener()的添加事件和removeEventListener()的删除事件,它有自己的监听方法:

	// 添加事件,事件流固定为冒泡
	attachEvent(事件名,事件处理函数)
	// 删除事件
	detachEvent(事件名,事件处理函数)

还有IE里的事件对象是window.event,事件源是srcElement,阻止冒泡写法也不一样:

function() {
    // IE里阻止冒泡
    window.event.cancelBubble = true;
    // IE里获取事件源的id
    var srcID = window.event.srcElement.id;
}
function(event) {
    // 非IE里阻止冒泡
    event.stopPropagation();
    // 非IE里获取事件源的id
    var srcID = event.target.id;
}

补充
关于js的浏览器兼容问题,一般用能力检测来解决,if(){}else{}
我们平时工作一般都是用jquery,IE这些特殊情况早就帮我们做好兼容啦。
jquery在1.7的版本之后,最流行的事件监听方法是 ( 元 素 ) . o n ( 事 件 名 , 执 行 函 数 ) , 它 还 有 一 种 事 件 委 托 的 写 法 (元素).on(事件名,执行函数),它还有一种事件委托的写法 ().on()(委托给哪个元素).on(事件名,被委托的元素,执行函数)

最后说一点,如果元素被阻止冒泡了,千万别去用事件委托的方式监听事件,因为事件委托的原理是利用事件冒泡,当冒泡被阻止,就无法监听了。

<think>好的,我需要帮助用户理解JavaScript中的事件冒泡事件捕获的区别及用法。首先,我应该回忆一下这两个概念的基本定义工作流程。 根据引用[1][3],事件捕获事件冒泡事件传播的两个阶段。事件捕获是从外层元素向目标元素传播,而事件冒泡则是从目标元素向外层传播。用户可能想知道这两个阶段的具体顺序以及如何在实际中使用它们。 接下来,我需要解释它们的区别。根据引用[5],事件捕获在到达目标前处理,而事件冒泡在目标处理后向上传播。这里应该强调addEventListener的第三个参数,true为捕获,false为冒泡(默认)。 然后,用法部分。引用[4]提到事件冒泡更常用,符合直觉,而捕获用于特定需求。例如,事件委托通常利用冒泡,而捕获可能在需要提前拦截事件时使用。需要举例说明,比如使用stopPropagation来阻止传播,如引用[2][5]中的例子。 还需要注意实现方法,比如如何注册事件监听器,以及如何控制传播。可能还要提到事件委托的优势,如引用[3]中提到的性能优化。 最后,生成相关问题。可能需要覆盖应用场景、阻止传播的方法、事件委托以及如何选择捕获冒泡模式。</think>### JavaScript事件冒泡事件捕获的区别及用法详解 #### 一、工作机制对比 1. **事件捕获(Event Capturing)** - 传播方向:从根节点(如`window`)逐级向下传递到目标元素 - 触发顺序:父元素事件先于子元素触发 - 使用方式:通过`addEventListener`的第三个参数设为`true` - 应用场景:需要先于目标元素处理事件的场景(如表单验证拦截) ```javascript parent.addEventListener('click', () => { console.log('父元素捕获阶段'); }, true); // 注意第三个参数 ``` 2. **事件冒泡(Event Bubbling)** - 传播方向:从目标元素逐级向上传递到根节点 - 触发顺序:子元素事件先于父元素触发 - 默认模式:`addEventListener`第三个参数默认为`false` - 应用场景:事件委托(如动态列表项处理) ```javascript child.addEventListener('click', () => { console.log('子元素冒泡阶段'); }); // 默认使用冒泡 ``` #### 二、完整事件流演示 ```html <div id="grandparent"> <div id="parent"> <button id="child">点击测试</button> </div> </div> <script> document.querySelectorAll('div, button').forEach(el => { // 捕获阶段监听 el.addEventListener('click', (e) => { console.log(`${el.id} 捕获`); }, true); // 冒泡阶段监听 el.addEventListener('click', (e) => { console.log(`${el.id} 冒泡`); }); }); </script> ``` **输出顺序**: ``` grandparent 捕获 parent 捕获 child 捕获 child 冒泡 parent 冒泡 grandparent 冒泡 ``` #### 三、核心区别总结 | 特性 | 事件捕获 | 事件冒泡 | |--------------------|-------------------|-------------------| | 传播方向 | 根节点→目标元素 | 目标元素→根节点 | | 默认启用 | 否 | 是 | | 监听器参数 | `addEventListener(..., true)` | `addEventListener(..., false)` | | 适用场景 | 提前拦截/预处理 | 事件委托/常规处理 | #### 四、实用技巧与注意事项 1. **阻止事件传播** 使用`event.stopPropagation()`可终止事件传播流程: ```javascript child.addEventListener('click', (e) => { e.stopPropagation(); // 阻止继续传播[^2] console.log('事件已拦截'); }); ``` 2. **事件委托模式** 利用冒泡机制实现动态元素处理: ```javascript document.getElementById('list').addEventListener('click', (e) => { if(e.target.tagName === 'LI') { console.log('点击列表项:', e.target.textContent); } }); // 自动处理新增元素[^3] ``` 3. **混合使用建议** - 优先使用冒泡机制(符合自然操作流程)[^4] - 捕获阶段适合做全局性的事件监控 - 避免在同一元素同时使用两种机制造成逻辑混乱 #### 五、性能优化策略 1. 使用事件委托减少监听器数量 2. 及时移除不需要的事件监听 3. 避免在捕获阶段进行耗时操作 4. 使用`passive: true`优化滚动事件
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值