第17章 事件

事件流

事件流描述了页面接收事件的顺序

事件冒泡

IE事件流被称为事件冒泡,事件冒泡中事件被定义为从最具体的元素(文档树中最深的节点)开始触发,然后向上传播至没有那么具体的元素(文档)。

事件捕获

事件捕获从最不具体的节点应该最先收到事件,而最具体的节点应该最后收到事件。

因为事件捕获实际上是为了在事件到达最终目标前拦截事件。

DOM事件流

事件流分为3个阶段:事件捕获、到达目标和事件冒泡,事件捕获最先发生,为提前拦截事件提供了可能性,然后,实际的目标元素接收到事件,最后一个阶段是冒泡,最迟要在这个阶段响应事件。

<!DOCTYPE html> 
<html> 
<head> 
 <title>Event Bubbling Example</title> 
</head> 
<body> 
 <div id="myDiv">Click Me</div> 
</body> 
</html>

点击页面中的div位置后,事件流的阶段

在 DOM 事件流中,实际的目标(div元素)在捕获阶段不会接收到事件。这是因为捕获阶段从 document 到html再到body就结束了。下一阶段,即会在div元素上触发事件的“到达目标” 阶段,通常在事件处理时被认为是冒泡阶段的一部分(稍后讨论)。然后,冒泡阶段开始,事件反向传 播至文档。

事件处理程序(事件监听器)

为响应事件而调用的函数

html事件处理程序

相当于在行内添加事件

<input type='button' value='Click' onclick="console.log('click')" />
<!-- 这里能够调用showMessage方法是因为作为事件处理程序执行的代码可以访问全局作用域中的一切 -->
<input type="button" value="Click Me" onclick="showMessage()"/>
<script> 
 function showMessage() &#123; 
     console.log("Hello world!"); 
 &#125; 
</script> 

DOM0事件处理程序

let btn = document.getElementById("myBtn");
// this指向元素本身
btn.onclick = function() &#123;
    console.log(this.id);  // myBtn
    console.log(btn === this);  // true
&#125;

DOM2事件处理程序

(通过这种方式可以为同一个元素添加多个事件)

let btn = document.getElementById("myBtn");
btn.addEventListener('click',function() &#123;
    console.log(this.id); // myBtn
    console.log(btn === this); // true
&#125;, false); // true 为捕获阶段触发,false为非捕获阶段触发

IE事件处理程序

var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function() &#123;
    console.log("Clicked");
&#125;);

事件对象

在DOM中发生事件时,所有相关信息都会被收集并且存储在一个名为event的对象中。这个对象包含了一些基本信息,比如导致事件的元素、发生的事件类型,以及可能与特定事件相关的任何其它数据。例如:鼠标操作导致的事件会生成鼠标位置信息,而键盘操作导致的事件会生成与被按下的键有关的信息。

DOM事件对象

event对象是传给事件处理程序的唯一参数

在事件处理程序内部,this指向currentTarget的值,target指向事件的实际目标。

let btn = document.getElementById("myBtn"); 
// 因为事件处理程序直接添加在了意图的目标,所以this、currentTarget、target的值是一样的
btn.onclick = function(event) &#123; 
 console.log(event.currentTarget === this); // true 
 console.log(event.target === this); // true 
&#125;; 
// 这个事件处理程序是添加在按钮的父节点上body,所以this和currentTarget都指向body,但是target指向myBtn
document.body.onclick = function(event) &#123;
    console.log(event.currentTarget === document.body); // true 
    console.log(this === document.body); // true 
    console.log(event.target === document.getElementById("myBtn")); // true 
&#125;

event.type属性表示事件类型,在一个处理程序处理多个事件时很有用

let btn = document.getElementById("myBtn"); 
let handler = function(event) &#123; 
 switch(event.type) &#123; 
 case "click": 
     console.log("Clicked"); 
     break; 
 case "mouseover":
         event.target.style.backgroundColor = "red"; 
         break; 
 case "mouseout": 
     event.target.style.backgroundColor = ""; 
     break; 
 &#125; 
&#125;; 
btn.onclick = handler; 
btn.onmouseover = handler; 
btn.onmouseout = handler; 

event.preventDefault()方法用于阻止特定事件的默认动作(比如a标签的href)。

event.stopPropagation()用于取消后续事件捕获或冒泡。

event.eventPhase可以确定事件流的状态:1:捕获阶段,2目标调用,3冒泡阶段。

event对象只在事件处理程序执行期间存在,一旦执行完毕,就会被销毁

事件类型

DOM3 Events定义了如下事件类型

用户界面事件(UIEvent)

涉及与BOM交互的通用浏览器事件

load

  • 在window上当页面加载完成后触发
  • 在frameset上当所有窗格frame都加载完成后触发
  • 在img元素上当图片加载完成后触发
  • 在object元素上当相应对象加载完成后触发
// 通过两种方式指定load事件处理程序
// 第一种:使用js方式
window.addEventListener("load", (event) => &#123; 
 console.log("Loaded!"); 
&#125;);
// 第二种:向body元素添加onload属性 一般来说,都可以通过给body元素上对应的属性赋值来指定,但是实际开发中尽量使用js方式
<body onload="console.log('Loaded!')"></body>

abort

在object元素上当相应对象加载完成前被用户提前终止下载时触发。

error

  • 在window上当JavaScript报错时触发
  • 在img元素上当无法加载指定图片时触发
  • 在object元素上当无法加载相应对象时触发
  • 在窗套上当一个或多个窗格无法完成加载时触发。

select

在文本框(input或textarea)上当用户选择了一个或多个字符时触发。

resize

在window或窗格上当窗口或窗格被缩放时触发。

scroll

unload

文档卸载完成后触发,unload事件一般是在从一个页面导航到另一个页面时触发,最常用于清理引用,以避免内存泄漏。

resize

当浏览器窗口被缩放到新高度或宽度时,会触发resize事件。、

焦点事件(FocusEvent)

在元素获得和失去焦点时触发

blur:当元素失去焦点时触发。这个事件不冒泡,所有浏览器都支持

focus:当元素获得焦点时触发。这个事件不冒泡,所有浏览器都支持 focusin(冒泡版)

focusout:当元素失去焦点时触发,这个事件是blur的通用版

鼠标事件(MouseEvent)

使用鼠标在页面上执行某些操作时触发

click:在用户单击鼠标主键(通常是左键)或按键盘回车键时触发。这主要是基于无障碍的考虑,让键盘和鼠标都可以触发onclick事件处理程序

dblclick:在用户双击鼠标主键(通常是左键)时触发。这个事件不是在DOM2 Events中定义的,但得到了很好的支持,DOM3Events将其进行了标准化

mousedown:在用户按下任意鼠标键时触发。这个事件不能通过键盘触发

mouseenter:在用户把鼠标光标从元素外部移到元素内部时触发。这个事件不冒泡,也不会在光标经过后代元素时触发。mouseenter事件不是在DOM2 Events中定义的,而是DOM3Events中新增的事件

mouseleave:在用户把鼠标光标从元素内部移到元素外部时触发。这个事件不冒泡,也不会在光标经过后代元素时触发。mouseleave事件不是在DOM2 Events中定义的,而是DOM3Events中新增的事件

窗口坐标

event对象的clientX和clientY属性中

页面坐标

event对象的pageX和pageY,在页面没有滚动的时候,pageX和pageY与clientX和clientY的值相同

屏幕坐标

event对象的screenX和screenY属性获取鼠标光标在屏幕上的坐标

键盘事件(KeyboardEvent)

使用键盘在页面上执行某些操作时触发

keydown:在按键被按下时触发

keypress,用户按下键盘上某个键并产生字符时触发,而且持续按住会重复触发。Esc键也会触发这个事件。DOM3 Events废弃了keypress事件,而推荐textInput事件

keyup,用户释放键盘上某个键时触发。 输入事件只有一个,即textInput。textInput会在文本被插入到文本框之前触发。

如果一个字符键被按住不放,keydown和keypress就会重复触发,直到这个键被释放

滚轮事件(WheelEvent)

使用鼠标滚轮(或类似设备)时触发

输入事件(InputEvent)

向文档中输入文本时触发

合成事件(CompositionEvent)

在使用某种IME(InputMethod Editor,输入法编辑器)输入字符时触发

IME通常需要同时按下多个键才能输入一个字符。合成事件用于检测和控制这种输入

内存与性能

在js中,页面中事件处理程序的数量与页面整体性能直接相关

  • 每个函数都是对象,都占用内存空间,对象越多,性能越差。

  • 为指定事件处理程序所需访问DOM的次数会先期造成整个页面交互的延迟。

事件委托

事件委托利用事件冒泡,可以只使用一个事件处理程序来管理一种类型的事件。

使用事件委托,只要给所有元素共同的祖先节点添加一个事件处理程序,就可以解决问题。

删除事件处理程序

事件处理程序越多,页面性能越差,应该即时删除不用的事件处理程序。

  1. 删除带有事件处理程序的元素,removeChild()或replaceChild()删除节点。innerHTML需要手动删除程序。
  2. 在onunload事件处理程序中趁页面尚未卸载先删除所有事件处理程序。

模拟事件

DOM事件模拟

// 创建一个event对象
document.createEvent(event:String)

自定义DOM事件