Skip to content

Latest commit

 

History

History
374 lines (219 loc) · 12 KB

第13章-事件.md

File metadata and controls

374 lines (219 loc) · 12 KB

本章内容

  1. 理解事件流
  2. 使用事件处理程序
  3. 不同的事件类型

JavaScript与HTML之间的交互通过事件实现,事件就是文档或者浏览器窗口中发生的一些特定的交互瞬间。可以使用侦听器(或处理程序)来预订事件,以便事件发生时执行相应的代码,这种在传统软件工程中称为观察员模式,支持页面的行为与页面的外观之间的松散耦合

13.1 事件流

事件流描述的从页面接收事件的顺序,IE的事件是冒泡流,Netscape的事件流是事件捕获流。

13.1.1 事件冒泡

IE的事件流叫做事件冒泡,也就是事件由最具体的元素(文档中嵌套最深的那个节点)接收,然后逐级向上传播到较为不具体的节点。

举例

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <div></div>
</body>
</html>

冒泡事件流

所有的现代浏览器都支持事件冒泡,但是具体实现上还有一些差别,IE5.5更在版本中的事件冒泡会跳过html元素。IE9,chrome和safari则将事件一直冒泡到window对象.

13.1.2 事件捕获

Netscape Communicator团队提出的另一种事件流叫做事件捕获,事件捕获的思想是不太具体的节点应该更早接收到事件,事件捕获的用于在事件到达预定目标之前捕获他。

事件捕获

虽然事件捕获是Netscape Communicator唯一支持的事件流模型,但是IE9、safari等浏览器都支持这种事件模型,规范要求事件应该从document对象开始传播,但是这些浏览器都是从window开始传播。

13.1.3 DOM事件流

"DOM2级事件" 规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段和冒泡阶段。首先发生的是事件捕获,为截获事件提供了机会,然后是实际的目标接收到事件,最后一个阶段是冒泡阶段,可以在这个阶段对事件做出响应。

13.2 事件处理程序

事件就是用户或浏览器自身执行的某种动作,诸如click、load和mouseover,都是事件的名称,响应某个事件的函数叫做事件处理程序(或事件监听器)

为事件指定事件处理程序的方式有很多种,如下

  • HTML 事件处理程序

    元素所支持的每种事件,都可以使用一个与事件同名HTML特性来指定,该特性的值是能够执行的JavaScript代码,例如

    <div class="box" onclick="console.log(1)"></div>

    当然也可以调用在页面中其他地方定义的脚本,事件处理程序中的代码在执行时,1. 有权访问全局作用域中的任何代码。

    <script>
      let showMsg = () => {
        console.log('hello world')
      }
    </script>
    
    <div class="box" onclick="showMsg()"></div>
    1. 可以通过event变量,直接访问事件对象,不用自己定义,也不用从函数的参数列表读取

    2. 在这个函数中this等于事件的目标元素

    <script>
      let showMsg = function (val, e) {
        console.log(val)
        console.log(e)
      }
    </script>
    <div class="box" onclick="showMsg(this.innerHTML, event)">hello world</div>

    HTML事件处理程序缺点

    1. 存在"时差问题",当用户触发相应的事件时,如果事件处理程序尚不具备执行条件,就会引发错误。(比如上面的script标签中放到页尾)

    2. 扩展事件处理程序中的作用域在不同的浏览器中会导致不同的结果。

    3. HTML与JavaScript代码紧密耦合,如果要更换事件处理程序,就要改动两个地方,这也是大家都转向JavaScript指定事件处理程序的原因所在。

13.2.2 DOM0级事件处理程序

通过JavaScript指定事件处理程序的传统方式,就是将一个函数赋值给一个事件处理程序属性。这种方式有以下几个特点。

  1. 简单
  2. 跨浏览器

每个元素(包括window和document)都有自己的事件处理程序,这些属性通常全部小写,例如onclick。

<button class="btn">按钮</button>
let btn = document.querySelector('.btn')

  btn.onclick = function () {
    console.log(this.className)
  }

需要注意的是,在这些代码运行之前不会指定事件处理程序,因此这些代码在页面中位于按钮后面,就有可能在一段时间内怎么单击都没有反应。

使用DOM0级方法指定的事件处理程序被认为是元素的方法,因此这时候的事件处理程序是在元素的作用域中运行,换句话说,程序中的this引用当前元素。

1. 以这种方式添加的事件处理程序会在事件流的冒泡阶段被处理

2. 也可以删除通过DOM0级方法指定的事件程序,如下

btn.onclick = null

使用DOM0级方法指定的事件处理程序

13.2.3 DOM2级事件处理程序

"DOM2级事件"定义了两个方法,分别用于指定和删除事件处理程序:addEventListenerremoveEventListener,所有的DOM节点都包括这两个方法,并且接受3个参数,要处理的事件名,作为事件处理程序的函数和一个布尔值。如果这个布尔值为true,表示在捕获阶段调用事件处理程序,如果是false,表示在冒泡阶段调用事件处理程序

let btn = document.querySelector('.btn')

  btn.addEventListener('click', function () {
    console.log(this.className)
  }, false)

与DOM0级方法一样,这里添加的事件处理程序也是在其依附的元素的作用域中运行,但其可以添加多个事件处理函数。并且是按照添加的顺序触发

let btn = document.querySelector('.btn')

  btn.addEventListener('click', function () {
    console.log('hello world 1')
  }, false)

  btn.addEventListener('click', function () {
    console.log('hello world 2')
  }, false)

// 打印出 hello world 1, hello world 2

移除事件处理程序

