0%

JavaScript学习笔记(13)

《JavaScript高级程序设计》第十三章,事件。


事件是文档/浏览器窗口发生的一些特定的交互瞬间,JavaScript和HTML之间的交互就是通过事件实现的。

事件流

事件流指的是从页面中接收事件的顺序。IE的是事件冒泡流(event bubbling),Netscape的则是事件捕获流(event capturing)。

  • 事件冒泡:事件由最具体的元素先接收,再逐级上传到较为不具体的元素。
  • 事件捕获:事件由不太具体的元素先接收,最具体的节点最后接收到事件。

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

要注意的是,实际目标在捕获阶段不会接收到事件,“事件在目标上发生”在事件处理中被视为冒泡阶段的一部分。不过各个浏览器都会在捕获阶段触发目标上的事件,结果是有两个机会在目标对象上操作事件。

事件处理程序

事件是用户或浏览器自身执行的某种动作,如clickload等;响应某个事件的函数就叫事件处理程序,如onclickonload。为事件指定处理程序的方式有好几种。

HTML handler

某个元素支持的每种事件,都可以用相应事件处理程序同名的属性来指定,属性值就是期待在事件发生时执行的JavaScript代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- Example 1 -->
<input type="button" value="click me" onclick="alert('Clicked')"/>

<!-- Example 2 -->
<script type="text/javascript">
function showMessage() {
alert("Hello World!");
}
</script>
<input type="button" value="click me" onclick="showMessage()"/>

<!-- Example 3 -->
<script src="functions.js"></script>
<input type="button" value="click me" onclick="funcFromFunctions()"/>

这里,首先会创建一个含有局部变量event(也就是事件对象)的函数,通过它可以访问各种元素属性,这个函数里的this为事件的目标元素。

1
2
3
4
5
<!-- 输出"click" -->
<input type="button" value="click me" onclick="alert(event.type)">

<!-- 输出"click me" -->
<input type="button" value="click me" onclick="alert(this.value)">

这个动态创建的函数用with来扩展作用域,如下。在函数内部,可以像访问局部变量那样访问document和该元素本身的成员。

1
2
3
4
5
6
7
function() {
with(document) {
with(this) {
// 元素属性值
}
}
}

因此,要访问自己的属性就简单很多了,如下。

1
2
<!-- 输出"click me" -->
<input type="button" value="click me" onclick="alert(value)">

实际上,这样扩展作用域的方式,无非就是想让事件处理程序无需引用表单元素就能访问其他表单字段。例子如下,这里是可以直接引用username元素的。

1
2
3
4
<form>
<input type="text" name="username" value=""/>
<input type="button" value="Echo Username" onclick="alert(username.value)"/>
</form>

总结一下,在HTML中指定事件处理程序有两个缺点:

  • 时差问题:事件触发时,事件处理程序可能还不具备执行条件,会触发错误。
  • 扩展作用域链问题:这样扩展事件处理程序的作用域链,在不同浏览器中会导致不同的结果,不同的JavaScript引擎遵循的标识符解析规则略有差异,可能会在访问非限定对象成员时出错。
  • 耦合问题:HTML和JavaScript代码耦合,不方便。

DOM0 level handler

DOM0级的设置方式就是,将一个函数赋值给一个事件处理程序属性;取消设置的方式是,将其设置为null。这个函数中的this引用的是当前元素。以这种方式添加的事件处理程序会在事件流的冒泡阶段被处理。

1
2
3
4
5
6
let btn = document.getElementById("myBtn");
btn.onclick = function() {
alert("Clicked");
alert(this.id); // "myBtn"
}
btn.onclick = null;

DOM2 level handler

DOM2级事件定义了两个操作:addEventListener()removeEventListener()。俩方法都接收3个参数:要处理的事件名、作为事件处理程序的函数,布尔值(true表示在捕获阶段调用处理程序;false表示在冒泡阶段调用处理程序)。

1
2
3
4
5
6
7
let btn = document.getElementById("myBtn");
let handler = function() {
alert("Clicked");
alert(this.id);
}
btn.addEventListener("click", handler, false);
btn.removeEventListener("click", handler, false);

