事件绑定

通用事件绑定

/**
 * 通用事件绑定
 * 
 * @param {Element} el 事件处理程序所在元素
 * @param {String} type 事件类型
 * @param {Function} fn 事件处理程序
 * @param {String} [proxy=null] 被事件委托的元素,即 proxy 上的事件将冒泡到 el 上触
 *                              发监听程序
 * @param {boolean} [capture=false] 是否于捕获阶段触发事件处理程序
 */
function bindListener(el, type, fn, proxy = null, capture = false) {
  // 长单词无法在代码压缩时被压缩,使用工厂函数 bindListener 代替每次
  // addEventListener 的调用
  el.addEventListener(type, function (evt) {
    const target = evt.target

    /**
     * 1. Element.matches(string) 如果元素 Element 被指定的选择器字符串 string 选择
     *    ,该方法返回 true
     * 2. 即使用 matches 方法判断 target 是否是目标元素,而不是使用
     *    evt.target.className === 'app' 等方法判断
     */
    if (proxy && target.matches(proxy)) {

      // 指定 this 对象就相当于在 target 上调用 fn 监听程序,就相当于在 target 设置了
      // 监听程序
      fn.call(target, evt)
    } else {
      // target 为非委托(被代理)元素
      fn(evt)
    }
  }, capture)
}

const el = document.querySelector('.target')
bindListener(el, 'click', evt => {
  evt.preventDefault() // 阻止 type 事件的默认行为
  evt.stopPropagation() // 阻止 type 事件进一步传播(如冒泡)
  console.log('activate listener')
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

事件冒泡

DOM2 级事件流,分为事件捕获阶段、处于目标阶段、事件冒泡阶段。事件捕获阶段不会涉及事件目标。

事件冒泡是指事件由最具体的元素接收后,然后逐级向上沿 DOM 树,传播到顶层 document 节点。

event.stopPropagation() 可阻止事件的进异步传播。

事件委托(代理)

事件委托利用事件冒泡原理,只使用一个事件处理程序监听所有子节点的所有同类事件。

<body>
  <div class="app">
    <p class="area-1">area 1</p>
    <p class="area-2">area 2</p>
  </div>
  <script>
    const target = document.querySelector('.app')
    target.addEventListener('click', evt => {
      const target = evt.target
      // do something
    }, false)
  </script>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13

示例中,app 所有的后代节点的 click 事件都将触发 app 的 listener。

事件委托优势

  • 代码简洁

  • 减少内存占用

事件委托的应用

  • 无限加载时,被加载元素的事件监听。新加入的子元素不用添加事件监听程序,转而在父容器元素监听所有子元素的某类事件。