Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

事件机制 #20

Open
K-Kevin opened this issue Jul 30, 2020 · 0 comments
Open

事件机制 #20

K-Kevin opened this issue Jul 30, 2020 · 0 comments

Comments

@K-Kevin
Copy link
Owner

K-Kevin commented Jul 30, 2020

一、什么是事件

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单击事件监听器设置在父元素上,这样事件就会从列表项冒泡到其父元素上。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant