第十三章 事件

225 阅读19分钟

事件流

事件冒泡

事件触发从子节点向父节点接收,逐级向上

事件捕获

事件触发从父节点向子节点接收,逐级向下

DOM事件流

"DOM2级事件"规定事件流包括三个阶段:事件捕获、处于目标阶段、事件冒泡

事件处理程序

HTML事件处理程序

HTML事件函数包括以下常用对象

  • event:事件对象,例如event.type是事件的类型
  • this:事件的目标元素

DOM0级事件处理程序

通过 JavaScript 指定事件处理程序的传统方式,将一个函数赋值给一个事件处理程序属性。如 btn.onclick = function () {console.log(this.id)}

删除DOM0级事件处理程序: btn.onclick = null

DOM2级事件处理程序

"DOM2级事件"定义两个方法,接受3个参数:要处理的事件名、事件处理函数、表示事件流的布尔值(true表示捕获阶段调用事件处理程序,false表示冒泡阶段调用事件处理程序)

addEventListener():指定事件处理程序

removeEventListener():删除事件处理程序

使用DOM2级方法添加事件可以添加多个相同的事件处理程序(如多个click事件),执行顺序按照添加的顺序。通过addEventListener()添加的事件只能通过removeEventListener()移除事件,但是无法移除通过 addEventListener()添加的匿名函数

事件对象

DOM中的事件对象

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

属性 / 方法类型说明
bubblesBoolean表明事件是否冒泡
cancelableBoolean表明是否可以取消事件的默认行为
currentTargetElement事件处理程序当前正在处理事件的那个元素
targetElement事件的目标
detailInteger与事件相关的细节信息
eventPhaseInteger调用事件处理程序的阶段:1——捕获阶段、2——"处于目标"、3——冒泡阶段
preventDefault()Function取消事件的默认行为。如果cancelable为true,则可以使用这个方法
stopImmediatePropagation()Function取消事件的进一步捕获或冒泡,同时阻止任何事件处理程序被调用(DOM3级事件新增)
stopPropagation()Function取消事件的进一步捕获或冒泡。如果bubbles为true,则可以使用这个方法
trustedBooleantrue表示事件是浏览器生成的,false表示事件是开发人员通过js创建的(DOM3级事件新增)
typeString被触发的事件的类型
viewAbstractView与事件关联抽象视图。等同于发生事件的window对象

this、currentTarget、target:在事件处理程序内部,this始终等于currentTarget,target只包含事件的实际目标。当 eventPhase 等于 2 时,this、target和 currentTarget 始终都是相等的

事件类型

  • UI(用户界面)事件,当用户与页面上的元素交互时触发;
  • 焦点事件,当元素获得或失去焦点时触发;
  • 鼠标事件,当用户通过鼠标在页面上执行操作时触发;
  • 滚轮事件,当使用鼠标滚轮时触发;
  • 文本事件,当在文档中输入文本时触发;
  • 键盘事件,当用户通过键盘在页面上执行操作时触发;
  • 合成事件,当为 IME(输入法编辑器)输入字符时触发;
  • 变动事件,当底层 DOM 结构发生变化时触发。

UI事件

  1. load事件

    当页面完全加载后(包括所有图像、JavaScript文件、css文件等外部资源),触发window上的load事件

  2. unload事件

    当文档被完全卸载后触发,从一个页面切换到另一个页面就会发生unload事件,可以利用这个事件清除引用,避免内存泄漏

  3. resize事件

    浏览器窗口调整到一个新的高度或宽度时触发resize事件

  4. scroll事件

    当滚动带滚动条的元素中的内容时,在该元素上面触发。

焦点事件

  • blur:元素失去焦点时触发,不冒泡
  • focus:元素获得焦点时触发,不冒泡
  • focusin:元素获得焦点时触发,冒泡
  • focusout:失去焦点时触发,冒泡

