我们知道,如果给 form 里面的 button 元素绑定事件,需要考虑它是否会触发 form 的 submit 行为。除此之外,其它场合给 button 元素绑定事件,你几乎不用担心这个事件会有什么非预期的附加效果,很自然地会这样写事件处理代码:

var button = document.querySelector('button')
button.addEventListener('click', function (e) {
console.log('点击了按钮')
})

你之所以放心这么写,是因为这个 button 元素没有使用事件代理,即没有代理任何子元素的事件。

事件代理的意思是,你要为一个元素绑定事件,但你不是直接把事件绑定到这个元素自己身上,而是绑定到这个元素的父元素上。当子元素的某个事件(比如点击事件)触发时,它的父元素相同的事件也会触发(我们常说的事件冒泡),此时我们说父元素代理了子元素的事件。

举个例子,比如一个 button 元素中包含一个齿轮图标:

<button>
<svg>
<use xlink:href="#gear"></use>
</svg>
</button>

当用户点击齿轮图标,必然要触发 click 事件,但你并不会直接绑定事件到 svg 或 use 元素上,而是绑定到它们的父元素 button 上。即:

document.querySelector('button').addEventListener('click', function (e) {
console.log('点击了按钮')
})

这种情况,我们可以说,button 元素代理了它的所有子元素的 click 事件。

但是,出现这种事件代理的情况时,我们就得小心了。

为了更直观地说明问题,我们把“父”元素上升到顶层的 document 元素:

document.documentElement.addEventListener('click', function (e) {
console.log('我被点击了')
})

只要网页中任意一个位置被点击了,都会触发绑定在 document 元素上的点击事件。​ 想要知道事件具体是发生在哪个元素上面,可以通过事件对象提供的 target 属性来判断。

document.documentElement.addEventListener('click', function (e) {
console.log(e.target)
})

我们很容易知道事件具体是发生在哪个元素身上的。于是在上面的示例中,如果父元素 document 想在按钮被点击时做点什么事情,我们很自然地会这么写:

document.documentElement.addEventListener('click', function (e) {
if (e.target.tagName === 'BUTTON') {
console.log('按钮被点击了')
}
})

这时问题就出现了,按钮即使被点击了 if 条件也不一定成立,即也不一定会输出“按钮被点击了”。因为用户在按钮上的某个位置点击了,根据用户点击的位置,e.target 可能是下面三种情况:

  • BUTTON 元素
  • SVG 元素
  • USE 元素

实际的情况是这样的:

我们真正的意图是,只要点击是发生在按钮上面,不论是按钮的哪个位置,我们都应视为按钮被点击了。 嗯,简单,我们再改一下,这样写:

document.documentElement.addEventListener('click', function (e) {
if (['BUTTON', 'SVG', 'USE'].includes(e.target.tagName.toUpperCase())) {
// 点击的是按钮
}
})

这样似乎没什么问题,也确实可以达到目的,但看上去总是有些别扭。因为这种情况对于最上层的 document 来说,得知道每个子元素的情况,本来我只需要关心离我最近的 button 元素就可以了。

根据 OOP 对内封装的思想,button 元素内部的事情应该在内部消化掉,其子元素对外不可见,应该只暴露 button 元素本身。依据这个思想和事件冒泡的特点,我们就有了比较好的解决办法:只需要禁止 button 内部元素的事件响应(包括事件冒泡)而只允许 button 元素本身的事件发生就行。有两种方式可以实现这个目的。

一种是使用 CSS 禁止 button 内部元素的事件响应:

button > * {
pointer-events: none;
}

另一种是使用 JS 来阻止 button 内部元素的事件响应(包括事件冒泡):

document.querySelector('button > svg').addEventListener('click', function (e) {
e.stopPropagation()
e.preventDefault()
}) document.querySelector('button').addEventListener('click', function (e) {
console.log(e.target.tagName)
})

这两种方式都能达到我们预期的效果:

综上,针对特定元素进行事件处理时,如果该元素有事件代理的情况,就要小心处理它所代理的子元素。

