javascript 事件代理,原理和问题

极客逐浪客
• 阅读 4835

有如下 html 片段

<ul>
    <li>11111111111</li>
    <li>22222222222</li>
    <li>33333333333</li>
</ul>

要对 li 添加 click 事件。通常做法:

var list = document.getElementsByTagName('li');
for (var i = 0; i < list.length; i++) {
    list[i].addEventListener('click', function(e) {
        console.log(e.target.textContent);
    })
}

事件代理的做法

var ul = document.getElementsByTagName('ul')[0];
ul.addEventListener('click', function(e) {
    if (e.target.tagName === 'LI') {
        console.log(e.target.textContent);
    }
})

我们来对比一下两段注册点击事件的代码。第一段代码很常规,直接找到相关元素然后一一绑定。第二段代码看起来简洁一点,但多了一层判断。

做一个简单的 delegate 封装

EventTarget.prototype.delegate = function(targetDom, type, listener) {
    this.addEventListener(type, function(e) {
        if (e.target.tagName === targetDom.toUpperCase()) {
            listener(e);
        }
    });
}

var ul = document.getElementsByTagName('ul')[0];
ul.delegate('li', 'click', function(e) {
    console.log(e.target.textContent);
});

接下来的篇幅主要会聊到事件流,事件代理的原理和问题,event 和 EventTarget 帮你深度了解 js 的事件。

事件触发的流程

通常我们把事件流分为三个阶段

var ul = document.getElementsByTagName('ul')[0];
ul.addEventListener('click', function(e) {
    if (e.target.tagName === 'LI') {
        console.log(e.target.textContent);
    }
})
document.body.addEventListener('click', function(e)     {
    console.log('body', e.target.textContent);
})
  1. 捕获阶段:这个就是向下去找触发事件的元素。但是一定注意如果这个时候 body 上的 click 并不会触发。如果希望 body 上的 click 先触发,就需要给 addEventListener 第三个参数 useCapture 设置为 true (dom3 引入的,所以比较新的浏览器才有效)。

  2. 目标阶段:执行目标的绑定函数

  3. 冒泡阶段:沿着父级元素一路向上,body 上如果也有 click 事件就会执行。event.stopPropagation() 可以用于事件处理函数中阻止冒泡行为。

事件代理的原理和问题

在我们了解了事件流之后,事件代理的原理就很好理解了,其实就是事件冒泡会触发容器 dom 的相关事件并执行监听函数。

那事件代理会带来什么问题呢?

<ul>
    <li>111111</li>
</ul>

如果 ul 有 click ,并且在它上面为 li 做了 click 事件的代理。这个时候其实我只想触发 li 的 click。解决方法如下:


var ul = document.getElementsByTagName('ul')[0];
ul.addEventListener('click', function(e) {
    if (e.target === e.currentTarget) {
        console.log(e.target);
    }
});
ul.delegate('li', 'click', function(e) {
    console.log(e.target);
});

很显然 stopPropagation 并不能解决问题,这里通过比较 target 和 currentTarget 来判断当前事件触发元素(target)是否是注册事件的元素(currentTarget)即可。

stopPropagation,target,currentTarget 都是 event 的方法和属性,所以下面详细讲讲 event 对象。

event 事件对象详解

常用属性:

  • currentTarget 注册事件的 dom 元素

  • srcElement ie6-8 的触发事件的 dom 元素,非标准

  • target 触发事件的 dom 元素

  • timeStamp 返回事件发生时的时间戳

  • type 事件的类型

常用方法

  • preventDefault() 阻止默认行为。例如 a 标签默认会跳转 href 指定的链接,执行该方法可以阻止跳转的发生。

  • stopImmediatePropagation() 阻止冒泡行为,并立即阻止该绑定元素上的相同类型事件处理函数的执行。立即阻止的意义在于,本方法执行之前函数执行或者其他的相同类型绑定不会有问题,但一旦执行该方法后面的程序将不再执行。例子

  • stopPropagation() 阻止冒泡行为。

EventTarget

我们来看一下 mdn 对于 EventTarget 的定义:
一个EventTarget是一个可以接受DOM事件且能绑定事件监听器的对象.最常见的EventTarget就是DOM元素对象,另外,还有一些不是DOM元素的对象也能成为EventTarget,比如document, window, XMLHttpRequest,等等.

EventTarget 有三个常用方法:

  1. addEventListener(type, listener[, useCapture])

    给 EventTarget 添加事件绑定,type 为事件类型(例如 ‘click’),listener 事件触发时的处理函数(默认参数 event 本文后面会讲到)。useCapture 是一个非必需的布尔值,默认为 false。如果设置为 true 即表示希望发起捕获(关于捕获,会在后面的事件触发流程里讲到)。
  2. removeEventListener(type, listener[, useCapture])