鼠标与滚轮事件

  • click:在用户单击主鼠标按钮或者按下回车键时触发
  • dblclick:在用户双击主鼠标按钮时触发
  • mousedown:在用户按下了任意鼠标按钮时触发
  • mouseenter:在鼠标光标从元素外部首次移动到元素范围之内时触发。不冒泡,光标移动到后代元素上不会触发
  • mouseleave:在位于元素上方的鼠标光标移动到元素范围之外时触发。不冒泡,光标移动到后代元素上不会触发
  • mousemove:当鼠标指针在元素内部移动时重复地触发
  • mouseout:鼠标指针位于一个元素上方,移入另一个元素(可能位于前一个元素的外部,也可能是这个元素的子元素)时触发
  • mouseover:鼠标指针位于一个元素外部,首次移入另一个元素边界之内时触发
  • mouseup:在用户释放鼠标按钮时触发
  1. 客户区坐标位置

    鼠标事件都是在浏览器视口中的特定位置上发生的。这个位置信息保存在事件对象的 clientX 和clientY 属性中。所有浏览器都支持这两个属性,它们的值表示事件发生时鼠标指针在视口中的水平和垂直坐标。

  2. 页面坐标位置

    页面坐标通过事件对象的 pageX 和pageY 属性获取,这两个属性表示事件在页面中的发生位置,即鼠标光标在页面中的位置,因此坐标是从页面本身而非视口的左边和顶边计算的。

    在页面没有滚动的情况下,pageX 和 pageY 的值与 clientX 和 clientY 的值相等。

  3. 屏幕坐标位置

    screenX 和 screenY 属性表示鼠标事件发生时鼠标指针相对于整个屏幕的坐标信息

  4. 修改键

    修改键包括Shift、Ctrl、Alt 和 Meta(在 Windows 键盘中是 Windows 键,在苹果机中是 Cmd 键)四个键,对应的四个属性shiftKey、ctrlKey、altKey 和 metaKey表示修改键的状态。这些属性值为布尔值, true表示相应的键被按下了,false则没有。

  5. 相关元素

    DOM 通过 event 对象的 relatedTarget 属性提供了相关元素的信息。这个属性只对于 mouseover和mouseout事件才包含值;对于其他事件,这个属性的值是null。

  6. 鼠标按钮

    对于 mousedown 和 mouseup 事件来说,在其 event 对象存在一个 button 属性,表示按下或释放的按钮。DOM 的 button 属性可能有如下 3 个值:0 表示主鼠标按钮,1 表示中间的鼠标按钮(鼠标滚轮按钮),2 表示次鼠标按钮

  7. 更多的事件信息

    event 对象中的 detail 属性,用于给出有关事件的更多信息。对于鼠标事件来说,detail 中包含了一个数值,表示在给定位置上发生了多少次单击。在同一个元素上相继地发生一次 mousedown 和一次 mouseup 事件算作一次单击。detail 属性从 1 开始计数,每次单击发生后都会递增。如果鼠标在 mousedown 和 mouseup 之间移动了位置,则 detail 会被重置为 0。

  8. 鼠标滚轮事件

    鼠标滚轮与页面交互、在垂直方向上滚动页面时(无论向上还是向下),就会触发 mousewheel 事件。与 mousewheel 事件对应的 event 对象包含一个 wheelDelta 属性。当用户向前滚动鼠标滚轮时,wheelDelta 是 120 的倍数;当用户向后滚动鼠标滚轮时,wheelDelta 是 -120 的倍数

键盘与文本事件

  • keydown:按下键盘上的任意键时触发
  • keypress:按下键盘上的字符键时触发
  • keyup:释放键盘上的键时触发
  • textInput:文本插入文本框之前触发
  1. 键码

    keydown和keyup 事件对象有一个keyCode属性,代表键盘的特定键。小写字母或数字的ASCII码和keyCode属性值相等

  2. 字符编码

    keypress事件有一个charCode属性,代表按下键的ASCII编码

  3. DOM3 级变化

    DOM3 级事件的键盘事件中,不在包含charCode属性,包含两个新的属性:key和char。key属性的值等于按下键的文本值(如"k","M","shift"),char属性在按下字符键时等于键文本值,按下非文本键时值为null

  4. textInput事件

    在可编辑区域中输入字符时,触发该事件。event对象包含data属性,等于用户输入的字符;以及inputMethod属性表示输入到文本框中的方式

变动事件

  • DOMSubtreeModified:在 DOM 结构中发生任何变化时触发后触发。
  • DOMNodeInserted:在一个节点作为子节点被插入到另一个节点中时触发。
  • DOMNodeRemoved:在节点从其父节点中被移除时触发。
  • DOMNodeInsertedIntoDocument:在一个节点被直接插入文档或通过子树间接插入文档之后触发。这个事件在 DOMNodeInserted 之后触发。
  • DOMNodeRemovedFromDocument:在一个节点被直接从文档中移除或通过子树间接从文档中移除之前触发。这个事件在 DOMNodeRemoved 之后触发。
  • DOMAttrModified:在特性被修改之后触发。
  • DOMCharacterDataModified:在文本节点的值发生变化时触发。

HTML5事件

  1. contextmenu事件

    显示自定义的上下文菜单

  2. beforeunload事件

    在浏览器卸载页面之前触发,弹出一个对话框,可用来取消卸载并继续使用原有页面。为了显示弹出框,必须将 event.returnValue 的值设置为要显示给用户的字符串(IE 和 Fiefox ),同时作为函数的值返回( Safari 和 Chrome )

  3. DOMContentLoaded事件

    在形成完整的 DOM 树之后就会触发

  4. readystatechange事件

    提供文档或元素的加载状态有关的信息。支持该事件的对象有一个readystate属性,可能值如下:

    • uninitialized(未初始化):对象存在但尚未初始化
    • loading(正在加载):对象正在加载数据
    • loaded(加载完毕):对象加载数据完毕
    • interactive(交互):可以操作对象了,但还没有完全加载
    • complete(完成):对象已经加载完毕
  5. pageshow和pagehide事件

    pageshow():页面显示时触发,在load事件之后触发,必须将事件处理程序添加到window。该事件包含一个persisted属性,如果页面被保存在了 bfcache 中,则这个属性的值为 true;否则,这个属性的值为 false。

    pageshow():浏览器卸载页面时触发,在unload事件之前,必须将事件处理程序添加到window。该事件也有一个persisted属性,如果页面是从 bfcache 中加载的,那么 persisted 的值就是 true

    当第一次触发 pageshow 时,persisted 的值一定是 false,而在第一次触发 pagehide 时,persisted 就会变成 true(除非页面不会被保存在 bfcache 中)。

  6. hashchange()事件

    URL参数列表发生变化时触发,事件处理程序必须加给window对象。该事件包含两个属性:oldURL和newURL,这两个属性分别保存着参数列表前后完整的URL。

设备事件

  1. orientationchange事件

    用户使用手机Safari时的查看模式,用户改变了设备的查看模式时触发该事件。window.orientation属性有三个值:0代表手机竖着正拿,90代表手机向左旋转90°,-90代表手机向右旋转90°

  2. MozOrientation事件

    设备的加速计检测设备方向改变时触发该事件,该事件的 event 对象包含三个属性:x、y和z。这三个属性的值介于1和-1之间,表示不同方向。静止状态下,x值为0,y值为0,z值为1。设备向右倾斜,x值减小;向左倾斜,x值增大。设备向远离用户的方向倾斜,y 值会减小;向接近用户的方向倾斜,y 值会增大。z 轴检测垂直加速度度,1 表示静止不动,在设备移动时值会减小。(失重状态下值为 0)

  3. deviceorientation事件

    设备在空间中的朝向。设备在三维空间中靠 x、y 和 z 轴定位。设备静止在水平表面上时,这三个值都是 0。x轴方向是从左往右,y 轴方向是从下往上,z 轴方向是从后往前。该事件的 event 对象有以下5个属性:

    • alpha:在围绕 z 轴旋转时(即左右旋转时),y 轴的度数差;是一个介于 0 到 360 之间的浮点数
    • beta:在围绕 x 轴旋转时(即前后旋转时),z 轴的度数差;是一个介于 -180 到 180 之间的浮点数
    • gamma:在围绕 y 轴旋转时(即扭转设备时),z 轴的度数差;是一个介于 -90 到 90 之间的浮点数
    • absolute:布尔值,表示设备是否返回一个绝对值
    • compassCalibrated:布尔值,表示设备的指南针是否校准过
  4. devicemotion事件

    触发 devicemotion 事件时,事件对象包含以下属性

    • acceleration:一个包含 x、y 和 z 属性的对象,在不考虑重力的情况下,告诉你在每个方向上的加速度
    • accelerationIncludingGravity:一个包含 x、y 和 z 属性的对象,在考虑 z 轴自然重力加速度的情况下,告诉你在每个方向上的加速度
    • interval:以毫秒表示的时间值,必须在另一个 devicemotion 事件触发前传入。这个值在每个事件中应该是一个常量
    • rotationRate:一个包含表示方向的 alpha、beta 和 gamma 属性的对象

触摸与手势事件

  1. 触摸事件

    • touchstart:当手指触摸屏幕时触发;即使已经有一个手指放在了屏幕上也会触发
    • touchmove:当手指在屏幕上滑动时连续地触发。在这个事件发生期间,调用preventDefault() 可以阻止滚动。
    • touchend:当手指从屏幕上移开时触发。
    • touchcancel:当系统停止跟踪触摸时触发。

    上面这几个事件都会冒泡,也都可以取消。每个触摸事件的 event 对象都提供了在鼠标事件中常见的属性:bubbles、cancelable、view、clientX、clientY、screenX、screenY、detail、altKey、shiftKey、ctrlKey 和 metaKey。除了常见的 DOM 属性外,触摸事件还包含下列三个用于跟踪触摸的属性:

    • touches:表示当前跟踪的触摸操作的 Touch 对象的数组
    • targetTouchs:特定于事件目标的 Touch 对象的数组
    • changeTouches:表示自上次触摸以来发生了什么改变的 Touch 对象的数组

    每个 Touch对象包含下列属性

    • clientX:触摸目标在视口中的 x 坐标
    • clientY:触摸目标在视口中的 y 坐标
    • identifier:标识触摸的唯一 ID
    • pageX:触摸目标在页面中的 x 坐标
    • pageY:触摸目标在页面中的 y 坐标
    • screenX:触摸目标在屏幕中的 x 坐标
    • screenY:触摸目标在屏幕中的 y 坐标
    • target:触摸的 DOM 节点目标
  2. 手势事件

    • gesturestart:当一个手指已经按在屏幕上而另一个手指又触摸屏幕时触发。
    • gesturechange:当触摸屏幕的任何一个手指的位置发生变化时触发。
    • gestureend:当任何一个手指从屏幕上面移开时触发。

    两个手指同时位于目标元素的范围之内,才能触发手势事件。每个手势事件的 event 对象除了包含标准的鼠标事件属性,还包含两个额外的属性:rotation 和 scale 。rotation 表示手指变化引起的旋转角度,负值表示逆时针旋转,正值表示顺时针旋转(该值从 0 开始)。 scale 表示两个手指间的距离;这个值从 1 开始,并随距离拉大而增长,随距离缩短而减小

内存和性能

事件委托

事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。最适合采用事件委托技术的事件包括 click、mousedown、mouseup、keydown、keyup 和 keypress。

var list = document.getElementById("myLinks"); 
EventUtil.addHandler(list, "click", function(event){ 
  	event = EventUtil.getEvent(event); 
  	var target = EventUtil.getTarget(event); 
  	switch(target.id){ 
  		case "doSomething": 
  			document.title = "I changed the document's title"; 
  			break; 
	  	case "goSomewhere": 
  			location.href = "http://www.wrox.com"; 
  			break; 
  		case "sayHi": 
  			alert("hi"); 
  			break; 
  	} 
}); 

事件委托优点:

  • document 对象很快就可以访问,而且可以在页面生命周期的任何时点上为它添加事件处理程序(无需等待 DOMContentLoaded 或 load 事件)。换句话说,只要可单击的元素呈现在页面上,就可以立即具备适当的功能
  • 在页面中设置事件处理程序所需的时间更少。只添加一个事件处理程序所需的 DOM 引用更少,所花的时间也更少
  • 整个页面占用的内存空间更少,能够提升整体性能

移除事件处理程序

空事件处理程序,即DOM元素被清除的时候没有解除事件引用,遗留下的事件处理程序,这是造成 Web 应用程序内存与性能问题的主要原因。导致空事件处理程序有两种情况:

  • 从文档中移除带有事件处理程序的元素时,使用innerHTML替换内容时没有解除事件引用
  • 卸载页面的时候事件处理程序没有被清空的话,很可能滞留在内存中(最好的做法是在页面卸载之前,先通过 onunload 事件处理程序移除所有事件处理程序)

模拟事件

DOM中的事件模拟

在 document 对象使用 createEvent() 方法创建 event 对象。这个方法接收一个参数,表示要创建的事件类型的字符串。

  • UIEvents:一般化的 UI 事件。鼠标事件和键盘事件都继承自 UI 事件。DOM3 级中是 UIEvent
  • MouseEvents:一般化的鼠标事件。DOM3 级中是 MouseEvent
  • MutationEvents:一般化的 DOM 变动事件。DOM3 级中是 MutationEvent
  • HTMLEvents:一般化的 HTML 事件。没有对应的 DOM3 级事件