使用这种方式的好处是可以添加多个事件处理程序,它们会按照定义的顺序依次执行。不过要注意的是,如果添加的事件处理函数是匿名函数,那就没有办法通过removeEventListener()移除了。

大多数情况下,都会将事件处理程序添加到事件流的冒泡阶段,这样可以最大限度地兼容各种浏览器,不建议在捕获阶段注册事件处理程序。

IE handler

IE里实现了与DOM类似的两个方法:attachEvent()detachEvent()。俩方法都接收2个参数:事件处理程序名称、事件处理程序函数。这里添加的事件处理程序都会被添加到冒泡阶段。值得注意的是,这里的事件处理程序会在全局作用域运行,而非其所属元素作用域内运行。

1
2
3
4
5
let btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function() {
alert("Clicked");
alert(this === window); // true
});

这里也可以添加多个事件处理程序,不过执行顺序和它们定义的顺序相反,也就是后定义的handler会先被触发。

事件对象

在触发事件时,会产生一个事件对象event,它包含了所有与事件有关的信息。

DOM中的事件对象

无论以怎样的方法去定义事件处理程序,浏览器都会传入一个event对象。不同的事件有不同的属性和方法,不过有一些是通用的:

  • type:事件类型
  • target:事件的目标
  • currentTarget:其事件处理程序当前正在处理事件的那个元素
  • bubbles:事件是否冒泡
  • cancelable:事件的默认行为可否取消
  • defaultPrevented:为true表示调用了preventDefault()
  • trusted:为true表示事件由浏览器生成,为false表示事件由开发人员通过JavaScript创建
  • detail:事件相关细节
  • eventPhase:调用事件处理程序的阶段,1为捕获阶段、2为处于目标、3为冒泡阶段
  • view:与事件关联的抽象视图,等同于发生事件的window对象(?)
  • preventDefault():取消事件默认行为,当cancelabletrue时可用
  • stopPropagation():取消事件的进一步捕获/冒泡,允许当前节点的其他事件监听函数执行,当bubblestrue时可用
  • stopImmediatePropagation():取消事件的进一步捕获/冒泡,同时阻止任何当前节点的其他事件监听函数被调用

区分thiscurrentTargettargetthis始终为currentTarget的值,为被指定handler的元素;target则为包含事件的实际目标。当直接把handler指定给目标元素时,三者包含相同的值;当把handler指定给目标元素的父节点时 ,三者的值不相同。

1
2
3
4
5
6
7
8
9
10
11
let btn = document.getElementById("myBtn");

btn.onclick = function(event) {
alert(event.currentTarget === btn); // true
alert(event.target === btn); // true
}

document.body.onclick = function(event) {
alert(event.currentTarget === document.body); // true
alert(event.target === btn); // true
}

stopPropagation()可以用来停止事件在DOM层次的传播,例子如下。这里如果不调用stopPropagation()会在点击按钮时出现两个警告框,现在click事件不会传播到document.body上,因此不会触发注册在这个元素上的onclick事件处理程序。(这里如果用stopImmediatePropagation()那么就不会弹出任何警告框)。

1
2
3
4
5
6
7
8
9
10
let btn = document.getElementById("myBtn");

btn.onclick = function(event) {
alert("Clicked");
event.stopPropagation();
}

document.body.onclick = function(event) {
alert("Body clicked");
}

* 只有在事件处理程序执行期间,event对象才会存在;一旦事件处理程序执行完成,event对象就会被销毁。

IE中的事件对象

在IE中,用不同方法去定义事件处理程序,那么访问event对象的方式也不同。当用DOM0级方法添加时,event对象是作为window对象的一个属性存在的;当在HTML中/用DOM1级方法添加时,event对象是直接作为参数被传入的,可以直接访问。这里的event对象有以下通用的属性/方法。

  • cancelBubble:默认值false,将其设置为true时可以取消事件冒泡,和DOM中的stopPropagation()作用相同
  • returnValue:默认值true,将其设置为true时可以取消事件的默认行为,和DOM中的preventDefault()作用相同
  • srcElement:事件的目标,和DOM中的target相同
  • type:事件类型

