事件处理

543 阅读15分钟

事件处理

事件就是 Web 浏览器通知应用程序发生了什么事情.

  • 事件类型 (event type) 是一个用来说明发生什么类型事件的字符串.

  • 事件目标 (event target) 是发生的事件或与之相关的对象.

  • 事件处理程序 (event handler) 或事件监听程序 (event listener) 是处理或响应事件的函数.

  • 事件对象 (event object) 是与特定事件相关且包含有关该事件详细信息的对象.事件对象作为参数传递给事件处理程序函数.

  • 事件传播 (event propagation) 是浏览器决定哪个对象触发其事件处理程序的过程.事件传播的另一种形式称为事件捕获 (event capturing),在容器元素上注册的特定处理程序有机会在事件传播到真实目标之前拦截(或 “捕获”).

事件类型 event type

事件大致可以分成几类:

  1. 依赖于设备的输入事件,比如鼠标和键盘.如: “mousedown”、“mousemove”、“mouseup”、“keydown”、“keypress”、“keyup” 这样的传统事件类型,也包括像 “touchmove”、“gesturechange”这样新的触摸事件类型.

  2. 独立于设备的输入事件,有些事件没有直接相关的特定输入设备.例如: click 事件表示激活了链接、按钮或其他文档元素,这通常是通过鼠标单机实现,但也能通过键盘或触摸感知设备上的手势来实现.

  3. 用户界面事件,通常出现在定义 Web 应用用户界面的 HTML 表单元素上.包括文本输入域获取键盘焦点的 focus 事件、用户改变表单元素显示值的 change 事件和用户单击表单中的“提交”按钮 submit 事件.

  4. 状态变化事件,有些事件不是由用户活动而是由网络或浏览器活动触发,用来表示某种声明周期或相关状态的变化.当文档加载时,在 window 对象上发生 load 事件.HTML5 离线 Web 应用 API 包括 online 和 offline 事件.用于读取用户选择本地文件的新 API 使用像 “loadstart”、“progress”、“loadend”事件来实现 I/O 过程的异步通知.

  5. 特定 API 事件, HTML5及相关规范定义的大量 Web API 都有自己的事件类型.拖放 API 定义了如 “dragstart”、“dragenter”、“dragover” 和 “drop”.HTML 的 和 定义了 “waiting”、“playing”、“seeking” 和 “volumechange” 等事件.

  6. 计时器和错误处理程序,例如计时器 (timer) 和错误处理程序 (error handler) 属于客户端 JavaScript 异步编程模型的部分,并有相似的事件.

传统事件类型

处理鼠标、键盘、HTML表单和 Window 对象的事件都是 Web 应用中最常用的,它们存在很长的时间并得到了广泛的支持.

表单事件

表单和超链接都是网页中最早支持脚本的元素.这就意味这表单事件是所有事件类型中最稳定且得到良好支持的那部分.

  • 当提交表单和重制表单时, 元素会分别触发 submitreset 事件.
  • 当用户和类按钮表单元素 (单选按钮和复选框) 交互时,它们会发生 click 事件.
  • 当用户输入文字、选择选项或选择复选框来改变相应表单元素的状态时,这些通常维护某种状态的表单元素会触发 change 事件.
  • 表单元素在得到和失去焦点时会分别触发 focusblur 事件.

Window 事件

window 事件是指事件的发生与浏览器窗口本身而非窗口中显示的任何特定文档内容相关.但是,这些事件中有一些会和文档元素上发生的事件同名.

  • load 事件,当文档和其所有外部资源(比如图片)完全加载并显示给用户时就会触发它.
  • unload 事件,与 load 事件相对,当用户离开当前文档转向其他文档时会触发它.
  • resize 和 scroll 事件,当用户调整浏览器窗口大小或滚动它时会触发它们.

鼠标事件

