JavaScript 事件代理需要注意的地方


Posted in Javascript onSeptember 08, 2020

我们知道,如果给 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" rel="external nofollow" ></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 元素

实际的情况是这样的:

JavaScript 事件代理需要注意的地方

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

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 事件代理需要注意的地方

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

以上就是JavaScript 事件代理需要注意的地方的详细内容,更多关于JavaScript 事件代理的资料请关注三水点靠木其它相关文章!

Javascript 相关文章推荐
jquery ajax提交表单数据的两种方式
Nov 24 Javascript
Javascript学习笔记之 函数篇(一) : 函数声明和函数表达式
Jun 24 Javascript
JavaScript转换与解析JSON方法实例详解
Nov 24 Javascript
JS实现合并两个数组并去除重复项只留一个的方法
Dec 17 Javascript
轻松搞定jQuery.noConflict()
Feb 15 Javascript
js阻止冒泡和默认事件(默认行为)详解
Oct 20 Javascript
JavaScript判断输入是否为数字类型的方法总结
Sep 28 Javascript
Bootstrap treeview实现动态加载数据并添加快捷搜索功能
Jan 07 Javascript
JS加密插件CryptoJS实现的Base64加密示例
Aug 16 Javascript
小程序开发踩坑:页面窗口定位(相对于浏览器定位)(推荐)
Apr 25 Javascript
layui 弹出删除确认界面的实例
Sep 06 Javascript
基于小程序请求接口wx.request封装的类axios请求
Jul 02 Javascript
Vue axios 跨域请求无法带上cookie的解决
Sep 08 #Javascript
详解JavaScript的this指向和绑定
Sep 08 #Javascript
vue点击按钮实现简单页面的切换
Sep 08 #Javascript
Vue filter 过滤器、以及在table中的使用介绍
Sep 07 #Javascript
VUE中setTimeout和setInterval自动销毁案例
Sep 07 #Javascript
vue a标签点击实现赋值方式
Sep 07 #Javascript
JavaScript实现多球运动效果
Sep 07 #Javascript
You might like
可定制的PHP缩略图生成程式(需要GD库支持)
2007/03/06 PHP
PHP array_push 数组函数
2009/12/26 PHP
实现PHP框架系列文章(6)mysql数据库方法
2016/03/04 PHP
PHP+百度AI OCR文字识别实现了图片的文字识别功能
2019/05/08 PHP
Yii框架使用PHPExcel导出Excel文件的方法分析【改进版】
2019/07/24 PHP
js 图片等比例缩放代码
2010/05/13 Javascript
jquery 动态增加,减少input表单的简单方法(必看)
2016/10/12 Javascript
简单的jQuery拖拽排序效果的实现(增强动态)
2017/02/09 Javascript
JS按条件 serialize() 对应标签的使用方法
2017/07/24 Javascript
vue中使用cookies和crypto-js实现记住密码和加密的方法
2018/10/18 Javascript
利用weixin-java-miniapp生成小程序码并直接返回图片文件流的方法
2019/03/29 Javascript
layui监听工具栏的实例(操作列表按钮)
2019/09/10 Javascript
解决layer弹出层的内容页点击按钮跳转到新的页面问题
2019/09/14 Javascript
jQuery 添加元素和删除元素的方法
2020/07/15 jQuery
Python中使用HTMLParser解析html实例
2015/02/08 Python
Python实现求最大公约数及判断素数的方法
2015/05/26 Python
python 环境变量和import模块导入方法(详解)
2017/07/11 Python
Python Requests模拟登录实现图书馆座位自动预约
2018/04/27 Python
在IPython中进行Python程序执行时间的测量方法
2018/11/01 Python
PyQt5实现类似别踩白块游戏
2019/01/24 Python
Linux上使用Python统计每天的键盘输入次数
2019/04/17 Python
Django REST framework 如何实现内置访问频率控制
2019/07/23 Python
django之状态保持-使用redis存储session的例子
2019/07/28 Python
Python爬虫 urllib2的使用方法详解
2019/09/23 Python
tensorflow 保存模型和取出中间权重例子
2020/01/24 Python
俄罗斯游戏商店:Buka
2020/03/01 全球购物
总裁岗位职责
2013/12/04 职场文书
中学生团员自我评价分享
2013/12/07 职场文书
节能宣传周活动总结
2014/05/08 职场文书
单位活动策划方案
2014/08/17 职场文书
群众路线个人剖析材料及整改措施
2014/11/04 职场文书
2014年房产销售工作总结
2014/12/08 职场文书
高三复习计划
2015/01/19 职场文书
2015年企业工作总结范文
2015/04/28 职场文书
2015年城管执法工作总结
2015/07/23 职场文书
致运动员的广播稿
2015/08/19 职场文书