事件类型

下面就分别介绍几类事件。

UI事件

指的是那些不一定与用户操作有关的事件,有下面这么些事件。

  • DOMActivate:表示元素已经被用户操作激活(通过鼠标或键盘),这个事件在DOM3已被废弃,不过还有浏览器在支持它,不建议使用此事件
  • load:当页面加载完时在window上触发、当所有框架加载完时在框架集上触发、当图像加载完时在<img>上触发、当嵌入的内容加载完时在<object>上触发
  • unload:卸载完触发,和上面类似
  • abort:在用户停止下载时,如果嵌入的内容没加载完,则在<object>上触发
  • error:发生错误时触发
  • select:当用户选择文本框(<input><textarea>)中的字符时触发
  • resize:当窗口或框架的大小变化时在window或框架上触发
  • scroll:当滚动带滚动条的元素时,在该元素上触发

除了第一个,其他事件都在DOM2中归为HTML事件。要确定浏览器是否支持,可以像下面这样:

1
let isSupported = ducument.implementation.hasFeature("HTMLEvents", "2.0");

load事件

先举个文档上触发load事件的例子。

1
window.addEventListener("load", function() { ... });
1
2
3
4
5
6
7
8
<!DOCTYPE html>
<html>
<head>
<title>Example</title>
</head>
<body onload="alert('Loaded')">
</body>
</html>

值得一提的是,在window上发生的任何事件都可以在<body>中通过相应特性设置,但这只是一种向后兼容,因为无法在HTML中访问window元素。建议是在document而非window上触发load事件。

再举个图片上触发load事件的例子。譬如想创建新的<img>元素,在图片加载完毕后给出提示。

1
2
3
4
5
6
7
8
window.addEventListener("load", function() {
let image = document.createElement("img");
image.addEventListener("load", function() {
alert("Image loaded");
});
document.body.appendChild(image);
image.src = "smile.gif";
});

这个例子中,首先,我们把代码放到了windowonload中,因为我们必须在添加新元素之前确定页面已经加载完毕,否则去操作document.body会导致错误。然后,我们创建了新元素、设置事件处理程序、将其添加到页面中、为其设置src。这里要注意需要在指定src前指定事件。新的图像元素并不是在添加到文档后才开始下载,只要设置了src属性后就会开始下载。

<script>元素/<link>元素也支持load事件,不过只有在设置了<script>元素的src属性/<link>元素的href属性并将该元素添加到文档后,才会开始下载JavaScript/CSS文件,所以这里就无所谓“指定事件处理程序”和“设置src”的顺序了。

unload事件

在文档被完全卸载后触发,通常的使用场景是清除引用,以避免内存泄漏。要注意的是,由于其在一切都被卸载之后才触发,那么之前加载存在的对象此时就不一定存在了,此时操作DOM节点或元素的样式就会导致错误。

resize事件

当浏览器窗口被调整到新的高度或宽度时会触发。要注意的是不同浏览器有不同的触发时机,比如IE、Safari、Chrome和Opera会在窗口变化1px的时候触发,然后随着变化不断重复触发;Firefox则只会在用户停止调整窗口大小时才会触发。前一情况下,应注意不要在handler里加太多计算,因为可能会频繁执行,导致浏览器卡顿。

scroll事件

虽然scroll事件是在window对象上发生的,但它实际表示的则是页面中相应元素的变化。混杂模式下可通过document.bodyscrollTopscrollLeft监控到变化;标准模式下则通过document.documentElement(即<html>元素)的scrollTopscrollLeft来反映变化。

焦点事件

有这么些:

  • focus:在元素获得焦点时触发,不冒泡
  • blur:在元素失去焦点时触发,不冒泡
  • focusin:在元素获得焦点时触发,和focus等价,会冒泡
  • focusout:在元素失去焦点时触发,blur的通用版本
  • DOMFocusIn:在元素获得焦点时触发,和focus等价,会冒泡;只有Opera支持此事件
  • DOMFocusOut:在元素失去焦点时触发,blur的通用版本;只有Opera支持此事件