当用户在文档上移动或单击鼠标时都会产生鼠标事件.这些事件在鼠标指针所对应的最深嵌套元素上触发,但它们会冒泡知道文档最顶层.传递给鼠标事件处理程序的事件对象有属性集,它们描述了当事件发生时鼠标的位置和按键状态,也指明当前是否有任何辅助键按下.

  • mousemove 事件,用户每次移动或拖动鼠标时触发,这些事件的发生非常频繁,所以 mousemove 事件处理程序一定不能触发计算密集型任务.

  • mousedown 和 mouseup 事件,当用户按下或释放鼠标按键时触发.在 mousedown 和 mouseup 事件队列之后,浏览器也会触发 click 事件.

  • mouseover 和 mouseout 事件,当用户鼠标悬停在一个新元素或不再悬停在某个元素上时,会分别触发 mouseover 和 mouseout 事件.

  • dblclick 事件,用户在相当短的时间内连续两次单击鼠标按键会触发它.

  • contextmenu 事件,用户单击右键时,通常会显示上下文菜单(context menu).在显示之前,会触发 contextmenu 事件,所以取消这个事件就可以阻止菜单的显示.

  • mousewheel 事件(在 Firefox 中是 DOMMouseScroll事件),当用户滚动鼠标滚轮时会触发它,传递的事件对象属性指定滚轮转动的大小和方向.

键盘事件

当键盘聚焦到 Web 浏览器时,用户每次按下或释放键盘上的按键时都会产生事件.无论任何文档元素获取键盘焦点都会触发键盘事件,并且它们会冒泡到 Document 和 Window 对象.如果没有元素获取焦点,可以直接在文档上触发事件.传递的事件对象有 keyCode 属性,它指定按下或释放的键是哪个.

  • keydown 和 keyup 事件,按下或释放按键会触发它们.

  • keypress 事件,当 keydown 事件产生可打印字符时,在 keydown 和 keyup 之间会触发另一个 keypress 事件. keypress 是较高级的文本事件,其事件对象指定产生的字符而非按下的键.

HTML5 事件

HTML5 定义了大量新的 Web 应用 API,其中许多 API 都定义了事件.

例如 和 元素.这些元素有长长的事件列表,它们触发各种关于网络事件、数据缓冲状况和播放状态的通知: canplay、loadeddata、playing 等.

HTML5 的拖放 API 允许 JavaScript 引用参与基于操作系统的拖放操作,实现 Web 和 原生应用间的数据传输.共有7个事件类型. 案例: 拖拽本地图片到网站 tinyjpg.com/

HTML5 定义了历史管理机制,它允许 Web 应用同浏览器的返回和前进按钮交互.这个机制设计的事件是hashchangepopstate,它们在 Window 对象上触发而非任何单独的文档元素.

HTML5 为表单定义了大量的新特征,例如表单验证机制,当验证失败时在表单元素上会触发 invalid 事件.

HTML5 包含了对离线 Web 应用的支持,它们可以安装到本地应用缓存中,所以即使浏览器离线时它们依旧能运转.相关的两个最重要事件是 offline 和 online.

触摸屏和移动设备事件

在许多情况下,触摸屏事件映射到传统的事件类型(比如 click 和 scroll),但不是每次和触摸屏 UI 的交互都能仿效鼠标,也不是所有的触摸都可以当做鼠标事件处理. 手势事件是高级事件,用于通知已经翻译的手势.当手指触摸屏幕时会触发 touchstart 事件,当手指移动时会触发 touchmove 事件,而当手指离开屏幕时会触发 touchend 事件.不像鼠标事件,触摸事件并不直接报告触摸的坐标.相反,触摸事件传递的事件对象有一个 changedTouches 属性,该属性是一个累数组对象,其每个元素都描述触摸的位置.

注册事件处理程序

注册事件处理程序有两种基本方式:

  1. 给事件目标对象或文档元素设置属性.
  2. 将事件处理程序传递到给对象或元素的一个特定的注册处理程序方法.

对于通过方法调用的处理程序注册,有一个标准方法,命名为 addEventListener(),除 IE8 及以前版本之外,所有浏览器都支持这种方式,而 IE9 之前的版本支持的是一个叫 attachEvent() 的不同方法.

设置 JavaScript 对象属性为事件处理程序

注册事件处理程序最简单的方式就是通过设置事件目标的属性为所需事件处理程序函数.按照约定,事件处理程序属性的名字有 “on” 后面跟着事件名组成: onclick、onchange、onload、onmouseover等. 所有都是小写