要小心 JavaScript 的事件代理的更多相关文章

  1. JavaScript的事件代理(转)

    如果你想给网页添加点JavaScript的交互性,也许你已经听过JavaScript的事件代理(event delegation),并且觉得这是那些发烧友级别的JavaScript程序员才会关心的什么 ...

  2. JavaScript事件代理和委托(Delegation)

    JavaScript事件代理 首先介绍一下JavaScript的事件代理.事件代理在JS世界中一个非常有用也很有趣的功能.当我们需要对很多元素添加事件的时候,可以通过将事件添加到它们的父节点而将事件委 ...

  3. JavaScript 事件代理

    转自:http://www.cnblogs.com/silence516/archive/2009/09/03/delegateEvent.html 如果你想给网页添加点JavaScript的交互性, ...

  4. Atitit事件代理机制原理 基于css class的事件代理

    Atitit事件代理机制原理 基于css class的事件代理 1.1. 在javasript中delegate这个词经常出现,看字面的意思,代理.委托1 1.2. 事件代理1 1.3. 代理标准化规 ...

  5. JS中事件代理与委托

    在javasript中delegate这个词经常出现,看字面的意思,代理.委托.那么它究竟在什么样的情况下使用?它的原理又是什么?在各种框架中,也经常能看到delegate相关的接口.这些接口又有什么 ...

  6. 事件代理总结: 已经有一些使用主流类库的事件代理示例出现了,比如说jQuery、Prototype以及Yahoo! UI。你也可以找到那些不用任何类库的例子,比如说Usable Type blog上的这一个。一旦需要的话,事件代理将是你工具箱里的一件得心应手的工具,而且它很容易实现。

    如果你想给网页添加点JavaScript的交互性,也许你已经听过JavaScript的事件代理(event delegation),并且觉得这是那些发烧友级别的JavaScript程序员才会关心的什么 ...

  7. JavaScript中事件委托(事件代理)详解

    在JavaScript的事件中,存在事件委托(事件代理),那么什么是事件委托呢? 事件委托在生活中的例子: 有三个同事预计会在周一收到快递.为签收快递,有两种办法:一是三个人在公司门口等快递:二是委托 ...

  8. 关于JavaScript中的事件代理

    今天面试某家公司Web前端开发岗位,前面的问题回答的都还算凑活,并且又问了一下昨天面试时做的一道数组去重问题的解题思路(关于数组去重问题,可以观赏我前几天写的:http://www.cnblogs.c ...

  9. 【转载】浅谈事件冒泡与事件捕获 - javascript 事件代理

    原文:https://segmentfault.com/a/1190000000749838 事件冒泡与事件捕获 事件冒泡和事件捕获分别由微软和网景公司提出,这两个概念都是为了解决页面中事件流(事件发 ...

随机推荐

  1. CodeForces - 1245 C - Constanze's Machine

    Codeforces Round #597 (Div. 2) Constanze is the smartest girl in her village but she has bad eyesigh ...

  2. 一只简单的网络爬虫(基于linux C/C++)————利用正则表达式解析页面

    我们向一个HTTP的服务器发送HTTP的请求后,服务器会返回可能一个HTML页面(当然也可以是其他的资源),我们可以利用返回的HTML页面,在其中寻找其他的Url,例如我们可以这样在浏览器上查看一下H ...

  3. Jmeter简单压测之服务器监控

    此篇为最近工作需要到内容,故现在做一个总结. 最近家里电脑坏了,等待会公司空闲在编写. 文章构思中,敬请期待.......

  4. django+nginx+uwsgi的生产环境部署(Ubuntu16.04)

    一,准备工作: 代码一定要能本地跑起来! 各种基础包的安装略默认已经安装python3,nginx,uwsgi等基础依赖,注意版本问题. 本地setting.py文件修改如下(改为生产模式,把debu ...

  5. 2-JVM内存模型

    内存模型 方法区 JDK1.7 之前包含1.7 将方法区称为 Perm Space 永久代 JDK1.8之后包含1.8 将方法区称为 MetaSpace 元空间. 堆(分配内存会大一些) 分配对象.n ...

  6. 02_Java语法

    1.注释 2.关键字 3.标识符 4.常量 5.变量 6.数据类型 7.数据类型转换 8.表达式 9.运算符 9.1算数运算符 9.2赋值运算符 9.3比较运算符 9.4逻辑运算符 9.5三元运算符 ...

  7. 一元三次方程 double输出 -0.00

    求一个 a*x*x*x+b*x*x+c*x+d 的解 题目很简单,但是我输出了-0.00,然后就一直卡着,这个问题以后要注意. 让0.00 编程-0.00的方法有很多. 第一种就是直接特判 if(fa ...

  8. mysql优化–explain分析sql语句执行效率

    Explain命令在解决数据库性能上是第一推荐使用命令,大部分的性能问题可以通过此命令来简单的解决,Explain可以用来查看SQL语句的执行效 果,可以帮助选择更好的索引和优化查询语句,写出更好的优 ...

  9. 软路由OpenWrt(LEDE)2020.4.4编译 UnPnP+NAS+多拨+网盘+DNS优化

    近期更新:2020.04.24编译-基于OpenWrt R2020.3.19版本.   2020.04.04更新记录: 修正国内域名加速脚本部分缺陷 内置打印机共享,ZeroTier 新增多套主题 S ...

  10. 杂记---主要关于PHP导出excel表格学习

    今天上午处理了一下WIN7系统的电脑前置话筒和耳机口无法使用的问题,主要现象是耳机插入后没声音,麦插入话筒说话对方也听不到,后置端口一切正常.刚开始判断肯定是设置的问题,于是用另一台电脑百度搜索“wi ...