JavaScript第十三章(2)

275 阅读21分钟

4 事件类型

Web 浏览器中可能发生的事件有很多类型。如前所述,不同的事件类型具有不同的信息,而“DOM3 级事件”规定了以下几类事件。

  • UI(User Interface,用户界面)事件,当用户与页面上的元素交互时触发;
  • 焦点事件,当元素获得或失去焦点时触发;
  • 鼠标事件,当用户通过鼠标在页面上执行操作时触发;
  • 滚轮事件,当使用鼠标滚轮(或类似设备)时触发;
  • 文本事件,当在文档中输入文本时触发;
  • 键盘事件,当用户通过键盘在页面上执行操作时触发;
  • 合成事件,当为 IME(Input Method Editor,输入法编辑器)输入字符时触发;
  • 变动(mutation)事件,当底层 DOM结构发生变化时触发。
  • 变动名称事件,当元素或属性名变动时触发。此类事件已经被废弃,没有任何浏览器实现它们, 因此本章不做介绍。

除了这几类事件之外,HTML5也定义了一组事件,而有些浏览器还会在 DOM和 BOM中实现其他 专有事件。这些专有的事件一般都是根据开发人员需求定制的,没有什么规范,因此不同浏览器的实现 有可能不一致。

DOM3 级事件模块在 DOM2 级事件模块基础上重新定义了这些事件,也添加了一些新事件。包括 IE9在内的所有主流浏览器都支持 DOM2级事件。IE9也支持 DOM3级事件。

4.1 UI事件