当焦点从页面中的一个元素移动到另一个元素,会依次触发以下事件:

  1. 失去焦点的元素上focusout
  2. 获得焦点的元素上focusin
  3. 失去焦点的元素上blur
  4. 失去焦点的元素上DOMFocusOut
  5. 获得焦点的元素上focus
  6. 获得焦点的元素上DOMFocusIn

鼠标与滚轮事件

有这么些:

  • click:单击鼠标/回车时
  • dbclick:双击鼠标
  • mousedown:按下鼠标
  • mouseup:释放鼠标
  • mouseenter:鼠标移入某元素时触发;不冒泡;光标移动到后代元素上不会触发
  • mouseleave:鼠标移出某元素时触发;不冒泡;光标激动到后代元素上不会触发
  • mouseover:鼠标移入某元素时触发;移入和移出其子元素时也会触发
  • mouseout:鼠标移出某元素时触发;移入和移出其子元素时也会触发
  • mousemove:鼠标在元素内部移动时重复触发
  • mousewheel:滚动鼠标滚轮时触发

* enter和over的区别:参考

所有元素都支持鼠标事件;除了enterleave,其他事件都会冒泡、可以取消。

只有在同一个元素上相继触发downup才能触发click;只有触发两次click才能触发dbclick。也就是:

  1. mousedown
  2. mouseup
  3. click
  4. mousedown
  5. mouseup
  6. click
  7. dbclick

下面介绍一下鼠标相关的信息。

坐标

坐标原点都是左上角。

  • 客户区坐标
    鼠标事件的event包含两个属性:clientXclientY,分别表示事件发生时鼠标指针在视口中的水平和垂直坐标,此位置不包括页面滚动的距离。

  • 页面坐标
    鼠标事件的event包含两个属性:pageXpageY,分别表示事件发生时鼠标指针在页面中的水平和垂直坐标,此位置包括页面滚动的距离。

  • 屏幕坐标
    鼠标事件的event包含两个属性:screenXscreenY,分别表示事件发生时鼠标指针相对于整个屏幕的坐标。

修改键

因为鼠标事件触发的同时,有可能同时会按着键盘某些键,它会影响鼠标事件的行为。这些修改键包括:ShiftCtrlAltMeta(Windows的Windows键和Mac的Cmd键)。DOM为鼠标事件event对象定义了几个属性:shiftKeyctrlKeyaltKeymetaKey,当相应键被按下时,该属性值为true

鼠标按钮

针对mousedownmouseup事件。它的event上存在一个button属性,表示按下或释放的按钮,该值为0表示主鼠标按钮、为1表示中间鼠标按钮、为2表示次鼠标按钮。

鼠标滚轮

针对mousewheel事件。此事件可在任何元素上触发,最终会冒泡到documentwindow上。此事件对应的eventwheelDelta属性,当用户向上滚动滚轮时,它是120的倍数;当用户向下滚动滚轮时,它是-120的倍数。不过不同浏览器有不同的实现,不赘述。

相关元素

只有mouseovermouseout事件才有relatedTarget,其他事件的该值均为null

  • mouseover
    relatedTarget —> target(如上图的div)
  • mouseout
    target(如上图的div) —> relatedTarget

其他信息

event对象还有一个detail属性,对于鼠标事件,该值为一个数组,表示在给定位置上发生的单击次数。相继发生一个mousedown和一个mouseup算一次单击,累计递增。如果鼠标在mousedownmouseup之间移动了位置,则detail会被重置为0

* 需要注意无障碍性的问题,因为这些鼠标事件只有click可以由键盘触发,是对屏幕阅读器友好的。

键盘与文本事件

有3个键盘事件:

  • keydown:按任意键时触发,如果按住不放则重复触发
  • keypress:按字符键时触发,如果按住不放则重复触发
  • keyup:释放键时触发

有1个文本事件:

  • textInput:对keypress的补充,用意是将文本显示给用户之前更容易拦截文本;在文本插入文本框之前触发

当用户按下任意键,触发顺序为:keydown(如果按住不放则* n) -> keyup
当用户按下字符键,触发顺序为:keydown -> keypress(如果按住不放则一起* n) -> keyup

