106-js-22-事件

概述

事件绑定:普通绑定和代理绑定

事件冒泡

事件绑定

// 通用的事件绑定函数
function bindEvent(elem, type, fn) {
    elem.addEventListener(type, fn)
}

// 普通绑定
const btn1 = document.getElementById('btn1')
bindEvent(btn1, 'click', function (event) {
    // console.log(event.target) // 获取触发的元素
    event.preventDefault() // 阻止默认行为
    alert(this.innerHTML)
})

事件冒泡

事件冒泡:由触发元素一层一层向上传递

阻止事件冒泡:event.stopPropagation()

<div id="div1">
    <p id="p1">激活</p>
    <p id="p2">取消</p>
    <p id="p3">取消</p>
    <p id="p4">取消</p>
</div>
<div id="div2">
    <p id="p5">取消</p>
    <p id="p6">取消</p>
</div>
const p1 = document.getElementById('p1')
bindEvent(p1, 'click', event => {
    // event.stopPropagation() // 阻止冒泡
    console.log('激活')
})
const body = document.body
bindEvent(body, 'click', event => {
    console.log('body clicked')
    console.log(event.target)
})
const div2 = document.getElementById('div2')
bindEvent(div2, 'click', event => {
    console.log('div2 clicked')
    console.log(event.target)
})

事件代理

由于冒泡机制,所以事件会传递到触发元素的父级元素,不需要使用循环为每个同级元素添加事件。

当许多同级元素都需要相同的事件行为时,将事件加在他们的父元素上,比如瀑布流。

由于父级元素可能包含其他的元素,不需要添加事件,所以需要区分。

function bindEvent(elem, type, selector, fn) {
    if (fn == null) {
        fn = selector
        selector = null
    }
    elem.addEventListener(type, event => {
        const target = event.target
        if (selector) {
            // 代理绑定
            if (target.matches(selector)) {
                fn.call(target, event)
            }
        } else {
            // 普通绑定
            fn.call(target, event)
        }
    })
}
<div id="div3">
    <a href="#">a1</a><br>
    <a href="#">a2</a><br>
    <a href="#">a3</a><br>
    <a href="#">a4</a><br>
    <button>加载更多...</button>
</div>
// 代理绑定
const div3 = document.getElementById('div3')
bindEvent(div3, 'click', 'a', function (event) {
    event.preventDefault()
    alert(this.innerHTML)
})

target.matches(selector)

MDN

Element.matches()

如果元素被指定的选择器字符串选择,Element.matches()方法返回true; 否则返回false。

let result = element.matches(selectorString);
  • result 的值为 truefalse.
  • selectorString 是个css选择器字符串.

示例

比如我们有这样的一个 HTML 片段:

<ul id="list">
  <li>item 1</li>
  <li>item 2</li>
  <li>item 3</li>
  ......
  <li>item n</li>
</ul>
// ...... 代表中间还有未知数个 li

我们来实现把 #list 下的 li 元素的事件代理委托到它的父层元素也就是 #list 上:

// 给父层元素绑定事件
document.getElementById('list').addEventListener('click', function (e) {
  // 兼容性处理
  var event = e || window.event;
  var target = event.target || event.srcElement;
  // 判断是否匹配目标元素
  if (target.nodeName.toLocaleLowerCase === 'li') {
    console.log('the content is: ', target.innerHTML);
  }
});

在上述代码中, target 元素则是在 #list 元素之下具体被点击的元素,然后通过判断 target 的一些属性(比如:nodeName,id 等等)可以更精确地匹配到某一类 #list li 元素之上;

使用 Element.matches 精确匹配

如果改变下 HTML 成:

<ul id="list">
  <li className="class-1">item 1</li>
  <li>item 2</li>
  <li className="class-1">item 3</li>
  ......
  <li>item n</li>
</ul>
// ...... 代表中间还有未知数个 li

这里,我们想把 #list 元素下的 li 元素(并且它的 class 为 class-1)的点击事件委托代理到 #list 之上;

如果通过上述的方法我们还需要在 if (target.nodeName.toLocaleLowerCase === 'li') 判断之中在加入一个判断 target.nodeName.className === 'class-1'

但是如果想像 CSS 选择器般做更加灵活的匹配的话,上面的判断未免就太多了,并且很难做到灵活性,这里可以使用 Element.matches API 来匹配;

Element.matches API的基本使用方法: Element.matches(selectorString)selectorString 既是 CSS 那样的选择器规则,比如本例中可以使用 target.matches('li.class-1'),他会返回一个布尔值,如果 target 元素是标签 li 并且它的类是 class-1 ,那么就会返回 true,否则返回 false;

参考

JavaScript 事件委托详解

推荐阅读

MDN-Element/matches