Skip to content

事件机制 #20

@K-Kevin

Description

@K-Kevin

一、什么是事件

MDN:

事件是您在编程时系统内发生的动作或者发生的事情,系统响应事件后,如果需要,您可以某种方式对事件做出回应。例如:如果用户在网页上单击一个按钮,您可能想通过显示一个信息框来响应这个动作。在这篇文章中,我们将讨论一些关于事件的重要概念,并且观察它们在浏览器上如何运行。这篇文章不会面面俱到,仅聚焦于您现阶段需要掌握的知识。

二、一些事件 Event reference

在Web中, 事件在浏览器窗口中被触发并且通常被绑定到窗口内部的特定部分 — 可能是一个元素、一系列元素、被加载到这个窗口的 HTML 代码或者是整个浏览器窗口。举几个可能发生的不同事件:

  • 用户在某个元素上点击鼠标或悬停光标。
  • 用户在键盘中按下某个按键。
  • 用户调整浏览器的大小或者关闭浏览器窗口。
  • 一个网页停止加载。
  • 提交表单。
  • 播放、暂停、关闭视频。
  • 发生错误。

三、使用网页事件的方式

1.传统事件模型-DOM0 traditional model

兼容性很好

Netscape already started in Version 3, Explorer followed in Version 4.

So both browsers, and in fact all modern browsers, accept this code

// ondblclick onmouseover onmouseout 
element.onclick = doSomething;

移除事件:

element.onclick = null;

不通过事件触发

element.onclick()

Microsoft has added the fireEvent() method to Explorer 5.5 and higher on Windows for the same purpose. The syntax is element.fireEvent('onclick')

问题

传统事件模型只能接受一个函数,如果注册多个事件,就会被后面的所覆盖

element.onclick = startDragDrop;
// 覆盖上面的 startDragDrop
element.onclick = spyOnUser;

2.早期的事件回调(行内事件处理器-请勿使用)

<A HREF="somewhere.html" onClick="alert('I\'ve been clicked!')">
<A HREF="somewhere.html" onClick="doSomething()">

3.addEventListener() 和 removeEventListener()-DOM2

W3C 和微软都各自开发了他们自己的一套事件模型用于替换 Netscape 的传统事件模型。

1).W3C(Document Object Model (DOM) Level 2 Events

W3C 的 DOM2 事件模型提供了在同一元素上注册多个事件的方式。

var btn = document.querySelector('button');

function bgChange() {
  var rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')';
  document.body.style.backgroundColor = rndCol;
}   

btn.addEventListener('click', bgChange);
// addEventListener() 三个参数:事件类型、执行函数(或匿名函数)、布尔值
element.addEventListener('click',doSomething,false)

注册多个:

// 先后执行顺序无法确定
element.addEventListener('click',startDragDrop,false)
element.addEventListener('click',spyOnUser,false)

移除事件:

element.removeEventListener('click',spyOnUser,false)

匿名函数(Anonymous functions)

element.addEventListener('click',function () {
	this.style.backgroundColor = '#cc0000'
},false)

this

在 Javascript 中,this 始终指向函数的 “owner” ,在事件回调中可以方便的使用 this 去操作 HTML element。

element.addEventListener('click',doSomething,false);
another_element.addEventListener('click',doSomething,false);

function doSomething() {
	this.style.backgroundColor = '#cc0000';
}

是否注册了事件?

W3C 的事件模型有个问题就是不能知道元素是否已经注册了哪些事件。

在传统事件模型中,我们可以这么做:

alert(element.onclick)

被注册的函数会被显示,undefined 表示没有注册。在最新的 DOM Level 3 Events 中添加了eventListenerList 去存储注册事件,不过 api 太新,兼容性不够。

庆幸的是,removeEventListener() 不会报错,哪怕没有注册,所以可以放心使用。

2).Microsoft(不推荐)
// 注册
element.attachEvent('onclick',doSomething)

// 注册多个
element.attachEvent('onclick',startDragDrop)
element.attachEvent('onclick',spyOnUser)

// 移除
element.detachEvent('onclick',spyOnUser)
DOM3级事件

