《JavaScript 高级程序设计》第十七章 事件 学习记录

363 阅读18分钟

1、事件流

  • 事件接收顺序。IE支持事件冒泡流,Nestcape Communicator支持事件捕获流

1、事件冒泡

  • 事件冒泡,因为事件被定义为从最具体的元素(文档树最深的节点)开始触发,向上传播至没有那么具体的元素。
  • 现代浏览器都支持事件冒泡。一直冒泡到window对象

2、事件捕获

  • 事件捕获的意思是最不具体的节点最先收到事件,而最具体的节点最后收到事件。
  • 事件捕获是为了在事件到达最终目标前拦截事件。

3、DOM事件流

  • 三个阶段:事件捕获,到达目标,事件冒泡

    ![image-20210622001701564](/Users/hanyefeng/Library/Application Support/typora-user-images/image-20210622001701564.png)

  • DOM事件流,实际的目标在捕获阶段不会接收到事件。在到达目标阶段才会触发,通常被认为是冒泡阶段的一部分

  • 事件目标上有两个机会处理事件。

2、事件处理程序

  • 为响应事件而调用的函数称为事件处理程序(事件监听器)。

1、HTML事件处理程序

<input type="button" value="click me" onclick="xxx">

  • 事件处理程序可以访问全局作用域中的一切。
  • 通过属性的方式指定的事件处理程序,会创建一个函数来封装属性的值,即局部变量event
  • this相当于事件的目标元素
  • document和元素自身的成员都可以被当成局部变量来访问(with)
  • 表单里,可以直接访问同一表单中其他成员
  • 问题:
    • 时机问题,可能已经交互了,但事件处理程序的代码还无法执行。
    • 对事件处理程序作用域链的扩展在不同浏览器中可能导致不同的结果。
    • HTML与JavaScript强耦合,如果需要修改事件处理程序必须在两个地方修改代码。

2、DOM0 事件处理程序

  • 把一个函数赋值给一个事件处理程序属性。

    let btn = document.getElementById("myBtn")
    btn.onclick = function() {
      console.log("Clicked")
    }
    
  • this等于元素本身

  • 移除事件处理程序btn.onclick = null

3、DOM2事件处理程序

  • addEventListener()removeEventListener()
  • 参数:事件名,事件处理函数,是否捕获(布尔值)默认false
let btn = document.getElementById("myBtn")
btn.addEventListener("click", ()=> {
	console.log(this.id)
}, false)
  • 优势是可以添加多个事件处理程序,以添加顺序来触发。
  • 通过removeEventListener()来传入与添加相同参数来移除,所以匿名函数无法移除。

4、IE事件处理程序

  • attachEvent()detachEvent()
  • 参数:事件处理程序的名字和事件处理函数。只支持冒泡
var btn = document.getElementById("myBtn")
btn.attachEvent("onclick", function(){
  console.log("Clicked")
})
  • this等于window
  • 可以添加多个事件处理程序,但触发顺序与添加顺序相反
  • 通过detachEvent()移除,需要传入与添加相同参数来移除

5、跨浏览器事件处理程序

  • addHandler()

  • removeHandler()

var EventUtil = {
  addHandler: function(element, type, handler) {
    if(element.addEvenetListener) {
      element.addEventListener(type, handler, false)
    }else if(element.attachEvent) {
      element.attachEvent("on" + type, handler)
    }else {
      element["on" + type] = handler
    }
  },
  removeHandler: function(element, type, handler) {
    if(element.removeEvenetListener) {
      element.removeEventListener(type, handler, false)
    }else if(element.detachEvent) {
      element.detachEvent("on" + type, handler)
    }else {
      element["on" + type] = null
    }
  }
}

3、事件对象

  • DOM中发生事件时,所有相关信息都会存储在event对象中。
  • 包含一些基本信息,如导致事件的元素,发生的事件类型,以及可能与特定事件相关的任何其他数据。

1、DOM事件对象

  • 所有事件对象都包含以下公共属性和方法
