9

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

 3 years ago
source link: https://zhuanlan.zhihu.com/p/364522351
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

前端系统课2104期开班 wx:xiedaimala03

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

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

<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)
})
</script>

若愚: 不错,代码能运行,不过有很多可以优化的地方。比如 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浏览器不要考虑用

自荐一下我的前端系统课,文章内容绝大多数都分散在课程的主线任务、项目、日常拓展课直播里


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK