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


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

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

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

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

    先看看react是怎么解决的

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

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

    html

    1. <div class="a">
    2. <div class="b">
    3. <div class="e">点击<b>捣蛋鬼E</b>,会执行
    4. <B>e,b,a</B>函数
    5. </div>
    6. <div class="c">点击<b>捣蛋鬼C,stop</b>,执行执行<b>c</b>函数</div>
    7. <div class="f">点击<b>捣蛋鬼F</b>,会执行<b>f,b,a</b>函数</div>
    8. </div>
    9. </div>

    javascript:
    实现一个对象

    • 内部维护点击事件的数组
    • 可以通过构造函数传参的方式绑定委托事件的dom,如果不传默认是body
    • 动态给事件回调函数的e对象添加_stop=true来阻止冒泡
    1. function ProxyClickEvent(proxyBox) {
    2. var self = this;
    3. self.eventPool = [];
    4. if (proxyBox) {
    5. self._proxyBox = proxyBox;
    6. } else {
    7. self._proxyBox = document.body;
    8. }
    9. self.__bindEvent = self._bindEvent.bind(self);
    10. self._proxyBox.addEventListener("click", self.__bindEvent)
    11. }
    12. ProxyClickEvent.prototype._bindEvent = function(e) {
    13. let self = this;
    14. let target = e.target;
    15. for (let i = self.eventPool.length - 1; i >= 0; i--) {
    16. let event = self.eventPool[i];
    17. let fn = event.fn;
    18. /**
    19. * 1.如果target等于事件对应的dom
    20. * 2.否则事件dom是target的父节点&&e._target不为false
    21. * 3.如果e._stop为true时候,break
    22. * 那就执行回调函数
    23. *
    24. * **/
    25. if (event.dom == e.target) {
    26. fn(e);
    27. } else if (!e.__stop && event.dom.contains(e.target)) {
    28. fn(e);
    29. } else if (e.__stop) {
    30. break;
    31. }
    32. }
    33. }
    34. ProxyClickEvent.prototype.add = function(dom, fn) {
    35. if (!(dom instanceof HTMLElement)) {
    36. console.error("invalid parameter dom", dom, "fn", fn)
    37. throw "function add arg1 should be a HTMLElement,but get " + (typeof dom)
    38. }
    39. if (typeof fn !== "function") {
    40. console.error("invalid parameter dom", dom, "fn", fn)
    41. throw "function add arg2 should be a function,but get " + (typeof fn)
    42. }
    43. this.eventPool.push({
    44. dom: dom,
    45. fn: fn
    46. })
    47. }
    48. ProxyClickEvent.prototype.remove = function(dom, fn) {
    49. this.eventPool = this.eventPool.filter(item => item.dom != dom && item.fn != fn)
    50. }
    51. ProxyClickEvent.prototype.destroyed = function() {
    52. this.eventPool = [];
    53. self._proxyBox.removeEventListener("click", this.__bindEvent);
    54. }

    使用方式
     

    1. let proxyEvent = new ProxyClickEvent();
    2. window.onload = function() {
    3. proxyEvent.add(document.querySelector(".a"), function() {
    4. console.log("点击了a")
    5. })
    6. proxyEvent.add(document.querySelector(".b"), function() {
    7. console.log("点击了b")
    8. })
    9. proxyEvent.add(document.querySelector(".c"), function(e) {
    10. e.__stop = true;
    11. console.log("点击了c")
    12. })
    13. proxyEvent.add(document.querySelector(".e"), function() {
    14. console.log("点击了e")
    15. })
    16. proxyEvent.add(document.querySelector(".f"), function() {
    17. console.log("点击了f")
    18. })
    19. }


    这存在在个问题①

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


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

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

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

    1. <!DOCTYPE html>
    2. <html lang="en">
    3. <head>
    4. <meta charset="UTF-8">
    5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
    6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
    7. <title>Document</title>
    8. </head>
    9. <body>
    10. <div class="a" clickFn="fna">
    11. <div class="b" clickFn="fnb">
    12. <div class="e" clickFn="fne">点击<b>捣蛋鬼E</b>,会执行
    13. <B>e,b,a</B>函数
    14. </div>
    15. <div class="c" clickFn="fnc">点击<b>捣蛋鬼C,stop</b>,执行执行<b>c</b>函数</div>
    16. <div class="f" clickFn="fnf">点击<b>捣蛋鬼F</b>,会执行<b>f,b,a</b>函数</div>
    17. </div>
    18. </div>
    19. <script src="./ProxyClickEvent.js"></script>
    20. <script>
    21. var funs = {
    22. fna: function() {
    23. console.log("点击a")
    24. },
    25. fnb: function() {
    26. console.log("点击b")
    27. },
    28. fnc: function(e) {
    29. e.__stop = true;
    30. console.log("点击c")
    31. },
    32. fnd: function() {
    33. console.log("点击d")
    34. },
    35. fne: function() {
    36. console.log("点击e")
    37. },
    38. fnf: function() {
    39. console.log("点击f")
    40. }
    41. }
    42. window.onload = function() {
    43. let proxyEvent = new ProxyClickEvent();
    44. let clickDoms = document.querySelectorAll("[clickFn]");
    45. for (let i = 0; i < clickDoms.length; i++) {
    46. let dom = clickDoms[i];
    47. let fnStr = clickDoms[i].getAttribute("clickFn");
    48. proxyEvent.add(dom, funs[fnStr])
    49. }
    50. }
    51. </script>
    52. </body>
    53. </html>

    存在问题②


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

    解决方案

    1. 暴力定时检测更新dom是否存在,过滤掉数组里面dom不存在的元素,
    2. 并且提供函数destroy时候销毁定时器
    3. 因为定时任务只是更新数组变量不涉及费时操作,所以不需要当心性能问题
    4. 如果是单页面的话,必须在页面销毁前调用destroy函数,销毁定时器

    建议:如果涉及到dom的父子节点的增删的话,不使用这套代码,因为add的次序被破坏了,会影响“冒泡顺序”

    代码:

    1. function ProxyClickEvent(proxyBox) {
    2. var self = this;
    3. self.eventPool = [];
    4. if (proxyBox) {
    5. self._proxyBox = proxyBox;
    6. } else {
    7. self._proxyBox = document.body;
    8. }
    9. //更新数组,过滤掉被销毁的dom的事件
    10. self._bind_clearRemoveDomEventItem = self._clearRemoveDomEventItem.bind(self);
    11. //定时过滤dom不存在的元素
    12. self._timer = setInterval(function() {
    13. self._bind_clearRemoveDomEventItem();
    14. }, 1000)
    15. self.__bindEvent = self._bindEvent.bind(self);
    16. self._proxyBox.addEventListener("click", self.__bindEvent)
    17. }
    18. ProxyClickEvent.prototype._clearRemoveDomEventItem = function() {
    19. if (this.eventPool.some((el) => !document.body.contains(el.dom))) {
    20. this.eventPool = this.eventPool.filter(item => document.body.contains(item.dom));
    21. }
    22. }
    23. ProxyClickEvent.prototype._bindEvent = function(e) {
    24. let self = this;
    25. let target = e.target;
    26. for (let i = self.eventPool.length - 1; i >= 0; i--) {
    27. let event = self.eventPool[i];
    28. let fn = event.fn;
    29. /**
    30. * 1.如果target等于事件对应的dom
    31. * 2.否则事件dom是target的父节点&&e._target不为false
    32. * 3.如果e._stop为true时候,break
    33. * 那就执行回调函数
    34. *
    35. * **/
    36. if (event.dom == target) {
    37. fn(e);
    38. } else if (!e.__stop && event.dom.contains(target)) {
    39. fn(e);
    40. } else if (e.__stop) {
    41. break;
    42. }
    43. }
    44. }
    45. ProxyClickEvent.prototype.add = function(dom, fn) {
    46. if (!(dom instanceof HTMLElement)) {
    47. console.error("invalid parameter dom", dom, "fn", fn)
    48. throw "function add arg1 should be a HTMLElement,but get " + (typeof dom)
    49. }
    50. if (typeof fn !== "function") {
    51. console.error("invalid parameter dom", dom, "fn", fn)
    52. throw "function add arg2 should be a function,but get " + (typeof fn)
    53. }
    54. this.eventPool.push({
    55. dom: dom,
    56. fn: fn
    57. })
    58. }
    59. ProxyClickEvent.prototype.remove = function(dom, fn) {
    60. this.eventPool = this.eventPool.filter(item => item.dom != dom && item.fn != fn)
    61. }
    62. ProxyClickEvent.prototype.destroy = function() {
    63. this.eventPool = [];
    64. this._proxyBox.removeEventListener("click", this.__bindEvent);
    65. clearInterval(this._timer);
    66. }


     

  • 相关阅读:
    leetcode 594.最长和谐子序列(滑动窗口)
    精品基于Javaweb的酒店民宿管理推荐平台SSM
    【点云处理】点云法向量估计及其加速(5)
    Unity之NetCode多人网络游戏联机对战教程(3)--NetworkObject组件讲解
    C++中有哪些常用的算法和数据结构?
    反压缩 js ,我的万花筒写轮眼开了,CV 能力大幅提升
    基于SSM+Jsp的书店仓库管理系统
    Nim 枚举类型 对性能的影响
    GitHub下载太慢的解决方案
    npm报错
  • 原文地址:https://blog.csdn.net/u014071104/article/details/125458507