属性/方法类型读/写说明
bubbles布尔值只读表示事件是否冒泡
cancelable布尔值只读表示是否可以取消事件的默认行为
currentTarget元素只读当前事件处理程序所在的元素
defaultPrevented布尔值只读true表示已调用preventDefault()方法
detail整数只读事件相关的其他信息
eventPhase整数只读事件处理程序阶段:1代表捕获阶段,
2代表到达目标,3代表冒泡阶段
preventDefault()函数只读取消事件默认行为(cancelable为true可用)
stopImmediatePropagation()函数只读取消所有后续事件捕获或事件冒泡,
并阻止调用任何后续事件处理程序
stopPropagation()函数只读取消所有后续事件捕获或事件冒泡(bubbles为true可用)
target元素只读事件目标
trusted (isTrusted)布尔值只读true表示事件是由浏览器生成的,false表示是开发者创建的
type字符串只读被触发事件类型
ViewAbstractView只读与事件相关的抽象视图,等于事件发生的window对象
  • event对象只在事件处理程序执行期间存在,一旦执行完毕,就会被销毁。

2、IE事件对象

  • event是window的一个属性
属性/方法类型读/写说明
cancelBubble布尔值读/写默认为false,设置为true取消冒泡
returnValue布尔值读/写默认true,设置为false取消事件默认行为
srcElement元素只读时间目标(target)
type字符串只读触发事件的类型

3、跨浏览器事件对象

var EventUtil = {
  addHandler: function(element, type, handler) {
  //... 
 },
  getEvent: function(event) {
    return event ? event : window.event
  },
  getTarget: function(event) {
    return event.target || event.srcElement
  },
  preventDefault: function(event) {
    if(event.preventDefault) {
      event.preventDefault()
    }else {
      event.returnValue = false
    }
  },
  removeHandler: function(element, type, handler) {
    //...
  },
  stopPropagation: function(event) {
    if(event.stopPropagation) {
      event.stopPropagation()
    }else {
      event.cancelBubble = true
    }
  }
}

4、事件类型

1、用户界面事件(UIEvent)

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

  • load:window上当页面加载完成后,在窗套上当所有窗格加载完成后,在<img>元素上当图片加载完成后,在<object>元素上当相应对象加载完成后触发

  • unload:window上当页面完全卸载后,在窗套上当所有窗格都卸载完成后,在<object>元素上当相应对象卸载完成后触发

  • abort:在<object>元素上相应对象加载完成前被用户提前终止下载时触发

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

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

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

  • scroll:当用户滚动包含滚动条的元素时在元素上触发

1、load事件

  • window上触发,load 事件会在整个页面(包括 所有外部资源如图片、JavaScript 文件和 CSS 文件)加载完成后触发
window.addEventListener("load", (event)=> {
  console.log("Loaded!")
})
<!DOCTYPE html>
<html>
<head>
  <title>Load Event Example</title>
</head>
<body onload="console.log('Loaded!')">
</body>
</html>
  • 图片上也会触发load事件,包括DOM中的图片和非DOM中的图片。可以在HTML中直接给<img>元素的 onload 属性指定事件处理程序
< img src="smile.gif" onload="console.log('Image loaded.')">
let image = document.getElementById("myImage")
image.addEventListener("load", (event)=> {
  console.log(event.target.src)
})
  • <script>元素会在JavaScript文件加载完成后触发load事件

2、unload事件

  • 一般从一个页面导航到另一个页面出发,常用来清理引用,避免内存泄漏。
window.addEventListener("unload", (event)=> {
  console.log("Unloaded!")
})

3、resize事件

  • 当浏览器窗口被缩放到新高度或新宽度的时候触发。
  • 最大化最小化也会触发
window.addEventListener("resize", (event)=> {
  console.log("Resized!")
})

4、scroll事件

window.addEventListener("scroll", (event)=> {
  if(document.compatMode === "CSS1Compat") {
    console.log(document.documentElement.scrollTop)
  }else {
    console.log(document.body.scrollTop)
  }
})

2、焦点事件(FocusEvent)

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

  • blur:在元素失去焦点时触发,不冒泡

  • focus:在元素获得焦点时触发,不冒泡

  • focusin:在元素获得焦点时触发。是focus的冒泡版

  • focusout:在元素失去焦点时触发。是blur的通用版

  • 当焦点从页面一个元素到另一个元素

    1. focusout 在失去焦点的元素上触发
    2. focusin 在获得焦点的元素上触发
    3. blur 在失去焦点的元素上触发
    4. focus 在获得焦点的元素上触发