给 EventTarget 注销事件绑定。参数和 addEventListener 相同。需要特别注意两点。

1、如果同一个监听事件分别为“事件捕获”和“事件冒泡”注册了一次,一共两次,这两次事件需要分别移除。两者不会互相干扰。
2、如果此事件正在执行,会立即停止。
  1. dispatchEvent

手动触发事件,举个例子(来自 mdn):

var event = new Event('build');

// Listen for the event.
elem.addEventListener('build', function (e) {
    console.log(e.target)
}, false);

// Dispatch the event.
elem.dispatchEvent(event);

上面这个例子就是一个简单的自定义事件,如果想深入了解可以看张鑫旭的漫谈js自定义事件、DOM/伪DOM自定义事件

点赞
收藏
评论区
推荐文章
待兔 待兔
1年前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Stella981 Stella981
3年前
ASMSupport教程4.7 生成关系运算符
<p在java中,关系运算符是很常用的,分别是&gt;,,&lt;,&gt;,&lt;,!这六种,我们按照惯例看看我们需要生成的代码:</p<divid"scid:9D7513F9C04C4721824A2B34F0212519:dfec0f1ca2ec4ebabc9b91c161fbfa47"class"wlWri
Stella981 Stella981
3年前
SpringBoot 2.0 系列002
SpringBoot2.0系列002运行流程分析SpringBoot2.0系列001入门介绍以及相关概念(https://my.oschina.net/lt0314/blog/1810336)1\.SpringBoot运行的几种方式
Wesley13 Wesley13
3年前
4,MongoDB 之 $关键字 及 $修改器 $set $inc $push $pull $pop MongoDB
MongoDB中的关键字有很多,$lt$gt$lte$gte等等,这么多我们也不方便记,这里我们说说几个比较常见的一.查询中常见的等于大于小于大于等于小于等于等于:在MongoDB中什么字段等于什么值其实就是":"来搞定比如"name":"路飞学城"!(https://oscimg.oschin
Stella981 Stella981
3年前
PythonStudy——魔法函数 Magic Methods
魔法函数python中以双下划线开始和结束的函数(不可自己定义)为魔法函数调用类实例化的对象的方法时自动调用魔法函数(感觉不需要显示调用的函数都叫)在自己定义的类中,可以实现之前的内置函数,比如下面比较元素sorted时用It函数(lt(self,other):判断self对象是否小于other对象;)
Stella981 Stella981
3年前
Python 的 Magic Methods 指南
_摘要:_ 介绍本指南是数月博客的总结。主题是魔术方法。什么是魔术方法呢?它们是面向对象Python语言中的一切。它们是你可以自定义并添加“魔法”到类中的特殊方法。它们被双下划线环绕(比如\_\_init\_\_或\_\_lt\_\_)。介绍本指南是数月博客的总结。主题是魔术方法。什么是魔术方法呢?它们是面向对象Python语言中的一
Wesley13 Wesley13
3年前
MongoDB 范围查询
查询价格在2009000  $gt 大于   $lt  小于//查询价格2009000范围的数据db.prodgory.find({"price":{$gt:"200",$lt:"9000"}})查询给定范围数据  $in//给定范围查询db.product1.find({"categor
小万哥 小万哥
1年前
资源描述框架的用途及实际应用解析
RDF(资源描述框架)是一种用于机器理解网络资源的框架,使用XML编写。它通过URI标识资源,用属性描述资源,便于计算机应用程序处理信息。RDF在语义网上促进信息的确切含义和自动处理,使得网络信息可被整合。RDF语句由资源、属性和属性值组成。RDF文档包括&lt;rdf:RDF&gt;根元素和&lt;rdf:Description&gt;元素,后者用about属性标识资源。RDF还支持容器(如&lt;Bag&gt;、&lt;Seq&gt;和&lt;Alt&gt;)来描述集合。RDFS是RDF的扩展,提供描述类和属性的框架,而达布林核心是一组预定义属性,用于描述文档。
小万哥 小万哥
1年前
RSS 解析:全球内容分发的利器及使用技巧
RSS(ReallySimpleSyndication)是一种XML格式,用于网站内容的聚合和分发,让用户能快速浏览和跟踪更新。RSS文档结构包括&lt;channel&gt;和&lt;item&gt;元素,允许内容创作者分享标题、链接和描述。通过RSS,用户可以定制新闻源,过滤不相关信息,提高效率。RSS支持不同版本,如RSS0.91和RSS2.0,其中RSS2.0语法简单且广泛使用。RSS提高网站流量,适用于新闻、博客、日历等频繁更新的站点。RSS的历史始于1997年,至今仍无官方标准,但已成为内容共享的重要工具。