键码

针对keydownkeyup事件,eventkeyCode属性,表示与键盘特定键对应的代码。对于数字/字母键,键码为ASCII码中对应的数字/小写字母编码,比如7keyCode55AkeyCode65。稍微举几个例子:

  • Backspace:8
  • Tab:9
  • Enter:13
  • Shift:16

字符编码

针对keypress事件,eventcharCode属性,表示所按键代表字符的ASCII编码,此时的keyCode通常等于0或者等于所按键的键码。取得了字符编码后可以用String.fromCharCode()将其转换为实际字符。

DOM3级变化

DOM3级事件中的键盘事件不再含charCode属性,而是包含两个新的属性:keychar。其中key是为了取代keyCode而新增的,为键对应的文本,如按键K的为"K"、按键Shift的为"Shift";而char属性在按下字符键时行为与key相同,在按下非字符键时值为null

DOM3级事件还添加了一个location属性,表示按下什么位置上的键,0表示默认键盘、1表示左侧位置、2表示右侧位置、3表示数字小键盘、4表示移动设备键盘、5表示手柄。

DOM3级事件还添加了getModifierState()方法。此方法接收一个等于ShiftControlAltGraphMeta的字符串,表示要检测的修改键,当该键是活动的(即被按下的),返回true,反之返回false

不过keycharkeyIdentifierlocationkeyLocationgetModifierState()都存在跨浏览器问题。

textInput事件

只有在可编辑区域中按下能够输入实际字符的键时,才能够触发此事件(比如退格键就不能触发textInput但能触发keypress)。此事件的event对象含一个data属性,值为用户输入的字符(而非字符编码);还包含一个inputMethod属性,表示把文本输入到文本框中的方式,举例如下:

  • 0:表示浏览器不确定是怎么输入的
  • 1:表示使用键盘输入的
  • 2:表示文本是粘贴进来的
  • 3:表示文本时拖放进来的

复合事件

这个事件用于处理IME(Input Method Editor,输入法编辑器)的输入序列,可以让用户输入物理键盘上找不到的字符,比如使用拉丁文键盘的用户通过IME可以输入日文。一般会同时按住多个键,但最终只输入一个字符。有这么几个复合事件,以及对应的event.data值:

  • compositionstart
    • 在IME的文本复合系统打开时触发,表示要开始输入了
    • 此时data包含正在编辑的文本(如已经选中的需要马上替换的文本)
  • compositionupdate
    • 向输入字段中插入新字符时触发
    • 此时data包含正插入的新字符
  • compositionend
    • 在IME的文本复合系统关闭时触发,表示返回正常键盘输入状态
    • 此时data包含此次输入会话中插入的所有字符

此事件缺少浏览器支持。

变动事件

指DOM变化。

  • DOMSubtreeModified:DOM结构发生任何变化时触发,此事件在其他任何事件触发之后都会触发,会冒泡
  • DOMNodeInserted:插入节点时触发,会冒泡
  • DOMNodeRemoved:移除节点时触发,会冒泡
  • DOMNodeInsertedIntoDocument:直接向document插入节点时触发,此事件在DOMNodeInserted之后触发,不冒泡
  • DOMRemovedFromDocument:直接从document删除节点时触发,此事件在DOMNodeRemoved之后触发,不冒泡
  • DOMAttrModified:特性被修改后触发
  • DOMCharacterDataModified:文本节点的值发生变化时触发

HTML5事件

HTML5定义了浏览器应该支持的所有事件,这里我们举几个例子。

contextmenu事件

就是单击鼠标右键调出上下文菜单这个事件(Windows中是右键单击,Mac中是Ctrl+单击),此事件冒泡(所以可以把handler挂在document上)。
如果想改变浏览器的默认行为,通常会用contextmenu事件来显示自定义的上下文菜单,用click事件来隐藏该菜单。

beforeunload事件

这个事件在浏览器卸载页面之前触发,通常用于让用户确认是否离开页面。譬如退出一些系统的时候,会弹框告知用户页面将会被卸载,询问用户是否真的要关闭页面,还是希望继续留下来。