3、鼠标和滚轮事件(MouseEvent & WheelEvent)

  • 鼠标事件
    • click:在用户单击鼠标左键触发
    • dblclick:在用户双击鼠标左键触发
    • mousedown:在用户按下任意鼠标键时触发
    • mouseenter:在用户把鼠标光标从元素外部移到元素内部触发。不冒泡,不会在鼠标经过后代元素触发。
    • mouseleave:在用户把鼠标光标从元素内部移到元素外部触发。不冒泡,不会在鼠标经过后代元素触发。
    • mousemove:在鼠标光标在元素上移动时反复触发
    • mouseout:在用户把光标从一个元素一到另一个元素上触发,移动到的元素可以是原始元素的外部元素,也可以是子元素。
    • mouseover:在用户把鼠标光标从元素外部移到元素内部时触发。
    • mouseup:在用户释放鼠标键时触发
  • 滚轮事件
    • mousewheel:鼠标滚轮或带滚轮的类似设备上滚轮的交互

1、客户端坐标

  • 鼠标光标客户端浏览器视口中坐标,保存在event的clientXclientY上。

2、页面坐标

  • 鼠标发生时鼠标光标在页面中的坐标,保存在event的pageXpageY上。
  • 没有页面滚动时和客户端坐标相同

3、屏幕坐标

  • 鼠标发生时鼠标光标在屏幕中的坐标,保存在event的screenXscreenY上。

4、修饰符

  • Shift、Ctrl、Alt、Meta
  • ShiftKey、CtrlKey、AltKey、MetaKey。按下时的值为true,否则为false

5、相关元素

  • relatedTarget表示相关元素(与目标元素相反)信息(mouseover、mouseout才包含)
var EventUtil = {
  getRelatedTarget: function(event) {
    if(event.relatedTarget) {
      return event.relatedTarget
    }else if(event.toElement) {
      return event.toElement
    }else if(event.fromElement) {
      return event.fromElement
    }
  }
}

6、鼠标按键

  • button属性
    • 0 主键
    • 1 滚轮
    • 2 副键

7、额外事件信息

  • detail:鼠标事件表示在给定同一象素位置发生多少次单击,如果在mousedown和mouseup之间移动了,会重置为0

8、mousewheel事件

  • 鼠标滚轮滚动事件
  • wheelDelta属性,鼠标向前滚动+120,向后滚动-120

9、触摸屏设备

  • 不支持dblclick事件
  • 单指点击可以出发mousemove事件
  • mousemove也会触发museover和mouseout事件
  • 双指点触屏幕并滑动会触发mousewheel和scroll事件

10、无障碍问题

4、键盘与输入事件(KeyboardEvent & InputEvent)

  • keydown,用户按下键盘上某个键时触发,持续按住重复触发。
  • keypress,用户按下键盘上某个键并产生字符时触发,持续按住会重复触发。Esc也会触发。DOM3 Events废弃了这个事件,推荐textInput事件
  • keyup,用户释放键盘上某个键触发
  • textInput,输入事件,用于在文本显示给用户前更方便地截获文本输入,会在文本插入到文本框之前触发。
  • 支持与鼠标事件相同的修饰键

1、键码

  • keydown和keyup事件,event对象的keyCode属性中会保存一个键码,对应键盘上特定的一个键。对字母和数字,keyCode与小写字母和数字的ASCII编码一致。

2、字符编码

  • charCode属性,keypress事件才会被设置,包含的是对应的ASCII码,通常是0。

    var EventUtil = {
      getCharCode: function(event) {
        if(typeof event.charCode == 'number') {
          return event.charCode
        }else {
          return event.keyCode
        }
      }
    }
    
  • 可以通过String.fromCharCode() 转为实际字符串

3、DOM3的变化

  • 定义了keychar属性,未定义charCode
  • key代替keyCode,且包含字符串。按下字符键时,等于文本字符。按下非字符键时,key值是键名。
  • char在字符键与key类似,但在非字符键时为null
  • 不建议使用keychar
  • location,数值表示在哪里按的键,0默认,1左边,2右边,3数字键盘,4移动设备(虚拟键盘),5游戏手柄
  • getModifierState()接受一个参数,一个等于Shift、Control、Alt、AliGraph、Meta的字符串,表示要检测的修饰键。如果处于激活状态则返回true