创建了 event 对象之后,还需要使用与事件有关的信息对其进行初始化。

模拟事件的最后一步就是触发事件。这一步需要使用 dispatchEvent()方法,该方法接收一个参数,表示要触发事件的 event 对象。

  1. 模拟鼠标事件

    创建鼠标事件对象的方法是为 createEvent() 传入字符串"MouseEvents"。返回的对象有一个名为 initMouseEvent()方法, 用于指定与该鼠标事件有关的信息。这个方法接收 15 个参数:

    • type(字符串):表示要触发的事件类型,例如"click"。
    • bubbles(布尔值):表示事件是否应该冒泡。为精确地模拟鼠标事件,应该把这个参数设置true。
    • cancelable(布尔值):表示事件是否可以取消。为精确地模拟鼠标事件,应该把这个参数置为 true。
    • view(AbstractView):与事件关联的视图。这个参数几乎总是要设置为 document.defaultView
    • detail(整数):与事件有关的详细信息。这个值一般只有事件处理程序使用,但通常都设置为 0
    • screenX(整数):事件相对于屏幕的 X 坐标。
    • screenY(整数):事件相对于屏幕的 Y 坐标。
    • clientX(整数):事件相对于视口的 X 坐标。
    • clientY(整数):事件想对于视口的 Y 坐标。
    • ctrlKey(布尔值):表示是否按下了 Ctrl 键。默认值为 false
    • altKey(布尔值):表示是否按下了 Alt 键。默认值为 false。
    • shiftKey(布尔值):表示是否按下了 Shift 键。默认值为 false
    • metaKey(布尔值):表示是否按下了 Meta 键。默认值为 false
    • button(整数):表示按下了哪一个鼠标键。默认值为 0。
    • relatedTarget(对象):表示与事件相关的对象。这个参数只在模拟 mouseover 或 mouseout 时使用。
    var btn = document.getElementById("myBtn"); 
    //创建事件对象
    var event = document.createEvent("MouseEvents"); 
      
    //初始化事件对象
    event.initMouseEvent("click", true, true, document.defaultView, 0, 0, 0, 0, 0, false, false, false, false, 0, null); 
      
    //触发事件
    btn.dispatchEvent(event); 
    
  2. 模拟键盘事件

    调用 createEvent()并传入"KeyboardEvent"就可以创建一个键盘事件。返回的事件对象会包含一个 initKeyEvent()方法,这个方法接收下列参数:

    • type(字符串):表示要触发的事件类型,如"keydown"。
    • bubbles(布尔值):表示事件是否应该冒泡。为精确模拟鼠标事件,应该设置为 true。
    • cancelable(布尔值):表示事件是否可以取消。为精确模拟鼠标事件,应该设置为 true。
    • view(AbstractView):与事件关联的视图。这个参数几乎总是要设置为 document. defaultView。
    • key(布尔值):表示按下的键的键码。
    • location(整数):表示按下了哪里的键。0 表示默认的主键盘,1 表示左,2 表示右,3 表示数字键盘,4 表示移动设备(即虚拟键盘),5 表示手柄。
    • modifiers(字符串):空格分隔的修改键列表,如"Shift"。
    • repeat(整数):在一行中按了这个键多少次。
  3. 模拟其他事件

    要模拟变动事件,可以使用 createEvent("MutationEvents")创建一个包含initMutationEvent()方法的变动事件对象。这个方法接受的参数包括:type、bubbles、cancelable、relatedNode、preValue、newValue、attrName 和 attrChange。 要模拟 HTML 事件,同样需要先创建一个 event 对象——通过 createEvent("HTMLEvents"),然后再使用这个对象的 initEvent()方法来初始化它即可。

  4. 自定义DOM事件

    要创建新的自定义事件,可以调用 createEvent("CustomEvent")。返回的对象有一个名为 initCustomEvent()的方法,接收如下 4 个参数:

    • type(字符串):触发的事件类型,例如"keydown"。
    • bubbles(布尔值):表示事件是否应该冒泡。
    • cancelable(布尔值):表示事件是否可以取消。
    • detail(对象):任意值,保存在 event 对象的 detail 属性中