DOM事件机制简述(下)

158 阅读2分钟

事件委托

事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。


场景一
要给100个按钮添加click事件,咋办?

<body>
    <div id="div1">
        <button>click 1</button>
        <button>click 2</button>
        <button>click 3</button>
        ...
        <button>click 99</button>
        <button>click 100</button>
    </div>

:监听这100个按钮的祖先,等冒泡的时候判断target是不是这100个那按钮中的一个。

div1.addEventListener('click',(e) => {
    const t = e.target
    if(t.tagName.toLowerCase() === "button")
        console.log(t.textContent+'按钮被点击了')
})

场景二
要监听目前不存在的元素的click事件,咋办?

<body**>**
    <div id="div1">
    
    </div>
    <script>
setTimeout(() => {
    const button = document.createElement('button')
    button.textContent = 'click 1'
    div1.appenChild(button)
},100)
    </script>
</body>

:监听祖先,等点击的时候看看是不是我想要监听的元素即可。

div1.addEventListener('click',(e) => {
    const t = e.target
    if(t.tagName.tolowerCase() === "button"){ // target元素的标签名变小写后匹配
        console.log('button被点击了')
    }
})

从上两个场景可看出事件委托有两个优点
1、省监听数(内存)。
2、可以监听动态元素。

根据事件委托原理和上述代码示例,可以封装事件委托。根据场景#testDiv>li写出这样一个函数on('click', '#testDiv', 'li', fn),当用户点击#testDiv 里的li元素时,调用fn函数。即判断target是否匹配'li'

示例代码1

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

然而示例代码1只解决了target元素与选择器元素完全相等(匹配)的情形,即 t.matches(selector)?。当遇到下面情况上述函数就无法委托相关事件。

示例代码2

setTimeout(() => {
const button = document.createElement('button')
const span = document.createElement('span')
span.textContent = "click 1"
button.appendChild(span)
div1.appendchild(button)
},1000)

on('click', '#div1', 'button', () => {
    console.log('button 被点击了')
})

在示例代码2中,selector元素(button)与target元素(span)不匹配,on函数没有执行。所以我们需要改写一下on函数。根据冒泡原理,从target元素开始,按冒泡顺序遍历target的祖先元素和选择器元素进行匹配,直到传播到委托元素范围(scope)边界,即冒泡到#div1结束。当途中的某个祖先元素与选择器元素相匹配时,就传入匹配的祖先元素执行fn函数。

改写的示例代码1,on函数

function on(eventType, element, selector, fn) {
  element.addEventListener(eventType, e => {
    let el = e.target
    while (!el.matches(selector)) {    // 递归冒泡target的祖先元素
      if (element === el) {            // 当与selector元素匹配时
        el = null
        break
      }
      el = el.parentNode
    }
    el && fn.call(el, e, el)           // 传入匹配的祖先元素和target元素执行fn
    })
  return element
},

有兴趣,可将示例代码2整合进jQuery,实现
$('#xxx').on('click', 'li', fn)


其他内容

提问:JS支持事件吗?
:支持,也不支持。
本简述DOM事件不属于JS的功能,属于浏览器提供的DOM功能。JS只是调用了DOM提供的addEventListener。要让JS支持事件,可以用JS手写一个事件系统即可。


参考资料

MDN Event reference

©转载声明