likes
comments
collection
share

事件处理与委托基础

作者站长头像
站长
· 阅读数 19

入手

<div class="爷爷">
  <div class="爸爸">
    <div class="儿子">
      文字
    </div>
  </div>
</div>
// 给三个div分别添加事件监听fnYe/fnBa/fnEr

点击了谁?

  1. 点击文字,算不算点击儿子?
  2. 点击文字,算不算点击爸爸?
  3. 点击文字,算不算点击爷爷? 答案:都算

调用顺序

  1. 点击文字,最先调用fnYe /fnBa /fnEr中的哪一个函数? 答案:都行。IE5认为先调用fnEr,网景认为先调用fnYe。

W3C

2002年, W3C发布标准

  • 文档名为DOM Level 2 Events Specification。
  • 规定浏览器应该同时支持两种调用顺序。
  • 首先按 爷爷=>爸爸=>儿子 顺序看有没有函数监听。
  • 然后按 儿子=>爸爸=>爷爷 顺序看有没有函数监听。
  • 有监听函数就调用,并提供事件信息,没有就跳过。

术语

从外向内找监听函数,叫事件捕获从内向外找监听函数,叫事件冒泡。 那这样岂不是fnYe/fnBa/fnEr都调用两次?非也!开发者自己选择把fnYe放在捕获阶段还是冒泡阶段。

示意图

事件处理与委托基础

W3C 事件模型

addEventListener

各家的事件绑定API都是什么呢?

  • IE5*: xxx.attachEvent('onclick', fn) // 冒泡
  • 网景:xxx.addEventListener('click',fn) // 捕获

然后W3C就把两家综合起来:xxx.addEventListener('click', fn, bool)

如果bool不传或为falsy:就让fn走冒泡,即当浏览器在冒泡阶段发现xxx有fn监听函数,就会调用fn,并提供事件信息。

如果bool为true:就让fn走捕获,即当浏览器在捕获阶段发现xxx有fn监听函数,就会调用fn,并提供事件信息。

注意:浏览器两个阶段都要进行,只是通过bool值控制什么时候调用fn。

代码演示

jsbin.com/mifiyuhane/… 代码图解 事件处理与委托基础 如图所示:e对象是被传给所有监听函数,事件结束后,e对象就不存在了。

target vs currentTarget

区别:

  • e.target 用户操作的元素
  • e.currentTarget 程序员监听的元素 举例:
  • div > span{文字} 用户点击文字
  • e.target 就是span
  • e.currentTarget 就是div

取消冒泡

e.stopPropagation()

有的事件不能取消冒泡

  • 可以在MDN上搜索,例如scroll event。
  • Bubbles就是该事件是否冒泡。
  • Cancelable就是开发者是否可以取消冒泡。

如何阻止滚动?

由上可知,scroll事件不可取消冒泡,那我们如何阻止滚动呢?

<div id=x>
    <p>1</p>
      ...
    <p>30</p>
</div>
  1. 阻止scroll默认动作没用,因为先有滚动才有滚动事件。
x.addEventListenet('scroll',(e)=>{
  e.stopPropagation()
  e.preventDefault()
})

我们取消冒泡和默认事件,都不能阻止滚动。 2. 要阻止滚动,可阻止wheel和touchstart的默认动作。 注意需要找准滚动条所在的元素 wheel是鼠标滚轮,touchstart针对手机滑动。

x.addEventListener('wheel',(e)=>{
  e.preventDefault()})
x.addEventListener('otuchstart',(e)=>{
  e.preventDefault()})
  1. 但是此时滚动条还显示,用鼠标可以拖动滚动滚动。 我们可以在css里写入下面的语句,就可将滚动条隐藏
::-webkit-scrollbar{
width:0 !important}
  1. CSS使用overflow:hidden也可以直接取消滚动条。但此时JS依然可以修改scrollTop。

自定义事件

浏览器自带事件100多种 事件参考 | MDN (mozilla.org) 那开发者如何在自带事件之外,自定义一个事件? 例如我们要自定义一个xxx的事件。 <button id=btn> 点击时触发 xxx 的事件

btn.addEventListener('click', ()=>{
    const event = new CustomEvent('xxx', {
        detail:{name:'xxx', age:23},
        bubbles: true,
        cancelable: false
    })
    btn.dispatchEvent(event)
})    

这样我们就自定义了一个事件。

事件委托

场景1:假设要给100个按钮添加点击事件,咋办? 监听这100个按钮的祖先,等冒泡的收判断target是不是这100个按钮中的一个。

//html
<div id=div1>
    <button data-id="1">click 1</button>
    ...
    <button>click 2</button>
</div>
// js
div1.addEventListener('click',(e)=>{
    const t = e.target
    if(t.tagName.toLowerCase() === 'button'){
        console.log('button 被点击了')
        console.log(t.textContent)
        console.log(t.dataset.id)
    }
})

场景2:怎么监听目前不存在的元素的点击事件? 监听祖先,等点击的时候看看是不是我想要监听的元素即可。

//html
<div id=div1></div>
// js
setTimeout(()=>{
    const button=document.createElement('button')
    button.textContent = 'click 1'
    div1.apendChild(button)
}, 1000)
 
div1.addEventListener('click), (e)=>{
    const t = e.target
    if(t.tagName.toLowerCase() === 'button'{
        console.log('button 被点击了')
    }
})

优点:省监听数(内存) 可以监听动态元素

怎么封装一个事件委托呢?

// 调用
on('click', '#div1', 'button', ()=?{
    console.log('button 被点击了')
})
//
function on(eventType, element, selector, fn){
    if(!(element instanceof Element)){
       element = document.querySelector(element)
    }
    element.addEventListener(eventType, (e)=>{
        const t = e.target
        if(t.matches(selector)){
            fn(e)
        }
    })
}
转载自:https://juejin.cn/post/7245536612628824125
评论
请登录