DOM事件和事件委托

156 阅读4分钟

概述

DOM 允许 JavaScript 对 HTML 事件做出反应。系统内发生的动作或事情(例如点击按钮,混动滚轮等操作),可以通过某种方式来对发生的动作或事情做出回应。

在标准发布之前,ie5 浏览器认为应该先调用最内部的函数,而 Netscape (网景)反其道而行之,认为应该先调用外部的函数,对开发者造成了很多的不便。

直到 2002 年,W3C 发布标准,规定浏览器应该同时支持从外向内以及从内向外两种方式来寻找监听函数。

从外向内寻找的方式叫做事件捕获,而从内向外寻找的方式叫做事件冒泡

事件绑定 api

在规则被制定之前,不同的浏览器有不同的 api,例:

IE5 浏览器:

xxx.attachEvent('onclick',fn)//事件冒泡

Netscape(网景)浏览器:

xxx.addEventListener('click',fn)//事件捕获

在 W3C 制定标准后,事件绑定 api 被统一为:

xxx.addEventListener('click',fn,bool)

通过传入一个 bool 参数来决定寻找函数的方式,bool 为 true 时,通过事件捕获(外 → 内)来寻找 fn。

不传 bool 参数或 bool 值为 falsy 时,则使用事件冒泡(内 → 外)来寻找 fn。

这里提一个特例,如果被监听的元素只有一个(即 fn 分别在捕获和冒泡两个阶段监听事件,用户操作的元素就是开发者监听的元素)时,先用哪一种方式监听,就先执行哪一个,与传入的 bool 值无关。

取消冒泡

事件捕获不能被取消,但是事件冒泡可以。

使用e.stopPropagation()可以中断冒泡,告诉浏览器不再继续向上寻找函数,一般用于封装一些独立组件。

但是取消冒泡也有特例。。。有些事件冒泡不能被取消,例如滚动条(scroll)事件。

虽然 scroll 事件不能取消冒泡,但可以通过其他方式阻止滚动事件,例如阻止 wheel 滚轮事件,再使用 CSS 隐藏滚动条即可。(注意:即便隐藏滚动条,JS 依然可以修改 scrollTop)

自定义事件

除了 100 多种浏览器自带事件(列表在 MDN 上,点我查阅)之外,还可以自定义事件。

举个栗子:

xxx.addEventListener('click',()=>{
    const event = new CustomEvent('Name',{
            detail
    })//定义新的事件,事件名为'Name',detail为自定义事件内容。
xxx.dispatchEvent(event)//点击xxx,便会触发Name事件。
})

xxx.addEventListener('Name',(e)=>{
console.log(e.detail)
})//监听Name事件,事件触发时,打印出事件内容detail

事件委托

通过定义新变量,只需要指定一个事件处理,就可以管理同一类型的所有事件。 可以理解为找个中介,委托一个对象监听我本应该监听的事件。

场景 1:监听大量事件

例如:

div#a>button*100(创建一个 id 为“a”的 div 元素 ,其中有 100 个 button 。)

a.addEventListener('click'()=>{
    const t = e.target
    if(t.tagNmae.toLowerCase() === 'button'){
        console.log('button点击了')
        console.log('button内容是'+t.textContent)//可以知道点了100个中的哪一个
    }
})

场景 2:通过“监听一个元素的祖先元素,点击时判断是否为想要监听的元素”来监听一个目前不存在的元素的点击事件。

例如:

页面中有一个 id 为“a”的 div 元素

setTimeout(()=>{
    const button = document.createElement('button')
    button.textContent = 'click'
    a.appendChild(button)
},1000)

运行代码。在 1000ms 延迟后,创建一个 button 元素,内容为“click”,并将 button 放入 div 中。(运行代码时 button 元素还未被创建,所以属于一个“目前不存在”的元素)

那么如何监听这个 click 呢?通过事件委托即可。

a.addEventListener('click',e=>{
    const t = e.target
    if(t.tagName.toLowerCase()==='button'){
        console.log('button被点击了')
    }
})//div为动态判断,无论之前是什么样,只看点击时标签是否为button

事件委托的优点

  1. 省内存
  2. 可以监听动态元素

补充

本篇博客提到的事件并不是 JS 的功能,他属于浏览器提供的 DOM 的功能。JS 只是调用了 DOM 提供的 addEventListener 而已。

当然 js 也是可以支持事件的,只是需要手写一个事件系统,而我作为一个菜鸡,暂时还是不考虑比较好。。。