欢迎访问 生活随笔!

生活随笔

当前位置: 首页 > 编程资源 > 编程问答 >内容正文

编程问答

点击事件 事件委托的情况下实现阻止冒泡

发布时间:2024/3/24 编程问答 54 豆豆
生活随笔 收集整理的这篇文章主要介绍了 点击事件 事件委托的情况下实现阻止冒泡 小编觉得挺不错的,现在分享给大家,帮大家做个参考.

最近在研究react的事件,都说他是合成事件,整个dom唯一绑定事件的是document。
我想这不就是事件委托么,有啥好研究的,于是我去重温了下事件委托,顺便看了阻止冒泡。

突然突发奇想,如果两个结合在一起呢?

能否实现子节点点击了,父节点不能触发点击事件

好像n年前我还是小萌新,在广电商做切图仔的时候请教过我的组长,他没有给到我满意的答案。今天这个想法又冒出来了。
我得解决它一下。

先看看react是怎么解决的

它用了 e.nativeEvent.stopImmediatePropagation();
nativeEvent.stopImmediatePropagation是react自己新增的属性方法。

那我的想法就来了。我也可以啊。

html

<div class="a"><div class="b"><div class="e">点击<b>捣蛋鬼E</b>,会执行<B>e,b,a</B>函数</div><div class="c">点击<b>捣蛋鬼C,stop</b>,执行执行<b>c</b>函数</div><div class="f">点击<b>捣蛋鬼F</b>,会执行<b>f,b,a</b>函数</div></div></div>

javascript:
实现一个对象

  • 内部维护点击事件的数组
  • 可以通过构造函数传参的方式绑定委托事件的dom,如果不传默认是body
  • 动态给事件回调函数的e对象添加_stop=true来阻止冒泡
function ProxyClickEvent(proxyBox) {var self = this;self.eventPool = [];if (proxyBox) {self._proxyBox = proxyBox;} else {self._proxyBox = document.body;}self.__bindEvent = self._bindEvent.bind(self);self._proxyBox.addEventListener("click", self.__bindEvent)}ProxyClickEvent.prototype._bindEvent = function(e) {let self = this;let target = e.target;for (let i = self.eventPool.length - 1; i >= 0; i--) {let event = self.eventPool[i];let fn = event.fn;/*** 1.如果target等于事件对应的dom* 2.否则事件dom是target的父节点&&e._target不为false* 3.如果e._stop为true时候,break* 那就执行回调函数* * **/if (event.dom == e.target) {fn(e);} else if (!e.__stop && event.dom.contains(e.target)) {fn(e);} else if (e.__stop) {break;}}}ProxyClickEvent.prototype.add = function(dom, fn) {if (!(dom instanceof HTMLElement)) {console.error("invalid parameter dom", dom, "fn", fn)throw "function add arg1 should be a HTMLElement,but get " + (typeof dom)}if (typeof fn !== "function") {console.error("invalid parameter dom", dom, "fn", fn)throw "function add arg2 should be a function,but get " + (typeof fn)}this.eventPool.push({dom: dom,fn: fn})}ProxyClickEvent.prototype.remove = function(dom, fn) {this.eventPool = this.eventPool.filter(item => item.dom != dom && item.fn != fn)}ProxyClickEvent.prototype.destroyed = function() {this.eventPool = [];self._proxyBox.removeEventListener("click", this.__bindEvent);}

使用方式
 

let proxyEvent = new ProxyClickEvent();window.onload = function() {proxyEvent.add(document.querySelector(".a"), function() {console.log("点击了a")})proxyEvent.add(document.querySelector(".b"), function() {console.log("点击了b")})proxyEvent.add(document.querySelector(".c"), function(e) {e.__stop = true;console.log("点击了c")})proxyEvent.add(document.querySelector(".e"), function() {console.log("点击了e")})proxyEvent.add(document.querySelector(".f"), function() {console.log("点击了f")})}


这存在在个问题①

必须得从父级到子节点,按顺序add,否则事件“冒泡顺序”会错乱


目前先假设使用场景场景:
用户可以自己明确且可以控制从父级到自己按顺序的add

例如以下场景就非常适用:

通过dom自定义属性clickFn来绑定函数名字,然后获取携带clickFn属性的dom,遍历后追加点击事件
 