4、textInput事件

  • 只在可编辑区域触发
  • 只有在新字符被插入时才触发。
  • data属性,包含要插入的字符。data值始终是要被插入的值。
  • inputMethod属性,表示输入手段
    • 0 不确定
    • 1 键盘
    • 2 粘贴
    • 3 拖放
    • 4 IME
    • 5 表单
    • 6 手写
    • 7 语音
    • 8 组合
    • 9 脚本

5、设备上的键盘事件

5、合成事件

  • compositionstart,在IME的文本合成系统打开时触发,表示输入即将开始
    • data包含正在编辑的文本
  • compositionupdate,在新字符插入输入字段时触发
    • data包含要插入的新字符
  • compositionend,在IME的文本合成系统关闭时触发,恢复正常键盘输入。
    • data包含本次合成过程中输入的全部内容

6、变化事件

  • DOM发生变化时提供通知

7、HTML5事件

1、contextmenu事件

  • 鼠标右键操作事件,专门用于表示何时该显示上下文菜单,从而允许开发者取消默认的上下文菜单并提供自定义菜单。
  • contentmenu事件冒泡,只要给document指定一个事件处理程序即可。
  • 目标是触发操作的元素,使用event.preventDefault() 取消默认

2、beforeunload事件

  • 在window上触发,给开发者提供阻止页面被卸载掉机会,该页面不能取消。
  • 需要将event.returnValue设置为窗口要显示的字符串,并作为函数值返回。

3、DOMContentLoaded事件

  • 在DOM树构建完成后立即触发,不用等待图片、JavaScript文件、CSS文件或其他资源加载完成。
  • 事件实际目标是document,但会冒泡到window
  • event不含任何内容,除了target是document。
  • 常用于添加事件处理程序或执行其他DOM操作,始终在load前触发。
  • 对于不支持该事件的浏览器,使用超时为0的setTimeout函数

4、readystatechange事件

  • 支持readystatechange事件的每个对象都有一个readyState属性,可能的值如下
    • uninitialized:对象存在并尚未初始化
    • loading:对象正在加载数据
    • loaded:对象已经加载完数据
    • interactive:对象可以交互,但尚未加载完成
    • complete:对象加载完成
  • 顺序及数量不保证。

5、pageshow事件和pagehide事件

  • 往返缓存的功能,旨在使用浏览器前进和后退按钮时加快页面切换。

  • 将整个页面保存在内存中,导航到这个页面不会触发load事件

  • pageshow,在页面显示时触发,无论是否来自往返缓存。

    • 在新加载页面上,pageshow会在load事件之后触发
    • 在来自往返缓存的页面上,pageshow会在页面完全恢复后触发
    • 事件目标时document,但事件处理程序必须添加到window上
    • persisted,event的额外属性,是一个布尔值,表示页面是否存储在了往返缓存中。
  • pagehide,在页面从浏览器卸载后触发,在unload事件之前触发。

    • persisted,event的额外属性,是一个布尔值,表示页面是否会被保存在往返缓存中。
  • 注册了unload事件的页面会自动排除在往返缓存之外。

6、hashchange事件

  • 用在URL散列表发生变化时通知开发者。
  • event对象新属性oldURLnewURL分别保存变化前后的URL

8、设备事件

  • 设备事件可以用来确定用户使用设备的方式。

1、orientationchange事件

  • 用来判断用户的设备是处于垂直模式还是水平模式。
  • 移动Safari在window上暴露了window.orientation属性。
    • 0 垂直模式
    • 90 左转水平模式
    • -90 右转水平模式
  • 每当用户旋转设备改变了模式,就会触发orientationchange事件

2、deviceorientation事件

  • 获取设备加速计信息,只反应设备在空中的朝向,而不涉及移动相关信息。

3、devicemotion事件

  • 用于提示设备实际上在移动,不仅仅是改变了朝向。

9、触摸及手势事件