例如:

  // 设置 Window 对象的 onload 属性为一个函数
  // 该函数是事件处理程序: 当文档加载完毕时调用它
  window.onload = function() {
    // 查找一个 form 元素
    var elt = document.getElementById("shipping_address");

    // 注册事件处理程序函数,在表单提交之前调用它
    elt.onsubmit = function() { return validate(this) }
  }

事件处理程序属性的缺点是其设计都是围绕着假设每个事件目标对于每种事件类型将最多只有一个处理程序.如果想编写能够在任意文档中都能使用的脚本库代码,更好的方式是使用一种不修改或覆盖任何已有注册处理程序的技术 (比如 addEventListener()).

设置 HTML 标签属性为事件处理程序

用于设置的文档元素事件处理程序属性也能换成对应 HTML 标签的属性.属性值是 JavaScript 代码字符串.这段代码应该是事件处理程序函数主体,而非完整的函数声明.

例如:

  <button onclick="alert('hello world')">打招呼</button>

如果 HTML 事件处理程序包含多条 JavaScript 语句,必须使用分号分隔这些语句或断开属性值使其跨多行.

客户端编程的通用风格是保持 HTML 内容和 JavaScript 行为分离,避免使用 HTML 事件处理程序属性,因为这些属性直接混合了 JavaScript 和 HTML.

addEventListener()

window 对象、document 对象和所有文档元素对象都定义了一个名叫 addEventListener() 的方法,使用这个方法可以为事件目标注册事件处理程序.

addEventListener() 方法接受三个参数.

第一个参数: 注册处理程序的事件类型,这个事件类型 (或名字) 是字符串,但它不应该包括用于设置事件处理程序属性的前缀 “on”.

第二个参数: 当指定类型的事件发生时应该调用的函数.

第三个参数: 是布尔值,通常情况下会给这个参数传递 false. 如果传递 true,那么函数将注册为捕获事件处理程序,并在事件不同的调度阶段调用.

分别用设置事件处理程序属性和事件处理程序传递给 addEventListener() 方法的不同:

  var btn = document.createElement("button");
  btn.textContent = "点击";
  // 通过元素对象的点击事件处理程序的属性注册事件处理程序
  btn.onclick = function() {
    alert("hello world");
  }
  // 将处理程序函数传递给元素对象 addEventListener() 方法注册事件处理程序
  btn.addEventListener("click", function() {
    alert("hello world");
  }, false);

用 “click” 作为第一个参数调用 addEventListener() 不会影响 onclick 属性的值.所以前面的代码,单击按钮会产生两个 alert() 警告对话框.

removeEventListener() 方法,从对象中删除事件处理程序函数,它常用于临时注册事件处理程序,然后不久就删除它,接受三个参数(同 addEventListener).

attachEvent()

IE 9 之前的 IE 不支持 addEventListener() 和 removeEventListener() 方法. IE 5 及以版本定义了类似的方法 attachEvent() 和 detachEvent().

attachEvent() 和 detachEvent() 方法的工作原理与 addEventListener() 和 removeEventListener() 类似,但有如下例外:

  • IE 事件模型不支持事件捕获,所以 attachEvent() 和 detachEvent() 要求只有两个参数:事件类型和处理程序函数.

  • IE 方法的第一个参数使用了带 “on” 前缀的事件处理程序属性名,而非没有前缀的事件类型.如: 当给 addEventListener() 传递 “click” 时,要给 attachEvent() 传递 “onclick”.

  • attachEvent() 允许相同的事件处理程序函数注册多次.当特定的事件类型发生时,注册函数的调用次数和注册次数一样.

事件处理程序的调用

一旦注册了事件处理程序,浏览器就会在指定对象上发生指定类型事件时自动调用它.本节会详细介绍事件处理程序的调用,说明事件处理程序的参数调用上下文(this值)调用作用域事件处理程序返回值的意义.

事件处理程序的参数

通常调用事件处理程序时把事件对象作为它们的一个参数(但有例外,在 IE 8 及以前版本中, 通过设置属性注册事件处理程序,当调用它们时并未传递事件对象.需要通过全局对象 window.event 来获得事件对象). 事件对象的属性提供了有关事件的详细信息.

