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

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

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

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

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

  1. <button>
  2. <svg>
  3. <use xlink:href="#gear"></use>
  4. </svg>
  5. </button>

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

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

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

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

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

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

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

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

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

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

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

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

实际的情况是这样的:

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

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

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

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

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

  1. button > * {
  2. pointer-events: none;
  3. }

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

  1. document.querySelector('button > svg').addEventListener('click', function (e) {
  2. e.stopPropagation()
  3. e.preventDefault()
  4. })
  5. document.querySelector('button').addEventListener('click', function (e) {
  6. console.log(e.target.tagName)
  7. })

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

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

要小心 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. Java 线程池(ThreadPoolExecutor)原理分析与实际运用

    在我们的开发中"池"的概念并不罕见,有数据库连接池.线程池.对象池.常量池等等.下面我们主要针对线程池来一步一步揭开线程池的面纱. 有关java线程技术文章还可以推荐阅读:< ...

  2. 自动安装带nginx_upstream_check_module模块的Nginx脚本

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #!/bin/bash    useradd -s /sbin/no ...

  3. Leo2DNT(雷傲论坛转DiscuzNT)1.0转换程序发布

    数据转换程序 雷傲论坛(Leobbs4.x) -> Discuz!NT V1.0    本转换程序基于Leobbs4.x设计     声明: 1.本程序只对数据作转换,不会对原来的雷傲论坛(数据 ...

  4. DeepWalk论文精读:(1)解决问题&相关工作

    模块1 1. 研究背景 随着互联网的发展,社交网络逐渐复杂化.多元化.在一个社交网络中,充斥着不同类型的用户,用户间产生各式各样的互动联系,形成大小不一的社群.为了对社交网络进行研究分析,需要将网络中 ...

  5. 基于UDP的客户端和服务器端的代码设计

    实验平台 linux 实验内容 编写UDP服务器和客户端程序,客户端发送消息,服务器接收消息,并打印客户端的IP地址和端口号. 实验原理 UDP是无需连接的通信,其主要实现过程如下: 同样,我们可以按 ...

  6. 手把手教你使用ADB卸载手机内置App软件

    [一.前言] 不知道你们有没有那么一段黑暗时期,刚买个手机,手机上内置一堆app,还卸载不掉,然后每天各种广告,手机一共1G的运行内存,那些流氓app还要再占走一些内存,真是让人欲哭无泪啊,后来我就学 ...

  7. socket编程之并发回射服务器

    使用到的函数: // 子进程返回0,父进程返回子进程ID,出错返回-1 pid_t fork(void); pid_t wait(int *wstatus); // 最常用的option是WNOHAN ...

  8. 操作系统实验——PV操作实现生产者消费者模型

    操作系统PV操作之--生产者消费者模型 个人博客主页 参考资料: Java实现PV操作 | 生产者与消费者 浙大公开课 在操作系统的多进程.多线程操作中经常会有因为同步.互斥等等问题引发出的一系列问题 ...

  9. Coursera课程笔记----计算导论与C语言基础----Week 3

    存储程序式计算机 冯·诺伊曼式计算机 "关于EDVAC的报告草案" 组成:控制器(协调工作).运算器(算数&逻辑运算).存储器(存储操作信息和中间结果).输入设备.输出设备 ...

  10. ActiveMQ 事务、集群、持久订阅者、ActiveMQ监控

    JMS介绍 JMS是什么? JMS的全称Java Message Service,既Java消息服务. JMS是SUN提供的旨在统一各种MOM(Message-Oriented Middleware) ...