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 相关文章推荐
JavaScript 的继承
Oct 01 Javascript
jquery入门—选择器实现隔行变色实例代码
Jan 04 Javascript
js中reverse函数的用法详解
Dec 26 Javascript
JS将滑动门改为选项卡(需鼠标点击)的实现方法
Sep 27 Javascript
详解JavaScript中|单竖杠运算符的使用方法
May 23 Javascript
JavaScript中setter和getter方法介绍
Jul 11 Javascript
简单理解js的冒泡排序
Dec 19 Javascript
Vue路由跳转问题记录详解
Jun 15 Javascript
js实现从左向右滑动式轮播图效果
Jul 07 Javascript
一个基于react的图片裁剪组件示例
Apr 18 Javascript
详解如何制作并发布一个vue的组件的npm包
Nov 10 Javascript
详解微信小程序用定时器实现倒计时效果
Apr 30 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转盘抽奖接口实例
2015/02/09 PHP
php实现xml与json之间的相互转换功能实例
2016/07/07 PHP
PHP使用FFmpeg获取视频播放总时长与码率等信息
2016/09/13 PHP
网上应用的一个不错common.js脚本
2007/08/08 Javascript
扩展Jquery插件处理mouseover时内部有子元素时发生样式闪烁
2011/12/08 Javascript
简单的代码实现jquery定时器
2013/11/17 Javascript
JavaScript输入邮箱自动提示实例代码
2014/01/13 Javascript
JavaScript两种跨域技术全面介绍
2014/04/16 Javascript
JavaScript父子窗体间的调用方法
2015/03/31 Javascript
javascript实现全角转半角的方法
2016/01/23 Javascript
BootStrap框架个人总结(bootstrap框架、导航条、下拉菜单、轮播广告carousel、栅格系统布局、标签页tabs、模态框、菜单定位)
2016/12/01 Javascript
JS前端笔试题分析
2016/12/19 Javascript
Vue.js路由vue-router使用方法详解
2017/03/20 Javascript
EasyUI的TreeGrid的过滤功能的解决思路
2017/08/08 Javascript
Vue.js实现按钮的动态绑定效果及实现代码
2017/08/21 Javascript
详解node服务器中打开html文件的两种方法
2017/09/18 Javascript
postman+json+springmvc测试批量添加实例
2018/03/31 Javascript
JavaScript调用模式与this关键字绑定的关系
2018/04/21 Javascript
jQuery实现的3D版图片轮播示例【滑动轮播】
2019/01/18 jQuery
jQuery.parseJSON()函数详解
2019/02/28 jQuery
[03:31]DOTA2英雄基础教程 大地之灵
2013/12/17 DOTA
python魔法方法-属性转换和类的表示详解
2016/07/22 Python
Django+Ajax+jQuery实现网页动态更新的实例
2018/05/28 Python
Flask核心机制之上下文源码剖析
2018/12/25 Python
python爬虫 基于requests模块发起ajax的get请求实现解析
2019/08/20 Python
css3实现一款模仿iphone样式的注册表单
2013/03/20 HTML / CSS
CheapTickets香港机票预订网站:CheapTickets.hk
2019/06/26 全球购物
省级四好少年事迹材料
2014/01/25 职场文书
《长城》教学反思
2014/02/14 职场文书
外语专业毕业生自荐信
2014/04/14 职场文书
就业协议书样本
2014/08/20 职场文书
荆州古城导游词
2015/02/06 职场文书
民主评议党员个人总结
2015/02/13 职场文书
MySQL 查询速度慢的原因
2021/05/25 MySQL
聊聊Python String型列表求最值的问题
2022/01/18 Python
nginx常用配置conf的示例代码详解
2022/03/21 Servers