<!DOCTYPE html> <html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title> </head><body><div class="a" clickFn="fna"><div class="b" clickFn="fnb"><div class="e" clickFn="fne">点击<b>捣蛋鬼E</b>,会执行<B>e,b,a</B>函数</div><div class="c" clickFn="fnc">点击<b>捣蛋鬼C,stop</b>,执行执行<b>c</b>函数</div><div class="f" clickFn="fnf">点击<b>捣蛋鬼F</b>,会执行<b>f,b,a</b>函数</div></div></div><script src="./ProxyClickEvent.js"></script><script>var funs = {fna: function() {console.log("点击a")},fnb: function() {console.log("点击b")},fnc: function(e) {e.__stop = true;console.log("点击c")},fnd: function() {console.log("点击d")},fne: function() {console.log("点击e")},fnf: function() {console.log("点击f")}}window.onload = function() {let proxyEvent = new ProxyClickEvent();let clickDoms = document.querySelectorAll("[clickFn]");for (let i = 0; i < clickDoms.length; i++) {let dom = clickDoms[i];let fnStr = clickDoms[i].getAttribute("clickFn");proxyEvent.add(dom, funs[fnStr])}}</script> </body></html>

存在问题②


某个元素删除后,点击时候回调函数依旧会触发
 

解决方案

  • 暴力定时检测更新dom是否存在,过滤掉数组里面dom不存在的元素,
  • 并且提供函数destroy时候销毁定时器
  • 因为定时任务只是更新数组变量不涉及费时操作,所以不需要当心性能问题
  • 如果是单页面的话,必须在页面销毁前调用destroy函数,销毁定时器
  • 建议:如果涉及到dom的父子节点的增删的话,必须清空数组,然后再遍历添加到数组

    代码:

    function ProxyClickEvent(proxyBox) {var self = this;self.eventPool = [];if (proxyBox) {self._proxyBox = proxyBox;} else {self._proxyBox = document.body;}//更新数组,过滤掉被销毁的dom的事件self._bind_clearRemoveDomEventItem = self._clearRemoveDomEventItem.bind(self);//定时过滤dom不存在的元素self._timer = setInterval(function() {self._bind_clearRemoveDomEventItem();}, 1000)self.__bindEvent = self._bindEvent.bind(self);self._proxyBox.addEventListener("click", self.__bindEvent) } ProxyClickEvent.prototype._clearRemoveDomEventItem = function() {if (this.eventPool.some((el) => !document.body.contains(el.dom))) {this.eventPool = this.eventPool.filter(item => document.body.contains(item.dom));} } ProxyClickEvent.prototype._bindEvent = function(e) {let self = this;let target = e.target;for (let i = self.eventPool.length - 1; i >= 0; i--) {let event = self.eventPool[i];let fn = event.fn;/*** 1.如果target等于事件对应的dom* 2.否则事件dom是target的父节点&&e._target不为false* 3.如果e._stop为true时候,break* 那就执行回调函数* * **/if (event.dom == target) {fn(e);} else if (!e.__stop && event.dom.contains(target)) {fn(e);} else if (e.__stop) {break;}} } ProxyClickEvent.prototype.add = function(dom, fn) {if (!(dom instanceof HTMLElement)) {console.error("invalid parameter dom", dom, "fn", fn)throw "function add arg1 should be a HTMLElement,but get " + (typeof dom)}if (typeof fn !== "function") {console.error("invalid parameter dom", dom, "fn", fn)throw "function add arg2 should be a function,but get " + (typeof fn)}this.eventPool.push({dom: dom,fn: fn}) } ProxyClickEvent.prototype.remove = function(dom, fn) {this.eventPool = this.eventPool.filter(item => item.dom != dom && item.fn != fn) } ProxyClickEvent.prototype.destroy = function() {this.eventPool = [];this._proxyBox.removeEventListener("click", this.__bindEvent);clearInterval(this._timer); }


    已封装成npm包,点击去查看

    总结

    以上是生活随笔为你收集整理的点击事件 事件委托的情况下实现阻止冒泡的全部内容,希望文章能够帮你解决所遇到的问题。

    如果觉得生活随笔网站内容还不错,欢迎将生活随笔推荐给好友。