1、触摸事件

  • touchstart:手指放到屏幕上触发(一个手指已经放到屏幕上)
  • touchmove:手指在屏幕上滑动时连续触发。调用preventDefault()可以阻止滚动。
  • touchend:手指从屏幕上移开时触发
  • touchcancel:系统停止跟踪触摸时触发。
    • 都会冒泡,都可以被取消
    • event包含鼠标事件的公共属性
      • bubbles、cancelable、view、clientX、clientY、screenX、screenY、detail、altKey、shiftKey、ctrlKey和metaKey
    • 以下三个属性来跟踪触点
      • touches:Touch对象的数组,表示当前屏幕上的每个触点
      • targetTouches:Touch对象的数组,表示特定于事件目标的触点
      • changedTouches:Touch对象的数组,表示自上次用户动作之后变化的触点
    • 每个Touch对象包含的属性
      • clientX :触点在视口上x坐标
      • clientY:触点在视口上y坐标
      • identifier:触点ID
      • pageX:触点在页面上x坐标
      • pageY:触点在页面上y坐标
      • screenX:触点在屏幕上x坐标
      • screenY:触点在屏幕上y坐标
      • target:触摸事件的事件目标

2、手势事件

  • 会在两个手指触碰屏幕且相对距离或旋转角度变化时触发。
  • gesturestart:一个手指已经放在屏幕上,再把另一个手指放到屏幕上触发
  • gestureschange:任何一个手指在屏幕上位置发生变化时触发。
  • gestureend:其中一个手指离开屏幕时触发。
  • event包含基本的公共属性
  • event额外属性rotationscale
    • rotation表示手指变化旋转的度数,负值表示逆时针旋转,正值表示顺时针旋转。
    • scale表示两指之间距离变化的程度。

10、事件参考

5、内存与性能

1、事件委托

  • 过多的事件处理程序的解决方案时使用事件委托。利用事件冒泡,可以只使用一个事件处理程序来管理一种类型的事件。
  • 优点:
    • document对象随时可用,任何时候都可以给它添加事件处理程序。
    • 节省花在设置页面事件处理程序上的时间,只指定一个事件处理程序可用节省DOM的引用,节省时间。
    • 减少整个页面所需的内存,提升整体性能。

2、删除事件处理程序

  • 及时删除不用等事件处理程序,提升性能
  • 导致问题的原因
    • 删除带有事件处理程序的元素,如果删除的元素上还有事件处理程序,就不会被垃圾收集程序正常清理。
      • 需要在删除前,先手工删除它的事件处理程序。
    • 页面卸载后事件处理程序并没有被清理,仍然保存在内存中。
      • 在onunload事件处理程序里趁页面未卸载先删除所有的事件处理程序。删除后页面不会保存在往返缓存中。

6、模拟事件

1、DOM事件模拟

  • document.createEvent()创建一个event对象。接收一个参数,表示要创建事件类型的字符串。可用值:
    • UIEvents UIEvent 用户界面事件
    • MouseEvents MouseEvent 通用鼠标事件
    • HTMLEvents 无 通用HTML事件
  • dispatchEvent()触发事件
    • 该方法存在与所有支持事件的DOM节点上
    • 参数是要触发事件的event对象

1、模拟鼠标事件

  • 创建鼠标event对象 传入"MouseEvent"

  • initMouseEvent()方法,用来为新对象指定鼠标的特定信息。

    • type 要触发的事件类型,如click

    • bubbles 是否冒泡

    • cancelable:表示事件是否可以取消。

    • view:与事件关联的视图。基本上始终是 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 时使用

2、模拟键盘事件

  • 创建键盘event对象 传入"KeyboardEvent"

  • initKeyboardEvent()方法接收参数

    • type :要触发的事件类型,如keydown

    • bubbles :是否冒泡

    • cancelable:表示事件是否可以取消。

    • view:与事件关联的视图。基本上始终是 document.defaultView。

    • key:按下按键的字符串代码

    • location:按下键盘的位置

    • modifiers:空格分割的修饰符列表,如"Shift"

    • repeat:连续按了这个键多少次

  • FireFox传入“KeyEvents”, 包含“initKeyEvent()” 方法

3、模拟其他事件

  • 传入"HTMLEvents",使用返回对象的initEvent()方法来初始化

4、自定义DOM事件

  • 传入createEvent("CustomEvent"),返回对象包含initCustomEvent()方法,接收的参数:

    • type:要触发的事件类型,如 myevent

    • bubbles :表示事件是否冒泡

    • cancelable:表示事件是否可以取消。

    • detail:任意值,作为event对象的detail属性。

2、IE事件模拟

  • createEventObject() 创建event对象
  • 手动设置event具备的属性
  • 事件目标上调用fireEvent()方法,接收参数
    • 事件处理程序的名字
    • event对象