事件处理程序的运行环境

  1. 当通过设置属性注册事件处理程序时,相当于在事件目标元素对象上添加了对应事件类型的方法.它将作为这个对象的方法来调用.这就是说,在事件处理程序内,this 关键字指的是事件目标.

  2. 当使用 addEventListener() 注册时,调用的处理程序也是使用事件目标作为它们的 this 值.

  3. 当使用 attachEvent() 注册的处理程序作为函数调用,它们的 this 值是全局 (window) 对象. 解决方案如下:

  function addEvent(target, type, handler) {
    if (target.addEventListener) {
      target.addEventListener(type, handler);
    } else {
      target.attachEvent("on" + type, function(event) {
        var event = event || window.event; // IE 8 及以前版本,通过 window.event 来获得事件对象
        return handler.call(target, event);
      })
    }
  }

事件处理程序的作用域

事件处理程序和所有的 JavaScript 函数一样,也拥有一样的作用域规则,但是通过 HTML 属性来注册事件处理程序比较特殊,因为历史原因,它们运行在一个修改后的作用域链中.

事件处理程序的返回值

通过设置对象属性或 HTML 属性注册事件处理程序的返回值有时是非常有意义的.返回 false 就是告诉浏览器不要执行这个事件的相关默认操作.例如:表单提交按钮的 onclick 事件处理程序能返回 false 阻止浏览器提交表单.或当用户输入不符合规则的字符, onkeypress 事件处理程序能通过返回 false 来过滤键盘输入.

addEventListener() 或 attachEvent() 注册事件处理程序,通过调用 preventDefault() 方法或设置事件对象的 returnValue 属性,取消浏览器触发相应事件的默认行为.

调用顺序

  • 通过设置对象属性或 HTML 属性注册的处理程序一直优先调用.
  • 使用 addEventListener() 注册的处理程序按照它们的注册顺序调用.
  • 使用 attachEvent() 注册的处理程序可能按照任何顺序调用,所以代码不应该依赖于调用顺序.

事件传播

在调用在目标元素上注册的事件处理函数后,大部分事件会“冒泡”到 DOM 树根.调用目标的父元素的事件处理程序,然后调用在目标的祖父元素上注册的事件处理程序.这会一直到 Document 对象,最后到达 Window 对象. 事件冒泡为在大量单独文档元素上注册处理程序提供了替代方案,在共同的祖先上注册一个处理程序来处理所有的事件.

focus、blur、scroll 事件不会冒泡.

文档元素上的 load 事件会冒泡到 Document 对象上停止冒泡而不会传播到 Window 对象上.

事件冒泡分三个阶段:

  1. 第一阶段: 事件捕获阶段,它像反冒泡一样.最先调用 Window 对象的捕获处理程序,然后是 Document 对象的捕获处理程序,接着是 body 对象,再然后是 DOM 树向下,以次类推,直到调用事件目标的父元素的捕获事件处理程序.在目标对象本身上注册的捕获事件处理程序不会被调用.事件捕获提供了在事件没有送达目标之前查看它们的机会.事件捕获用于程序调试.

  2. 第二阶段: 目标对象本身事件处理程序的调用

  3. 第三阶段: 事件冒泡

事件取消

事件取消分为: 取消事件浏览器默认操作和取消事件传播

  • 取消浏览器默认操作,在支持 addEventListener() 的浏览器中,也能通过调用事件对象的 preventDefault() 方法取消事件的默认操作.在 IE 9 之前的 IE 中,可以通过设置事件对象的 returnValue 属性为 false 来达到同样的效果.

  • 取消事件传播,在支持 addEventListener() 的浏览器中,可以调用事件对象的一个 stopPropagation() 方法以阻止事件的继续传播.stopPropagation() 方法可以在事件传播期间的任何时间调用,他能工作在捕获期阶段、事件目标本身中和冒泡阶段.IE 9 之前的 IE 不支持 stopPropagation() 方法,但 IE 事件对象有一个 cancelBubble 属性,设置这个属性为 true 能阻止事件进一步传播.

事件委托

事件委托可以解决事件处理程序过多的问题. 事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件.