通过addEventListener添加的事件处理程序只能使用removeEventListener来移除,移除时传入的参数和添加处理程序时使用的参数相同,这也意味着通过其添加的匿名函数将无法移除。

const handleClick = () => {
  console.log('hello world 3')
}

btn.addEventListener('click', handleClick, false)
btn.removeEventListener('click', handleClick, false) // 移除的时候需要和添加的时候参数保持一致,匿名函数无法移除

如果不是特别需要,不建议在捕获阶段注册事件处理程序

13.2.4 IE事件处理程序

IE实现了与DOM中类似的两个方法,attachEvent, detachEvent。接受相同的参数,事件名称和事件处理程序函数。

attachEvent添加与detachEvent移除事件

let $btn = document.querySelector('button')
const handle = function () {
  console.log('1)
}
const handle2 = function () {
  console.log('2')
}
// 添加事件
$btn.attachEvent('onclick', handle)
$btn.attachEvent('onclick', handle2)
// 移除事件
$btn.detachEvent('onclick', handle)

通过attachEvent添加的事件处理程序以添加时的相反顺序触发,移除事件的时候,事件名称和事件处理函数必须与添加的时候相同,匿名函数无法移除

13.2.5 跨浏览器的事件处理程序

恰当的使用能力检测,可以编写跨浏览器的事件处理。要保证处理事件在大多数浏览器下一致运行,只需要关注冒泡阶段。

我们要创建的一个方法是addHandler(),它的职责是分别使用DOM0级方法,DOM2级方法或IE方法来添加事件。

addHandler

const addHandler = (element, type, handler) => {
  if (element.addEventListener) {
    element.addEventListener(type, handler, false)
  } else if (element.attachEvent) {
    element.attachEvent(`on${type}`, handler)
  } else {
    element[`on${type}`] = handler
  }
}

removeHandler

const removeHandler = (element, type, handler) => {
  if (element.removeEventListener) {
    element.removeEventListener(type, handler, false)
  } else if (element.detachEvent) {
    element.detachEvent(`on${type}`, handler)
  } else {
    element[`on${type}`] = null
  }
}

实例

const $btn = document.querySelector('.btn')
const logClassName = function () {
  console.log(this.className)
}

addHandler($btn, 'click', logClassName)
addHandler($btn, 'click', function () {
  console.log('hello world')
})

removeHandler($btn, 'click', logClassName)

13.3 事件对象

在触发DOM上的某个事件时,会产生一个事件对象event,这个对象中包含着所有与事件有关的信息。 例如包含包括事件的元素,事件的类型以及其他特定事件相关的信息。例如鼠标操作导致得事件对象中,会包含鼠标的位置信息,而键盘操作的事件对象中,会包含与按下的键有关的信息,所有的浏览器都支持event对象,但支持的方式不同

13.3.1 DOM中的事件对象

兼容DOM的浏览器会将一个event对象传入到事件处理程序中,无论指定事件处理程序使用什么方式(DOM0和DOM2级),都会传入event对象,如下例子

let $btn = document.querySelector('.box')

const logEventType = (event) => {
  console.log(event.type)
}

$btn.onclick = logEventType
$btn.addEventListener('click', logEventType, false)

另外以下面这种方式提供event对象,可以让HTML特性事件处理程序函数执行相同的操作。

<div class="box" onclick="console.log(this.innerHTML, event)">hello world</div>

event对象包含创建它的特定的事件相关的属性和方法,触发的事件类型不一样,可用的属性和方法也不一样。不过所有的事件都会有下表列出的成员

属性/方法 | 类型 | 读/写 | 说明

  • | :-: | -: | -: bubbles | Boolean| 只读 | 表明事件是否冒泡

cancelable | Boolean| 只读 | 表明是否可以取消事件的默认行为

currentTarget | Element| 只读 | 其事件处理程序当前正在处理事件的那个元素

defaultPrevented | Boolean| 只读 | 为true表示已经调用了preventDefault()

detail | Integer| 只读 | 与事件相关的细节信息

eventPhase | Integer| 只读 | 调用事件处理程序的截断:1表示捕获阶段,2表示“处于目标”,3表示冒泡阶段

preventDefault() | Function| 只读 | 取消事件的默认行为,如果是cancelable是true,则可以使用这个方法。

stopImmediatePropagation() | Function| 只读 | 取消事件的进一步捕获或者冒泡,同事阻止任何事件处理程序被调用。

stopPropagation() | Function| 只读 | 取消事件的进一步捕获或者冒泡,如果bubbles为true则可以使用这个方法

target | Element| 只读 | 事件的目标

trusted | Boolean| 只读 | 为true表示事件是由浏览器生成的,为false则表示事件是由开发人员通过JavaScript创建的

type | String| 只读 | 被触发的事件类型

view | AbstractView| 只读 | 与事件关联的抽象视图,等同于发生事件的window对象

13.3.2 IE中的事件对象

与访问DOM中的event对象不同,要访问IE中的event对象有几种不同的方式,取决于指定事件处理程序的方法。在使用DOM0级方法添加事件处理程序时,event对象作为window对象的一个属性存在。

let $btn = document.querySelector('button')

$btn.onclick = () => {
  let event = window.event
  console.log(event.type)
}

13.3.3 跨浏览器的事件对象

13.4 事件类型

web浏览器中可能发生的事件有很多类型。如前所述,不同的事件类型具有不同的信 ,而DOM3级事件,规定了以下几类事件

13.4.1 UI事件

13.4.2 焦点事件

13.4.3 鼠标与滚轮事件