UI事件指的是那些不一定与用户操作有关的事件。这些事件在 DOM规范出现之前,都是以这种或 那种形式存在的,而在 DOM规范中保留是为了向后兼容。现有的 UI事件如下。

  • DOMActivate:表示元素已经被用户操作(通过鼠标或键盘)激活。这个事件在 DOM3 级事 件中被废弃,但 Firefox 2+和 Chrome支持它。考虑到不同浏览器实现的差异,不建议使用这个 事件。
  • load:当页面完全加载后在 window 上面触发,当所有框架都加载完毕时在框架集上面触发, 当图像加载完毕时在元素上面触发,或者当嵌入的内容加载完毕时在元素上面 触发。
  • unload:当页面完全卸载后在 window 上面触发,当所有框架都卸载后在框架集上面触发,或 者当嵌入的内容卸载完毕后在元素上面触发。
  • abort:在用户停止下载过程时,如果嵌入的内容没有加载完,则在元素上面触发。
  • error:当发生 JavaScript错误时在 window 上面触发,当无法加载图像时在元素上面触 发,当无法加载嵌入内容时在元素上面触发,或者当有一或多个框架无法加载时在框 架集上面触发。第 17章将继续讨论这个事件。
  • select:当用户选择文本框(或)中的一或多个字符时触发。第 14章将 继续讨论这个事件。
  • resize:当窗口或框架的大小变化时在 window 或框架上面触发。
  • scroll:当用户滚动带滚动条的元素中的内容时,在该元素上面触发。元素中包含所加 载页面的滚动条。
    1. load 事件

    JavaScript 中常用的一个事件就是 load。当页面完全加载后(包括所有图像、JavaScript 文件、 CSS文件等外部资源),就会触发 window 上面的 load 事件。有两种定义 onload 事件处理程序的方式。 第一种方式是使用如下所示的 JavaScript代码:

    EventUtil.addHandler(window, "load", function(event){ 
    
        alert("Loaded!");  }); 
    

    第二种指定 onload 事件处理程序的方式是为元素添加一个 onload 特性

    还有一些元素也以非标准的方式支持 load 事件。在 IE9+、Firefox、Opera、Chrome和 Safari 3+及 更高版本中,script元素也会触发 load 事件,以便开发人员确定动态加载的 JavaScript文件是否加 载完毕。与图像不同,只有在设置了script元素的 src 属性并将该元素添加到文档后,才会开始下 载 JavaScript 文件。换句话说,对于script元素而言,指定 src 属性和指定事件处理程序的先后顺 序就不重要了。

    1. unload 事件

    与 load 事件对应的是 unload 事件,这个事件在文档被完全卸载后触发。只要用户从一个页面切 换到另一个页面,就会发生 unload 事件。而利用这个事件多的情况是清除引用,以避免内存泄漏。 与 load 事件类似,也有两种指定 onunload 事件处理程序的方式。

    指定事件处理程序的第二种方式,也是为元素添加一个特性(与 load 事件相似)

    无论使用哪种方式,都要小心编写 onunload 事件处理程序中的代码。既然 unload 事件是在一切 都被卸载之后才触发,那么在页面加载后存在的那些对象,此时就不一定存在了。此时,操作 DOM节 点或者元素的样式就会导致错误。

    1. resize 事件

    当浏览器窗口被调整到一个新的高度或宽度时,就会触发 resize 事件。这个事件在 window(窗 口)上面触发,因此可以通过 JavaScript或者元素中的 onresize 特性来指定事件处理程序

    1. scroll 事件

    虽然 scroll 事件是在 window 对象上发生的,但它实际表示的则是页面中相应元素的变化。在混 杂模式下,可以通过元素的 scrollLeft 和 scrollTop 来监控到这一变化;而在标准模式下, 除 Safari 之外的所有浏览器都会通过元素来反映这一变化(Safari 仍然基于跟踪滚动位 置)

    4.2 焦点事件

    焦点事件会在页面元素获得或失去焦点时触发。利用这些事件并与 document.hasFocus()方法及 document.activeElement 属性配合,可以知晓用户在页面上的行踪。有以下 6个焦点事件。

    • blur:在元素失去焦点时触发。这个事件不会冒泡;所有浏览器都支持它。
    • DOMFocusIn:在元素获得焦点时触发。这个事件与 HTML事件 focus 等价,但它冒泡。只有 Opera支持这个事件。DOM3级事件废弃了 DOMFocusIn,选择了 focusin。
    • DOMFocusOut:在元素失去焦点时触发。这个事件是 HTML事件 blur 的通用版本。只有 Opera 支持这个事件。DOM3级事件废弃了 DOMFocusOut,选择了 focusout。
    • focus:在元素获得焦点时触发。这个事件不会冒泡;所有浏览器都支持它。
    • focusin:在元素获得焦点时触发。这个事件与 HTML事件 focus 等价,但它冒泡。支持这个 事件的浏览器有 IE5.5+、Safari 5.1+、Opera 11.5+和 Chrome。
    • focusout:在元素失去焦点时触发。这个事件是 HTML事件 blur 的通用版本。支持这个事件 的浏览器有 IE5.5+、Safari 5.1+、Opera 11.5+和 Chrome。

    这一类事件中主要的两个是 focus 和 blur,它们都是 JavaScript早期就得到所有浏览器支持的 事件。这些事件的大问题是它们不冒泡。因此,IE的 focusin 和 focusout 与 Opera的 DOMFocusIn 和 DOMFocusOut才会发生重叠。IE的方式后被 DOM3级事件采纳为标准方式。 当焦点从页面中的一个元素移动到另一个元素,会依次触发下列事件:

    (1) focusout 在失去焦点的元素上触发;

    (2) focusin 在获得焦点的元素上触发;

    (3) blur 在失去焦点的元素上触发;

    (4) DOMFocusOut 在失去焦点的元素上触发;

    (5) focus 在获得焦点的元素上触发;

    (6) DOMFocusIn 在获得焦点的元素上触发。

    其中,blur、DOMFocusOut 和 focusout 的事件目标是失去焦点的元素;而 focus、DOMFocusIn 和 focusin 的事件目标是获得焦点的元素。

    4.3 鼠标与滚轮事件

    鼠标事件是 Web 开发中常用的一类事件,毕竟鼠标还是主要的定位设备。DOM3 级事件中定 义了 9个鼠标事件,简介如下。

    • click:在用户单击主鼠标按钮(一般是左边的按钮)或者按下回车键时触发。这一点对确保 易访问性很重要,意味着 onclick 事件处理程序既可以通过键盘也可以通过鼠标执行。
    • dblclick:在用户双击主鼠标按钮(一般是左边的按钮)时触发。从技术上说,这个事件并不 是 DOM2级事件规范中规定的,但鉴于它得到了广泛支持,所以 DOM3级事件将其纳入了标准。
    • mousedown:在用户按下了任意鼠标按钮时触发。不能通过键盘触发这个事件。
    • mouseenter:在鼠标光标从元素外部首次移动到元素范围之内时触发。这个事件不冒泡,而且 在光标移动到后代元素上不会触发。DOM2级事件并没有定义这个事件,但 DOM3级事件将它 纳入了规范。IE、Firefox 9+和 Opera支持这个事件。
    • mouseleave:在位于元素上方的鼠标光标移动到元素范围之外时触发。这个事件不冒泡,而且 在光标移动到后代元素上不会触发。DOM2级事件并没有定义这个事件,但 DOM3级事件将它 纳入了规范。IE、Firefox 9+和 Opera支持这个事件。
    • mousemove:当鼠标指针在元素内部移动时重复地触发。不能通过键盘触发这个事件。
    • mouseout:在鼠标指针位于一个元素上方,然后用户将其移入另一个元素时触发。又移入的另 一个元素可能位于前一个元素的外部,也可能是这个元素的子元素。不能通过键盘触发这个事件。
    • mouseover:在鼠标指针位于一个元素外部,然后用户将其首次移入另一个元素边界之内时触 发。不能通过键盘触发这个事件。
    • mouseup:在用户释放鼠标按钮时触发。不能通过键盘触发这个事件。

    页面上的所有元素都支持鼠标事件。除了 mouseenter 和 mouseleave,所有鼠标事件都会冒泡, 也可以被取消,而取消鼠标事件将会影响浏览器的默认行为。取消鼠标事件的默认行为还会影响其他事 件,因为鼠标事件与其他事件是密不可分的关系。

    只有在同一个元素上相继触发 mousedown 和 mouseup 事件,才会触发 click 事件;如果 mousedown 或 mouseup 中的一个被取消,就不会触发 click 事件。类似地,只有触发两次 click 事 件,才会触发一次dblclick事件。如果有代码阻止了连续两次触发click事件(可能是直接取消click 事件,也可能通过取消 mousedown 或 mouseup 间接实现),那么就不会触发 dblclick 事件了。这 4 个事件触发的顺序始终如下:

    (1) mousedown (2) mouseup(3) click(4) mousedown (5) mouseup(6) click (7) dblclick

    1. 客户区坐标位置

    2. 页面坐标位置

    3. 屏幕坐标位置

    4. 修改键

    5. 相关元素

    6. 鼠标按钮

    7. 更多的事件信息

    8. 鼠标滚轮事件

    9. 触摸设备

    4.4 键盘与文本事件

    用户在使用键盘时会触发键盘事件。“DOM2 级事件”初规定了键盘事件,但在终定稿之前又 删除了相应的内容。结果,对键盘事件的支持主要遵循的是 DOM0级。 “DOM3级事件”为键盘事件制定了规范,IE9率先完全实现了该规范。其他浏览器也在着手实现这 一标准,但仍然有很多遗留的问题。 有 3个键盘事件,简述如下。

    • keydown:当用户按下键盘上的任意键时触发,而且如果按住不放的话,会重复触发此事件。
    • keypress:当用户按下键盘上的字符键时触发,而且如果按住不放的话,会重复触发此事件。 按下 Esc键也会触发这个事件。Safari 3.1之前的版本也会在用户按下非字符键时触发 keypress 事件。
    • keyup:当用户释放键盘上的键时触发。

    只有一个文本事件:textInput。这个事件是对 keypress 的补充,用意是在将文本显示给用户之 前更容易拦截文本。在文本插入文本框之前会触发 textInput 事件。 在用户按了一下键盘上的字符键时,首先会触发 keydown 事件,然后紧跟着是 keypress 事件, 后会触发 keyup 事件。其中,keydown 和 keypress 都是在文本框发生变化之前被触发的;而 keyup 事件则是在文本框已经发生变化之后被触发的。如果用户按下了一个字符键不放,就会重复触发 keydown 和 keypress 事件,直到用户松开该键为止。 如果用户按下的是一个非字符键,那么首先会触发 keydown 事件,然后就是 keyup 事件。如果按 住这个非字符键不放,那么就会一直重复触发 keydown 事件,直到用户松开这个键,此时会触发 keyup 事件。 键

    1. 键码

    2. 字符编码

    3. DOM3级变化

    4. textInput 事件

    5. 设备中的键盘事件

    4.5 复合事件

    复合事件(composition event)是 DOM3级事件中新添加的一类事件,用于处理 IME 的输入序列。 IME(Input Method Editor,输入法编辑器)可以让用户输入在物理键盘上找不到的字符。例如,使用拉 丁文键盘的用户通过 IME 照样能输入日文字符。IME 通常需要同时按住多个键,但终只输入一个字 符。复合事件就是针对检测和处理这种输入而设计的。有以下三种复合事件。

    • compositionstart:在 IME的文本复合系统打开时触发,表示要开始输入了。

    • compositionupdate:在向输入字段中插入新字符时触发。

    • compositionend:在 IME的文本复合系统关闭时触发,表示返回正常键盘输入状态。 复合事件与文本事件在很多方面都很相似。

    在触发复合事件时,目标是接收文本的输入字段。但它 比文本事件的事件对象多一个属性 data,其中包含以下几个值中的一个:

    • 如果在 compositionstart 事件发生时访问,包含正在编辑的文本(例如,已经选中的需要马 上替换的文本);

    • 如果在 compositionupdate 事件发生时访问,包含正插入的新字符;

    • 如果在 compositionend 事件发生时访问,包含此次输入会话中插入的所有字符。

    4.6 变动事件

    DOM2级的变动(mutation)事件能在 DOM中的某一部分发生变化时给出提示。变动事件是为 XML 或 HTML DOM设计的,并不特定于某种语言。DOM2级定义了如下变动事件。

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

    由于 DOM3级事件模块作废了很多变动事件,所以本节只介绍那些将来仍然会得到支持的事件。

    1. 删除节点

    2. 插入节点

    4.7 HTML5事件

    1. contextmenu 事件

    2. beforeunload 事件

    3. DOMContentLoaded 事件

    4. readystatechange 事件

    5. pageshow 和 pagehide 事件

    6. hashchange 事件

    4.8 设备事件

    1. orientationchange 事件

    2. MozOrientation 事件

    3. deviceorientation 事件

    4. devicemotion 事件

    4.9 触摸与手势事件

    1. 触摸事件

    2. 手势事件

    5 内存和性能

    由于事件处理程序可以为现代 Web 应用程序提供交互能力,因此许多开发人员会不分青红皂白地 向页面中添加大量的处理程序。在创建 GUI的语言(如 C#)中,为 GUI中的每个按钮添加一个 onclick 事件处理程序是司空见惯的事,而且这样做也不会导致什么问题。可是在 JavaScript中,添加到页面上 的事件处理程序数量将直接关系到页面的整体运行性能。导致这一问题的原因是多方面的。首先,每个 函数都是对象,都会占用内存;内存中的对象越多,性能就越差。其次,必须事先指定所有事件处理程 序而导致的 DOM访问次数,会延迟整个页面的交互就绪时间。事实上,从如何利用好事件处理程序的 角度出发,还是有一些方法能够提升性能的

    5.1 事件委托

    对“事件处理程序过多”问题的解决方案就是事件委托。事件委托利用了事件冒泡,只指定一个事 件处理程序,就可以管理某一类型的所有事件。例如,click 事件会一直冒泡到 document 层次。也就 是说,我们可以为整个页面指定一个 onclick 事件处理程序,而不必给每个可单击的元素分别添加事 件处理程序。

    如果可行的话,也可以考虑为 document 对象添加一个事件处理程序,用以处理页面上发生的某种 特定类型的事件。这样做与采取传统的做法相比具有如下优点。

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

    适合采用事件委托技术的事件包括click、mousedown、mouseup、keydown、keyup 和keypress。 虽然 mouseover 和mouseout 事件也冒泡,但要适当处理它们并不容易,而且经常需要计算元素的位置。 (因为当鼠标从一个元素移到其子节点时,或者当鼠标移出该元素时,都会触发 mouseout 事件。)

    5.2 移除事件处理程序

    每当将事件处理程序指定给元素时,运行中的浏览器代码与支持页面交互的 JavaScript 代码之间就 会建立一个连接。这种连接越多,页面执行起来就越慢。如前所述,可以采用事件委托技术,限制建立 的连接数量。另外,在不需要的时候移除事件处理程序,也是解决这个问题的一种方案。内存中留有那 些过时不用的“空事件处理程序”(dangling event handler),也是造成 Web 应用程序内存与性能问题的 主要原因。

    在两种情况下,可能会造成上述问题。第一种情况就是从文档中移除带有事件处理程序的元素时。 这可能是通过纯粹的 DOM操作,例如使用 removeChild()和 replaceChild()方法,但更多地是发 生在使用 innerHTML 替换页面中某一部分的时候。如果带有事件处理程序的元素被 innerHTML 删除 了,那么原来添加到元素中的事件处理程序极有可能无法被当作垃圾回收

    导致“空事件处理程序”的另一种情况,就是卸载页面的时候。毫不奇怪,IE8 及更早版本在这种 情况下依然是问题多的浏览器,尽管其他浏览器或多或少也有类似的问题。如果在页面被卸载之前没 有清理干净事件处理程序,那它们就会滞留在内存中。每次加载完页面再卸载页面时(可能是在两个页 面间来回切换,也可以是单击了“刷新”按钮),内存中滞留的对象数目就会增加,因为事件处理程序 占用的内存并没有被释放。

    一般来说,好的做法是在页面卸载之前,先通过 onunload 事件处理程序移除所有事件处理程序。 在此,事件委托技术再次表现出它的优势——需要跟踪的事件处理程序越少,移除它们就越容易。对这 种类似撤销的操作,我们可以把它想象成:只要是通过 onload 事件处理程序添加的东西,后都要通 过 onunload 事件处理程序将它们移除。

    6 模拟事件

    事件,就是网页中某个特别值得关注的瞬间。事件经常由用户操作或通过其他浏览器功能来触发。 但很少有人知道,也可以使用 JavaScript 在任意时刻来触发特定的事件,而此时的事件就如同浏览器创 建的事件一样。也就是说,这些事件该冒泡还会冒泡,而且照样能够导致浏览器执行已经指定的处理它 们的事件处理程序。在测试 Web 应用程序,模拟触发事件是一种极其有用的技术。DOM2 级规范为此 规定了模拟特定事件的方式,IE9、Opera、Firefox、Chrome和 Safari都支持这种方式。IE有它自己模拟 事件的方式

    6.1 DOM中的事件模拟

    可以在 document 对象上使用 createEvent()方法创建 event 对象。这个方法接收一个参数,即 表示要创建的事件类型的字符串。在 DOM2 级中,所有这些字符串都使用英文复数形式,而在 DOM3级中都变成了单数。这个字符串可以是下列几字符串之一。

    • UIEvents:一般化的 UI事件。鼠标事件和键盘事件都继承自 UI事件。DOM3级中是 UIEvent。
    • MouseEvents:一般化的鼠标事件。DOM3级中是 MouseEvent。
    • MutationEvents:一般化的 DOM变动事件。DOM3级中是 MutationEvent。
    • HTMLEvents:一般化的 HTML事件。没有对应的 DOM3级事件(HTML事件被分散到其他类 别中)。

    要注意的是,“DOM2 级事件”并没有专门规定键盘事件,后来的“DOM3 级事件”中才正式将其 作为一种事件给出规定。IE9是目前唯一支持 DOM3级键盘事件的浏览器。不过,在其他浏览器中,在 现有方法的基础上,可以通过几种方式来模拟键盘事件。 在创建了 event 对象之后,还需要使用与事件有关的信息对其进行初始化。每种类型的 event 对 象都有一个特殊的方法,为它传入适当的数据就可以初始化该 event 对象。不同类型的这个方法的名 字也不相同,具体要取决于 createEvent()中使用的参数。

    1. 模拟鼠标事件

    2. 模拟键盘事件

    3. 模拟其他事件

    4. 自定义 DOM事件

    6.2 IE中的事件模拟

    在 IE8及之前版本中模拟事件与在 DOM中模拟事件的思路相似:先创建 event 对象,然后为其指 定相应的信息,然后再使用该对象来触发事件。当然,IE在实现每个步骤时都采用了不一样的方式。

    调用 document.createEventObject()方法可以在 IE中创建 event 对象。但与 DOM方式不同 的是,这个方法不接受参数,结果会返回一个通用的 event 对象。然后,你必须手工为这个对象添加 所有必要的信息(没有方法来辅助完成这一步骤)。后一步就是在目标上调用 fireEvent()方法,这 个方法接受两个参数:事件处理程序的名称和 event 对象。在调用 fireEvent()方法时,会自动为 event 对象添加 srcElement 和 type 属性;其他属性则都是必须通过手工添加的。换句话说,模拟任 何IE支持的事件都采用相同的模式。