DOM3级事件在DOM2级事件的基础上添加了更多的事件类型,全部类型如下:

  1. UI事件,当用户与页面上的元素交互时触发,如:load、scroll
  2. 焦点事件,当元素获得或失去焦点时触发,如:blur、focus
  3. 鼠标事件,当用户通过鼠标在页面执行操作时触发如:dbclick、mouseup
  4. 滚轮事件,当使用鼠标滚轮或类似设备时触发,如:mousewheel
  5. 文本事件,当在文档中输入文本时触发,如:textInput
  6. 键盘事件,当用户通过键盘在页面上执行操作时触发,如:keydown、keypress
  7. 合成事件,当为IME(输入法编辑器)输入字符时触发,如:compositionstart
  8. 变动事件,当底层DOM结构发生变化时触发,如:DOMsubtreeModified

同时DOM3级事件也允许使用者自定义一些事件

四、事件顺序(Event order

假设以下场景:element1、element2 都有一个 onClick 事件,如果点击 element2,两个点击事件都会触发,但谁先触发就是下面要说的事件顺序

-----------------------------------
| element1                        |
|   -------------------------     |
|   |element2               |     |
|   -------------------------     |
|                                 |
-----------------------------------

1.两个模型

  • Netscape 的定义 element1 先触发. 所谓事件捕获(event capturing).
  • Microsoft 的定义 element2 优先. 叫做事件冒泡(event bubbling).

Explorer only supports event bubbling. Mozilla, Opera 7 and Konqueror support both. Older Opera's and iCab support neither.

1).事件捕获
               | |
---------------| |-----------------
| element1     | |                |
|   -----------| |-----------     |
|   |element2  \ /          |     |
|   -------------------------     |
|        Event CAPTURING          |
-----------------------------------

element1 的事件先触发,element2 事件最后触发。

2).事件冒泡
               / \
---------------| |-----------------
| element1     | |                |
|   -----------| |-----------     |
|   |element2  | |          |     |
|   -------------------------     |
|        Event BUBBLING           |
-----------------------------------

element2 的事件先触发,element1 事件最后触发。

2.W3C 模型

W3C 整合后就是我们目前在使用的事件捕获、冒泡模型了。即任何事件都是先捕获直到抵达 target,然后再冒泡。

                 | |  / \
-----------------| |--| |-----------------
| element1       | |  | |                |
|   -------------| |--| |-----------     |
|   |element2    \ /  | |          |     |
|   --------------------------------     |
|        W3C event model                 |
------------------------------------------

开发者可以自由选择是在捕获阶段还是冒泡阶段去注册事件。即通过 addEventListener() 函数的第三参数。如果是 true,则设定为捕获阶段,如果为 false,则设定为冒泡。

场景一:

element1.addEventListener('click',doSomething2,true)
element2.addEventListener('click',doSomething,false)

如果点击 element2:

  1. 点击事件从捕获阶段开始,查找任何 element2 父元素的捕获点击事件。
  2. 发现在 element1 上的doSomething2() 事件,并执行。
  3. 找到目标本身,捕获阶段没有其他事件了。事件循环开始冒泡阶段,在目标本身找到并执行doSomething()
  4. 事件向上冒泡查找父元素是否有冒泡阶段的事件,直到结束。

场景二:

element1.addEventListener('click',doSomething2,false)
element2.addEventListener('click',doSomething,false)

如果点击 element2:

  1. 点击事件从捕获阶段开始,可以捕获到任何 element2 父元素的捕获点击事件,没有发现。
  2. 事件向下查找到目标本身。开始进入冒泡阶段,并执行注册在 element2 上的冒泡事件doSomething()
  3. 事件向上查找,检查目标的父元素是否有冒泡事件。
  4. 找到 element1,所以 doSomething2 执行
关闭

有时候希望停止冒泡或者捕获。

Microsoft

window.event.cancelBubble = true

W3C model

e.stopPropagation()

stopPropagation:阻止捕获和冒泡阶段中当前事件的进一步传播

stopImmediatePropagation:如果有多个相同类型事件的事件监听函数绑定到同一个元素,当该类型的事件触发时,它们会按照被添加的顺序执行。如果其中某个监听函数执行了 event.stopImmediatePropagation() 方法,则当前元素剩下的监听函数将不会被执行。(译者注:注意与 event.stopPropagation() 之间的区别)