DOMContentLoaded事件

load事件会在页面中一切都加载完毕后触发,DOMContentLoaded事件则是在DOM树构建完成后触发,不管图片、JavaScript文件、CSS文件等资源是否下载完毕。

readystatechange事件

此事件的目的是提供与文档或元素的加载状态有关的信息。支持此事件的每一个对象都有一个readyState属性,有下面5种取值:

  • uninitialized:对象存在但尚未初始化
  • loading:对象正在加载数据
  • loaded:对象加载数据完成
  • interactive:可以操作对象了,但还没完全加载
  • complete:对象已加载完毕

不过对于不同的对象来说,这5个状态并不是都要经历的,也不总是连续的,而且对于资源数不同的文档,这5个状态有可能并不是按顺序的。譬如当页面包含外部资源多时,interactive会在complete之前出现;而包含外部资源少时,这个顺序可能会互换。

pageshow和pagehide事件

先要说明一个情况,就是当页面出现的时候不一定会触发load事件,因为浏览器里有往返内存(back-forward cachebfcache),也就是浏览器中前进或后退进入的页面存在的地方。

pageshow事件就是在页面显示时触发,不管这个页面是否来自bfcache。对于重新加载的页面,pageshow会在load之后触发;对于来自bfcache的事件,pageshow会在页面状态完全恢复的时候触发。

pagehide事件则与其相对,在浏览器卸载页面时触发,且在unload事件之前触发。

这俩事件的共性:

  • 虽然目标为document,但须将其handler挂在window
  • 都有一个persisted属性,当页面已经/将要存在于bfcache时,该值为true,否则为false

hashchange事件

