DOM 事件与事件委托

270 阅读3分钟

2002 年,W3C 发布标准

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

术语

  • 从外向内找监听函数,叫事件捕获
  • 从内向外找监听函数,叫事件冒泡

开发者自己选择把调用放在 捕获阶段 还是放在 冒泡阶段

image.png

图片来源:饥人谷

addEventListener

事件绑定 API

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

如果 bool 不传或为 falsy

  • 就让 fn 走冒泡,即当浏览器在冒泡阶段发现有 baba 有 fn 监听函数,就会调用 fn,并提供事件信息

如果 bool 为 true

  • 就让 fn 走捕获,即当浏览器在捕获阶段发现 baba 有 fn 监听函数,就会调用 fn,并提供事件信息

target v.s. currentTarget

区别

  • e.target - 用户操作的元素
  • e.currentTarget - 程序员监听的元素
  • this 是 e.currentTarget,不推荐使用

举例

  • div > span{文字},用户点击文字
  • e.target 就是 span
  • e.currentTarget 就是 div

特例

背景

  • 只有一个 div 被监听(不考虑父子同时被监听)
  • fn 分别在捕获阶段和冒泡阶段监听 click 事件
  • 用户点击的元素就是开发者监听的

代码

  • div.addEventListener('click', f1)
  • div.addEventListener('click', f2, true)
  • 谁先监听谁先执行

取消冒泡

捕获不可取消,但冒泡可以

  • e.stopPropagation() 可中断冒泡,浏览器不再向上走
  • 一般用于封装某些独立的组件

不可取消冒泡

有些事件不可取消冒泡

  • MDN 搜索 SCroll event,看到 Bubbles 和 Cancelable
  • Bubbles 的意思是该事件是否冒泡
  • Cancelable 的意思是开发者是否可以取消冒泡

插曲:如何阻止滚动

scroll 事件不可取消冒泡

  • 阻止 scroll 默认动作没用,因先有滚动才有滚动事件
  • 要阻止滚动,可阻止 wheel 和 touchstart 的默认动作
  • 注意你需要找准滚动条所在的元素
  • 但是滚动条还能用,可用 CSS 让滚动条 display: none

CSS 也行

  • 使用 overflow: hidden 可以直接取消滚动条
  • 但此时 JS 依然可以修改 scrollTop

禁止滚动

    // 滚轮默认效果
    x.addEventListener('wheel',(e)=>{
        e.preventDefault()
    })
    
    // 取消手机滑动
    x.addEventListener('touchstart', (e)=>{
         e.preventDefault()
    })

取消滚动条(把滚动条宽度变成0)

    ::-webkit-scrollbar{width: 0 !important}

自定义事件

浏览器自带事件

  • 一共100多种事件, 可在 MDN 查看

定义事件

    button1.addEventListener('click', ()=>{
        const event = new CustomEvent('ivan', {
            detail: {name:'ivan', age:18}
            bubbles: true
            cancelable: false
        })  // 创建一个事件
        button1.diapatchEvent(event)
    })

    div1.addEventListener('ivan', (e)=>{
        console.log('事件触发了')
        console.log(e.detail)
    })

事件委托

优点

  • 省监听数(内存)
  • 可以监听动态元素

封装事件委托

    setTimeout(()=>{
        const button = document.createElement('button')
        button.textContent = 'click 1'
        div1.appendChild(button)
    }, 1000)
    
    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)
            }
        })
    }