站点工具

用户工具


# 当让你封装一个事件代理函数时

一天,学生小A兴致勃勃的找我

A: 老师,看我封装了一个事件代理函数,看起来还行吧

```html <body>

<div class="button">1</div>
<div class="button">2</div>
<div class="button">3</div>
<div class="button">4</div>
<div class="other">other</div>

</body> <script> function delegate(eventType, root, selector, handler) {

root.onclick = function(e) {
  if(e.target.classList.contains(selector)) {
    handler(e)
  }
}

}

delegate('click', document.body, 'button', function(e) {

console.log('click button')
console.log(e.target.innerText)

})

  

> 若愚: 不错,代码能运行,不过有很多可以优化的地方。比如 1. 如果button不是class,而是id,代码是不是又要改?而且第三个参数 'button' 让人感觉像传递的是标签名,而你这里明明需要传递的是class的名字。

过了几分钟

> A: 老师,看我改成这样,perfect!

function delegate(eventType, root, selector, handler) { root.onclick = function(e) {

if([...root.querySelectorAll(selector)].includes(e.target)) {
  handler(e)
}

} }

delegate('click', document.body, '.button', function(e) { console.log('click button') console.log(e.target.innerText) })

> 若愚: 不错呀,基本功挺扎实,不过还有些问题,估计你已经发现了,看下面的场景,如果用户点到第一个按钮的span元素上,对用户来说点击的就是button,但实际上e.target是span导致你的判断失效
<body>
  <div class="button"><span>1</span></div>
  <div class="button"><span>2</span></div>
  <div class="button"><span>3</span></div>
  <div class="button">4</div>
  <div class="other">other</div>
</body>

<script> function delegate(eventType, root, selector, handler) { root.onclick = function(e) {

if([...root.querySelectorAll(selector)].includes(e.target)) {
  handler(e)
}

} }

delegate('click', document.body, '.button', function(e) { console.log('click button') console.log(e.target.innerText) }) //点击button1 的 </script>

> A: 其实刚刚写demo的时候我就发现了,搞不定偷偷把button里面的span删除了,看来又被你发现了。我捋一捋:我们要给把用户点击button的事件监听代理到容器body上,当我点击button里面的内容本质上也是点击button,应该也要被监听到,而此时event.target是button里面的span 。 我知道了,事件会冒泡,我们可以根据event.target.parent 层层往上找,只要碰到一个父级元素符合selector的描述,就表示需要被监听到
<body>
  <div class="button"><span>1</span></div>
  <div class="button">2</div>
  <div class="button">3</div>
  <div class="button">4</div>
  <div class="other">other</div>
</body>

<script> function delegate(eventType, root, selector, handler) {

root.onclick = function(e) {
  let target = e.target
  while(target && target !== root) {
    if([...root.querySelectorAll(selector)].includes(target)) {
      handler(e)
      break
    }
    target = target.parentNode
  }
}

}

delegate('click', document.body, '.button', function(e) {

console.log('click button')
console.log(e.target.innerText)

})

</script>

> 若愚: 很棒呀,再提三个优化的点。  
> 1\. delegate里用addEventListener而不是onclick 绑定事件,否则你多次执行delegate之前的设置就无效了。  
> 2\. 你可以了解一下 event.composedPath ,这个是个数组,包含事件从目标到根节点经历的所有DOM对象,你可以根据selector元素在不在这个数组里来判断,这样就不用while循环了。  
> 3\. handler函数里面的this也设置一下

  

> A:这个我之前我之前还真没注意到,稍等查一下API... 搞定了!
<body>
  <div class="button"><span>1</span></div>
  <div class="button"><span>2</span></div>
  <div class="button"><span>3</span></div>
  <div class="button"><span>4</span></div>
  <div class="other">other</div>
</body>

<script> function delegate(eventType, root, selector, handler) { root.addEventListener(eventType, function(e) {

let path = e.path || e.composedPath()
const element = [...this.querySelectorAll(selector)].find(el => path.includes(el))
if(element) handler.bind(element)(e)

}) }

delegate('click', document.body, '.button', function(e) { console.log('click button') console.log(e) console.log(this) console.log(this.innerText) })

delegate('click', document.body, '.other', function(e) { console.log('click other') console.log(e) console.log(this) })

</script>

> 非常棒, 不过需要注意这个 event.composedPath有些新,如果需要适配IE浏览器不要考虑用

[](https://link.zhihu.com/?target=http%3A//js.jirengu.com/gumaq/edit%3Fhtml%2Coutput)

  

自荐一下我的[前端系统课](https://link.zhihu.com/?target=https%3A//mp.weixin.qq.com/s/1svw7rUXLK4SYgGngEQuew),文章内容绝大多数都分散在课程的主线任务、项目、日常拓展课直播里

> 饥人谷一直致力于培养有灵魂的编程者,打造专业有爱的国内前端技术圈子。如造梦师一般帮助近千名不甘寂寞的追梦人把编程梦变为现实,他们以饥人谷为起点,足迹遍布包括facebook、阿里巴巴、百度、网易、京东、今日头条、大众美团、饿了么、ofo在内的国内外大小企业。 了解培训课程:加微信 [xiedaimala03](https://wiki.jirengu.com/lib/exe/fetch.php?w=400&tok=5c45ca&media=%E9%A5%A5%E4%BA%BA%E8%B0%B7%E4%BC%81%E4%B8%9A%E5%BE%AE%E4%BF%A1%E5%B0%8F%E5%8A%A9%E7%90%86black.png),官网:https://jirengu.com
若愚 · 2023/02/08 19:04 · 当让你封装一个事件代理函数时.1675854273.txt.gz