很明显啦,就是URL中的hash值(#)改变时触发。此事件的handler必须挂给window,它的event有两个属性:oldURLnewURL。不过有些浏览器不支持这俩属性,这时候就要用location.hash来获取了。

设备事件

简要列举一下。

  • orientationonchange事件
    iOS手机竖屏和横屏(各两个方向)转换,此时可通过window.orientation访问到查看模式(0表示竖屏、90表示左转横屏、-90表示右转横屏、180表示倒的竖屏)。

  • MozOrientation事件
    当设备加速器检测到设备方向改变时触发,含有xyz三个属性。静止时x=0, y=0, z=1;设备向左倾斜时x值减小;设备向右倾斜时x值增大;设备向远离用户方向倾斜y值减小;设备向靠近用户方向倾斜y值增大;垂直方向上,静止时z值为1,移动时z值减小,失重时z值为0

  • deviceorientation事件
    当设备加速器检测到设备方向改变时触发,不过目的是告诉开发人员设备在空间中朝向哪儿,而不是如何移动。它把空间分为三个轴(x从左往右、y从下往上、z从后往前),有5个属性:alphabetagammaabsolutecompassCalibrated,不详细说了。

  • devicemotion事件
    此事件目的是告诉开发人员设备什么时候移动,而不是设备方向如何改变。通过它可以检测设备是否正在坠落、是否被走着的人拿在手里。它有4个属性:acceleration表示不考虑重力情况下三个轴方向的加速度、accelerationIncludingGravity表示考虑重力情况下三个轴方向的加速度、interval表示时间值、rotationRate表示方向。

触摸与手势事件

触摸事件

  • touchstart:手指触摸屏幕时触发,即使已有一个手指在屏幕上时也会触发
  • touchmove:手指在屏幕上滑动时连续触发
  • touchend:手指离开屏幕时触发
  • touchcancel:系统停止跟踪触摸时触发

上面几个事件都冒泡,都可以取消,event对象都有这么些属性:bubblescancelableviewclientXclientYscreenXscreenYdetailaltKeyshiftKeyctrlKeymetaKeytouches(表示当前跟踪的触摸操作的Touch对象数组)、targetTouches(特定于事件目标的Touch对象数组)、changedTouches(自上次触摸以来发生的改变的Touch对象数组)。

每个Touch对象包含以下属性:

  • clientXclientY:触摸目标在视口中的坐标
  • pageXpageY:触摸目标在页面中的坐标
  • screenXscreenY:触摸目标在屏幕中的坐标
  • target:触摸的DOM节点目标
  • identifier:标识触摸的唯一id

当触摸屏幕上的元素时,事件发生顺序如下:touchstartmouseovermousemove(一次)、mousedownmouseupclicktouchend

手势事件

当两个手指触摸屏幕时就会产生手势。

  • gesturestart:一个手指已经按在屏幕上,另一个手指又触摸屏幕时触发
  • gesturechange:屏幕上任一个手指位置发生变化时触发
  • gestureend:屏幕上任一个手指离开时触发

上面几个事件都冒泡,每个手势事件event包含的属性除上述外,还有额外的:rotation表示手指变化引起的旋转角度、scale表示两个手指见距离的变化情况。

内存和性能

当事件处理程序变多的时候,性能会下降。一是函数占内存,二是指定事件处理程序时候访问DOM也会有一定耗时。因此我们需要想一些解决办法。

  • 事件委托
    这个方法利用了事件冒泡,将handle挂给高一点的层级,这样就可以只给少数几个元素添加,而不用给每个元素都添加。有几个适合使用事件委托的事件:clickmousedownmouseupkeydownkeyupkeypress

  • 移除handler
    在删除一些元素或者卸载页面的时候,可以注意下先移除handler

模拟事件

有DOM规范的方式,大多数浏览器都支持,除了IE有它自己模拟的方式,这里就只记录DOM规范上的。

步骤

document.createEvent()来创建event对象,接收一个表示要创建的事件类型的字符串作为参数。在DOM2里这些字符串是复数,在DOM3里则是单数;DOM2中没专门规定键盘事件,DOM3中才有。此字符串可以是以下之一:UIEvent(s)MouseEvent(s)MutationEvent(s)HTMLEvents

创建之后就是初始化了,每种类型的事件有自己不同的方法,不过都是将与事件相关的信息作为参数传进去。

最后一步则是触发,使用的方法为dispatchEvent(),要向其中传入要触发的event对象。

模拟鼠标事件

document.createEvent("MouseEvents")会返回一个鼠标事件,其方法initMouseEvent()用来初始化,接收15个参数(按顺序):

  • type:要触发的事件类型,如"click"
  • bubbles:事件是否冒泡,若要精确模拟应该用true
  • cancelable:事件是否可以取消,若要精确模拟应该用true
  • view:事件关联的视图,几乎总是使用document.defaultView
  • detail:事件的详细信息,整数,通常设置为0
  • screenXscreenYclientXclientY:事件坐标,整数
  • ctrlKeyaltKeyshiftKeymetaKey:是否按键,默认false
  • button:表示按下了哪一个鼠标键,默认0
  • relatedTarget:与事件相关的对象,只在mouseovermouseout使用

模拟键盘事件

document.createEvent("KeyboardEvent")会返回一个键盘事件,其方法initKeyEvent()用来初始化,接收8个参数:

  • type:要触发的事件类型,如"keydown"
  • bubbles:事件是否冒泡,若要精确模拟应该用true
  • cancelable:事件是否可以取消,若要精确模拟应该用true
  • view:事件关联的视图,几乎总是使用document.defaultView
  • key:按下的键的键码
  • location:按下的键在哪一块儿(0默认主键盘、1表示左、2表示右、3数字键盘、4虚拟键盘、5手柄)
  • modifiers:空格分隔的修改键列表,如"Shift"
  • repeat:在一行中按了这个键多少次

模拟变动事件

document.createEvent("MutationEvents")会返回一个变动事件,其方法initMutationEvent()用来初始化,接收8个参数:typebubblescancelablerelatedNodepreValuenewValueattrNameattrChange

模拟HTML事件

document.createEvent("HTMLEvents")会返回一个HTML事件,其方法initEvent()用来初始化,接收3个参数:typebubblescancelable

自定义事件

目的是让开发人员创建自己的事件。用document.createEvent("CustomEvent")会返回一个自定义事件,其方法initCustomEvent()用来初始化,接收4个参数:typebubblescancelabledetail