DOM事件模型

100 阅读5分钟

事件处理程序

事件是某事发生的信号,所有DOM节点都生成这样的信号。

为了对事件做出响应,分配一个事件处理程序 —— 一个在事件发生时运行的JavaScript函数。

on<event>

事件处理程序fn可以设置在HTML标签中名为on<event>的特性(attribute)中,如下

<input type="button" value="Click me" onclick="fn()">

<script>
  function fn(){
    alert('点击了button')
  }
</script>

也可以使用DOM属性(property)on<event>来分配处理程序,例如:

<input id="elem" type="button" value="Click me - dom">

<script>
  elem.onclick = fn // 注意是赋值,而不是调用函数
  function fn(){
  	alert('点击了button')
  }
</script>

以上两种方式本质上相同,如果处理程序通过HTML特性(attribute)分配,浏览器使用特性的内容创建一个新的函数,并将这个函数写入DOM属性

addEventListener

上述分配处理程序的方式的根本问题是 —— 我们不能为一个事件分配多个处理程序。因此可以用以下方式

element.addEventListener(event, handler[, options]);

event

事件名,例如:"click"。

handler

处理程序。

options

具有以下属性的附加可选对象:

once:如果为 true,那么会在被触发后自动删除监听器。

capture:事件处理的阶段, 冒泡和捕获 。由于历史原因,options 也可以是 false/true,它与 {capture: false/true} 相同。

passive:如果为 true,那么处理程序将不会调用 preventDefault()

总结

3种分配事件监听函数的方式

  1. HTML (attribute) onclick="..."
  2. DOM (property) element.onclick = fn
  3. 方法 (method) element.addEventListener(event, handler[, phase])

无论如何分类处理程序 —— 它都会将获得一个事件对象作为第一个参数。该对象包含有关所发生事件的详细信息。

事件对象

当事件发生时,浏览器会创建一个event对象,将详细信息放入其中,并将其作为参数传递给处理程序.

event 对象的一些属性:

event.type 事件类型,如 "click"。

event.currentTarget 处理事件的元素(程序员监听的元素),与 this 相同

event.target 实际操作的元素

事件冒泡和事件捕获

W3C标准规定浏览器事件传播有3个阶段

  1. 捕获阶段:事件从Window向下走进元素
  2. 目标阶段:事件到达目标元素
  3. 冒泡阶段:事件从元素上开始上升

下图表示了在表格中点击<td>事件的流动,途中调用处理程序

先捕获:按照爷爷=>爸爸=>儿子的顺序查找是否有事件监听函数,如果有就执行,直到目标元素

再冒泡(这一过程可停止):事件从目标元素开始向上冒泡,直到<html>,再到document对象,有些事件甚至会到达window,会调用路径上所有处理程序

冒泡

冒泡的原理是,当一个事件发生在一个元素上,它会首先运行该元素上的处理程序,然后运行其父元素上的处理程序,一直向上到其它祖先上的处理程序

停止冒泡的方法是 event.stopPropagation()

注意区分event.targetevent.currentTargetcurrentTarget是被监听的元素,target是实际上被操作的元素 示例代码

捕获

为了在捕获阶段捕获事件,需要将事件监听函数的capture选项(或第三个选项)设置为true

elem.addEventListener(..., {capture: true})
// 或者,用 {capture: true} 的别名 "true"
elem.addEventListener(..., true)

代码示例

点击最里面的圆圈,先捕获再冒泡,冒泡到第三个div之后会被阻止

事件委托

如果需要给很多相似的元素添加相同的处理,可以借助捕获和冒泡实现事件委托模式,具体实现方式:

  • 在祖先元素上分配一个监听函数
  • 在监听函数中检查事件源元素 event.target
  • 如果事件发生在我们感兴趣的元素内,就处理该事件

优点包括:

  • 不需要添加许多处理程序,节约内存
  • 能够监听动态元素 代码示例
    <div id="div1"></div>
    
    <script>
    setTimeout(()=>{
      const button = document.createElement('button')
      button.textContent = 'click 1'
      div1.appendChild(button)
    },1000)
    
    div1.addEventListener('click',(e)=>{
      if(e.target.tagName.toLowerCase() === 'button'){
      console.log('button 被 click')}
    })
    </script>
    

封装事件委托

要求

写出这样一个函数 on('click', '#testDiv', 'li', fn)

当用户点击 #testDiv 里的 li 元素时,调用 fn 函数

要求用到事件委托

答案1

判断target是否匹配li

代码链接(需无痕模式访问)

上面这个答案其实有bug(button包括span)

代码链接(需无痕模式访问)

答案2

递归判断target / target的爸爸 / target的爷爷

代码链接(需无痕模式访问)

浏览器默认行为

许多事件会自动触发浏览器执行某些行为。

例如:

  • 点击一个链接 —— 触发导航(navigation)到该 URL。
  • 点击表单的提交按钮 —— 触发提交到服务器的行为。
  • 在文本上按下鼠标按钮并移动 —— 选中文本。

如果我们使用 JavaScript 处理一个事件,那么我们通常不希望发生相应的浏览器行为。而是想要实现其他行为进行替代。

阻止浏览器默认行为

  • 主流的方式是使用 event 对象。有一个 event.preventDefault() 方法。

  • 如果处理程序是使用 on<event>分配的,那返回 false 也同样有效。

    <a href="/" onclick="return false">Click here</a>
    or
    <a href="/" onclick="event.preventDefault()">here</a>
    

自定义事件

浏览器自带事件 developer.mozilla.org/zh-CN/docs/…

开发者可以自定义事件

  1. 事件构造器 developer.mozilla.org/zh-CN/docs/…
new CustomEvent(type)
new CustomEvent(type, options)
  1. dispatchEvent派发事件
dispatchEvent(event)

示例如下:

  <div id='container'>
    <button id="btn">点击触发自定义事件</button>
  </div>
  <script>
      
    btn.addEventListener('click',()=>{
      // 创建自定义事件
      const event = new CustomEvent('amber',{
        detail:{name:'amber',age:18},
        bubbles:true // chrome默认不冒泡
      })
      btn.dispatchEvent(event) // 触发自定义事件
    })

    container.addEventListener('amber',(e)=>{
      console.log(e.detail)
    })

  </script>

Javascript和DOM事件的关系

支持,也不支持。

DOM 事件不属于 JS 的功能,属于浏览器提供的 DOM 的功能

JS 只是调用了 DOM 提供的 addEventListener 而已