preventDefault:Event 接口的 **preventDefault()**方法,告诉user agent:如果此事件没有被显式处理,它默认的动作也不应该照常执行。此事件还是继续传播,除非碰到事件侦听器调用stopPropagation()stopImmediatePropagation(),才停止传播。

三个阶段

一旦查找顺序确定,事件就会经历三个阶段:捕获阶段、目标阶段、冒泡阶段。如果一个阶段不支持则会跳过,或者一个阶段的 propagation 被停止,也会跳过。举个例子,如果bubbles 属性呗设为 false,那么冒泡阶段就会被跳过。如果 stopPropagation() 在之前被调用,那么所有阶段都会跳过。

  • 捕获阶段:事件对象在目标对象的父元素中查找,从 Window 到目标的父元素
  • 目标阶段:事件对象到达目标元素,也叫在目标阶段。如果事件类型表明事件无需冒泡,则这个阶段结束,就会停止。
  • 冒泡阶段:事件对象从目标元素的父元素开始直到 Window。

image

EventTarget.addEventListener()

target.addEventListener(type, listener, options);
target.addEventListener(type, listener, useCapture);
target.addEventListener(type, listener, useCapture, wantsUntrusted  );  // Gecko/Mozilla only
  • type:表示监听事件类型的字符串。

  • listener:当所监听的事件类型触发时,会接收到一个事件通知(实现了 Event 接口的对象)对象。listener 必须是一个实现了 EventListener 接口的对象,或者是一个函数。有关回调本身的详细信息,请参阅The event listener callback

  • options 可选

    一个指定有关 listener 属性的可选参数对象

    可用的选项如下:

    capture: Boolean,表示 listener 会在该类型的事件捕获阶段传播到该 EventTarget 时触发。

    once: Boolean,表示 listener 在添加之后最多只调用一次。如果是 true, listener 会在其被调用之后自动移除。

    passive: Boolean,设置为true时,表示 listener 永远不会调用 preventDefault()。如果 listener 仍然调用了这个函数,客户端将会忽略它并抛出一个控制台警告。查看 Improving scrolling performance with passive listeners 了解更多.

    mozSystemGroup: 只能在 XBL 或者是 Firefox' chrome 使用,这是个 Boolean,表示 listener 被添加到 system group。

  • useCapture 可选

    Boolean,在DOM树中,注册了listener的元素, 是否要先于它下面的EventTarget,调用该listener。 当useCapture(设为true) 时,沿着DOM树向上冒泡的事件,不会触发listener。当一个元素嵌套了另一个元素,并且两个元素都对同一事件注册了一个处理函数时,所发生的事件冒泡和事件捕获是两种不同的事件传播方式。事件传播模式决定了元素以哪个顺序接收事件。进一步的解释可以查看 事件流JavaScript Event order 文档。 如果没有指定, useCapture 默认为 false 。

注意: 对于事件目标上的事件监听器来说,事件会处于“目标阶段”,而不是冒泡阶段或者捕获阶段。在目标阶段的事件会触发该元素(即事件目标)上的所有监听器,而不在乎这个监听器到底在注册时useCapture 参数值是true还是false。

注意: useCapture 仅仅在现代浏览器最近的几个版本中是可选的。 例如 Firefox 6以前的版本都不是可选的。为了能够提供更广泛的支持,你应该提供这个参数。

注意:如果同一个监听事件分别为“事件捕获”和“事件冒泡”注册了一次,这两次事件需要分别移除。两者不会互相干扰。移除捕获监听器不会影响非捕获版本的相同监听器,反之亦然。

  • wantsUntrusted

    如果为 true , 则事件处理程序会接收网页自定义的事件。此参数只适用于 Gecko(chrome的默认值为true,其他常规网页的默认值为false),主要用于附加组件的代码和浏览器本身。

事件委托

冒泡还允许我们利用事件委托——这个概念依赖于这样一个事实,如果你想要在大量子元素中单击任何一个都可以运行一段代码,您可以将事件监听器设置在其父节点上,并让子节点上发生的事件冒泡到父节点上,而不是每个子节点单独设置事件监听器。

一个很好的例子是一系列列表项,如果你想让每个列表项被点击时弹出一条信息,您可以将click单击事件监听器设置在父元素上,这样事件就会从列表项冒泡到其父元素上。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions