事件模型
事件的本质是程序各个组成部分之间的一种通信方式,也是异步编程的一种实现。DOM 支持大量的事件,本章介绍 DOM 的事件编程。
EventTarget 接口
DOM 的事件操作(监听和触发),都定义在EventTarget
接口。所有节点对象都部署了这个接口,其他一些需要事件通信的浏览器内置对象(比如,XMLHttpRequest
、AudioNode
、AudioContext
)也部署了这个接口。
该接口主要提供三个实例方法。
addEventListener
:绑定事件的监听函数removeEventListener
:移除事件的监听函数dispatchEvent
:触发事件
EventTarget.addEventListener()
EventTarget.addEventListener()
用于在当前节点或对象上,定义一个特定事件的监听函数。一旦这个事件发生,就会执行监听函数。该方法没有返回值。
target.addEventListener(type, listener[, useCapture]);
该方法接受三个参数。
type
:事件名称,大小写敏感。listener
:监听函数。事件发生时,会调用该监听函数。useCapture
:布尔值,表示监听函数是否在捕获阶段(capture)触发(参见后文《事件的传播》部分),默认为false
(监听函数只在冒泡阶段被触发)。该参数可选。
下面是一个例子。
function hello() {
console.log('Hello world');
}
var button = document.getElementById('btn');
button.addEventListener('click', hello, false);
上面代码中,button
节点的addEventListener
方法绑定click
事件的监听函数hello
,该函数只在冒泡阶段触发。
关于参数,有两个地方需要注意。
首先,第二个参数除了监听函数,还可以是一个具有handleEvent
方法的对象。
buttonElement.addEventListener('click', {
handleEvent: function (event) {
console.log('click');
}
});
上面代码中,addEventListener
方法的第二个参数,就是一个具有handleEvent
方法的对象。
其次,第三个参数除了布尔值useCapture
,还可以是一个属性配置对象。该对象有以下属性。
capture
:布尔值,表示该事件是否在捕获阶段
触发监听函数。once
:布尔值,表示监听函数是否只触发一次,然后就自动移除。passive
:布尔值,表示监听函数不会调用事件的preventDefault
方法。如果监听函数调用了,浏览器将忽略这个要求,并在监控台输出一行警告。
addEventListener
方法可以为针对当前对象的同一个事件,添加多个不同的监听函数。这些函数按照添加顺序触发,即先添加先触发。如果为同一个事件多次添加同一个监听函数,该函数只会执行一次,多余的添加将自动被去除(不必使用removeEventListener
方法手动去除)。
function hello() {
console.log('Hello world');
}
document.addEventListener('click', hello, false);
document.addEventListener('click', hello, false);
执行上面代码,点击文档只会输出一行Hello world
。
如果希望向监听函数传递参数,可以用匿名函数包装一下监听函数。
function print(x) {
console.log(x);
}
var el = document.getElementById('div1');
el.addEventListener('click', function () { print('Hello'); }, false);
上面代码通过匿名函数,向监听函数print
传递了一个参数。
监听函数内部的this
,指向当前事件所在的那个对象。
// HTML 代码如下
// <p id="para">Hello</p>
var para = document.getElementById('para');
para.addEventListener('click', function (e) {
console.log(this.nodeName); // "P"
}, false);
上面代码中,监听函数内部的this
指向事件所在的对象para
。
EventTarget.removeEventListener()
EventTarget.removeEventListener
方法用来移除addEventListener
方法添加的事件监听函数。该方法没有返回值。
div.addEventListener('click', listener, false);
div.removeEventListener('click', listener, false);
removeEventListener
方法的参数,与addEventListener
方法完全一致。它的第一个参数“事件类型”,大小写敏感。
注意,removeEventListener
方法移除的监听函数,必须是addEventListener
方法添加的那个监听函数,而且必须在同一个元素节点,否则无效。
div.addEventListener('click', function (e) {}, false);
div.removeEventListener('click', function (e) {}, false);
上面代码中,removeEventListener
方法无效,因为监听函数不是同一个匿名函数。
element.addEventListener('mousedown', handleMouseDown, true);
element.removeEventListener("mousedown", handleMouseDown, false);
上面代码中,removeEventListener
方法也是无效的,因为第三个参数不一样。
EventTarget.dispatchEvent()
EventTarget.dispatchEvent
方法在当前节点上触发指定事件,从而触发监听函数的执行。该方法返回一个布尔值,只要有一个监听函数调用了Event.preventDefault()
,则返回值为false
,否则为true
。
target.dispatchEvent(event)
dispatchEvent
方法的参数是一个Event
对象的实例(详见《Event 对象》章节)。
para.addEventListener('click', hello, false);
var event = new Event('click');
para.dispatchEvent(event);
上面代码在当前节点触发了click
事件。
如果dispatchEvent
方法的参数为空,或者不是一个有效的事件对象,将报错。
下面代码根据dispatchEvent
方法的返回值,判断事件是否被取消了。
var canceled = !cb.dispatchEvent(event);
if (canceled) {
console.log('事件取消');
} else {
console.log('事件未取消');
}
监听函数
监听函数(listener)是事件发生时,程序所要执行的函数。它是事件驱动编程模式的主要编程方式。
DOM提供三种方法,可以用来为事件绑定监听函数。
HTML标签的on-属性
HTML语言允许在元素标签的属性中,直接定义某些事件的监听代码。
<body onload="doSomething()">
<div onclick="console.log('触发事件')">
上面代码为body
节点的load
事件、div
节点的click
事件,指定了监听函数。
使用这个方法指定的监听函数,只会在冒泡阶段触发。
注意,使用这种方法时,on-
属性的值是将会执行的代码,而不是一个函数。
<!-- 正确 -->
<body onload="doSomething()">
<!-- 错误 -->
<body onload="doSomething">
一旦指定的事件发生,on-
属性的值是原样传入JavaScript引擎执行。因此如果要执行函数,不要忘记加上一对圆括号。
另外,Element元素节点的setAttribute
方法,其实设置的也是这种效果。
el.setAttribute('onclick', 'doSomething()');
Element节点的事件属性
Element节点对象有事件属性,同样可以指定监听函数。
window.onload = doSomething;
div.onclick = function(event){
console.log('触发事件');
};
使用这个方法指定的监听函数,只会在冒泡阶段触发。
addEventListener方法
通过Element
节点、document
节点、window
对象的addEventListener
方法,也可以定义事件的监听函数。
window.addEventListener('load', doSomething, false);
addEventListener方法的详细介绍,参见本节EventTarget接口的部分。
在上面三种方法中,第一种“HTML标签的on-属性”,违反了HTML与JavaScript代码相分离的原则;第二种“Element节点的事件属性”的缺点是,同一个事件只能定义一个监听函数,也就是说,如果定义两次onclick属性,后一次定义会覆盖前一次。因此,这两种方法都不推荐使用,除非是为了程序的兼容问题,因为所有浏览器都支持这两种方法。
addEventListener是推荐的指定监听函数的方法。它有如下优点:
-
可以针对同一个事件,添加多个监听函数。
-
能够指定在哪个阶段(捕获阶段还是冒泡阶段)触发回监听函数。
-
除了DOM节点,还可以部署在
window
、XMLHttpRequest
等对象上面,等于统一了整个JavaScript的监听函数接口。
this对象的指向
实际编程中,监听函数内部的this
对象,常常需要指向触发事件的那个Element节点。
addEventListener
方法指定的监听函数,内部的this
对象总是指向触发事件的那个节点。
// HTML代码为
// <p id="para">Hello</p>
var id = 'doc';
var para = document.getElementById('para');
function hello(){
console.log(this.id);
}
para.addEventListener('click', hello, false);
执行上面代码,点击<p>
节点会输出para
。这是因为监听函数被“拷贝”成了节点的一个属性,所以this
指向节点对象。使用下面的写法,会看得更清楚。
para.onclick = hello;
如果将监听函数部署在Element节点的on-
属性上面,this
不会指向触发事件的元素节点。
<p id="para" onclick="hello()">Hello</p>
<!-- 或者使用JavaScript代码 -->
<script>
pElement.setAttribute('onclick', 'hello()');
</script>
执行上面代码,点击<p>
节点会输出doc
。这是因为这里只是调用hello
函数,而hello
函数实际是在全局作用域执行,相当于下面的代码。
para.onclick = function () {
hello();
}
一种解决方法是,不引入函数作用域,直接在on-
属性写入所要执行的代码。因为on-
属性是在当前节点上执行的。
<p id="para" onclick="console.log(id)">Hello</p>
<!-- 或者 -->
<p id="para" onclick="console.log(this.id)">Hello</p>
上面两行,最后输出的都是para
。
总结一下,以下写法的this
对象都指向Element节点。
// JavaScript代码
element.onclick = print
element.addEventListener('click', print, false)
element.onclick = function () {console.log(this.id);}
// HTML代码
<element onclick="console.log(this.id)">
以下写法的this
对象,都指向全局对象。
// JavaScript代码
element.onclick = function (){ doSomething() };
element.setAttribute('onclick', 'doSomething()');
// HTML代码
<element onclick="doSomething()">
事件的传播
传播的三个阶段
当一个事件发生以后,它会在不同的DOM节点之间传播(propagation)。这种传播分成三个阶段:
-
第一阶段:从window对象传导到目标节点,称为“捕获阶段”(capture phase)。
-
第二阶段:在目标节点上触发,称为“目标阶段”(target phase)。
-
第三阶段:从目标节点传导回window对象,称为“冒泡阶段”(bubbling phase)。
这种三阶段的传播模型,会使得一个事件在多个节点上触发。比如,假设点击<div>
之中嵌套一个<p>
节点。
<div>
<p>Click Me</p>
</div>
如果对这两个节点的click
事件都设定监听函数,则click
事件会被触发四次。
var phases = {
1: 'capture',
2: 'target',
3: 'bubble'
};
var div = document.querySelector('div');
var p = document.querySelector('p');
div.addEventListener('click', callback, true);
p.addEventListener('click', callback, true);
div.addEventListener('click', callback, false);
p.addEventListener('click', callback, false);
function callback(event) {
var tag = event.currentTarget.tagName;
var phase = phases[event.eventPhase];
console.log("Tag: '" + tag + "'. EventPhase: '" + phase + "'");
}
// 点击以后的结果
// Tag: 'DIV'. EventPhase: 'capture'
// Tag: 'P'. EventPhase: 'target'
// Tag: 'P'. EventPhase: 'target'
// Tag: 'DIV'. EventPhase: 'bubble'
上面代码表示,click
事件被触发了四次:<p>
节点的捕获阶段和冒泡阶段各1次,<div>
节点的捕获阶段和冒泡阶段各1次。
- 捕获阶段:事件从
<div>
向<p>
传播时,触发<div>
的click
事件; - 目标阶段:事件从
<div>
到达<p>
时,触发<p>
的click
事件; - 目标阶段:事件离开
<p>
时,触发<p>
的click
事件; - 冒泡阶段:事件从
<p>
传回<div>
时,再次触发<div>
的click
事件。
注意,用户点击网页的时候,浏览器总是假定click
事件的目标节点,就是点击位置的嵌套最深的那个节点(嵌套在<div>
节点的<p>
节点)。所以,<p>
节点的捕获阶段和冒泡阶段,都会显示为target
阶段。
事件传播的最上层对象是window
,接着依次是document
,html
(document.documentElement
)和body
(document.body
)。也就是说,如果<body>
元素中有一个<div>
元素,点击该元素。事件的传播顺序,在捕获阶段依次为window
、document
、html
、body
、div
,在冒泡阶段依次为div
、body
、html
、document
、window
。
事件的代理
由于事件会在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件。这种方法叫做事件的代理(delegation)。
var ul = document.querySelector('ul');
ul.addEventListener('click', function(event) {
if (event.target.tagName.toLowerCase() === 'li') {
// some code
}
});
上面代码的click
事件的监听函数定义在<ul>
节点,但是实际上,它处理的是子节点<li>
的click
事件。这样做的好处是,只要定义一个监听函数,就能处理多个子节点的事件,而且以后再添加子节点,监听函数依然有效。
如果希望事件到某个节点为止,不再传播,可以使用事件对象的stopPropagation
方法。
p.addEventListener('click', function(event) {
event.stopPropagation();
});
使用上面的代码以后,click
事件在冒泡阶段到达<p>
节点以后,就不再向上(父节点的方向)传播了。
但是,stopPropagation
方法只会阻止当前监听函数的传播,不会阻止<p>
节点上的其他click
事件的监听函数。如果想要不再触发那些监听函数,可以使用stopImmediatePropagation
方法。
p.addEventListener('click', function(event) {
event.stopImmediatePropagation();
});
p.addEventListener('click', function(event) {
// 不会被触发
});
Event 对象概述
事件发生以后,会产生一个事件对象,作为参数传给监听函数。浏览器原生提供一个Event
对象,所有的事件都是这个对象的实例,或者说继承了Event.prototype
对象。
Event
对象本身就是一个构造函数,可以用来生成新的实例。
event = new Event(type, options);
Event
构造函数接受两个参数。第一个参数type
是字符串,表示事件的名称;第二个参数options
是一个对象,表示事件对象的配置。该对象主要有下面两个属性。
bubbles
:布尔值,可选,默认为false
,表示事件对象是否冒泡。cancelable
:布尔值,可选,默认为false
,表示事件是否可以被取消,即能否用Event.preventDefault()
取消这个事件。
var ev = new Event(
'look',
{
'bubbles': true,
'cancelable': false
}
);
document.dispatchEvent(ev);
上面代码新建一个look
事件实例,然后使用dispatchEvent
方法触发该事件。
注意,如果不是显式指定bubbles
属性为true
,生成的事件就只能在“捕获阶段”触发监听函数。
// HTML 代码为
// <div><p>Hello</p></div>
var div = document.querySelector('div');
var p = document.querySelector('p');
function callback(event) {
var tag = event.currentTarget.tagName;
console.log('Tag: ' + tag); // 没有任何输出
}
div.addEventListener('click', callback, false);
var click = new Event('click');
p.dispatchEvent(click);
上面代码中,p
元素发出一个click
事件,该事件默认不会冒泡。div.addEventListener
方法指定在冒泡阶段监听,因此监听函数不会触发。如果写成div.addEventListener('click', callback, true)
,那么在“捕获阶段”可以监听到这个事件。
另一方面,如果这个事件在div
元素上触发。
div.dispatchEvent(click);
那么,不管div
元素是在冒泡阶段监听,还是在捕获阶段监听,都会触发监听函数。因为这时div
元素是事件的目标,不存在是否冒泡的问题,div
元素总是会接收到事件,因此导致监听函数生效。
Event 对象的实例属性
Event.bubbles,Event.eventPhase
Event.bubbles
属性返回一个布尔值,表示当前事件是否会冒泡。该属性为只读属性,一般用来了解 Event 实例是否可以冒泡。前面说过,除非显式声明,Event
构造函数生成的事件,默认是不冒泡的。
Event.eventPhase
属性返回一个整数常量,表示事件目前所处的阶段。该属性只读。
var phase = event.eventPhase;
Event.eventPhase
的返回值有四种可能。
- 0,事件目前没有发生。
- 1,事件目前处于捕获阶段,即处于从祖先节点向目标节点的传播过程中。
- 2,事件到达目标节点,即
Event.target
属性指向的那个节点。 - 3,事件处于冒泡阶段,即处于从目标节点向祖先节点的反向传播过程中。
event.cancelable,event.defaultPrevented
以下属性与事件的默认行为有关。
(1)cancelable
cancelable属性返回一个布尔值,表示事件是否可以取消。该属性为只读属性,只能在新建事件时改变。除非显式声明,Event构造函数生成的事件,默认是不可以取消的。
var bool = event.cancelable;
如果要取消某个事件,需要在这个事件上面调用preventDefault方法,这会阻止浏览器对某种事件部署的默认行为。
(2)defaultPrevented
defaultPrevented属性返回一个布尔值,表示该事件是否调用过preventDefault方法。
if (e.defaultPrevented) {
// ...
}
event.currentTarget,event.target
以下属性与事件的目标节点有关。
(1)currentTarget
currentTarget属性返回事件当前所在的节点,即正在执行的监听函数所绑定的那个节点。作为比较,target属性返回事件发生的节点。如果监听函数在捕获阶段和冒泡阶段触发,那么这两个属性返回的值是不一样的。
function hide(e){
console.log(this === e.currentTarget); // true
e.currentTarget.style.visibility = "hidden";
}
para.addEventListener('click', hide, false);
上面代码中,点击para节点,该节点会不可见。另外,在监听函数中,currentTarget属性实际上等同于this对象。
(2)target
target属性返回触发事件的那个节点,即事件最初发生的节点。如果监听函数不在该节点触发,那么它与currentTarget属性返回的值是不一样的。
function hide(e){
console.log(this === e.target); // 有可能不是true
e.target.style.visibility = "hidden";
}
// HTML代码为
// <p id="para">Hello <em>World</em></p>
para.addEventListener('click', hide, false);
上面代码中,如果在para节点的em子节点上面点击,则e.target
指向em子节点,导致em子节点(即World部分)会不可见,且输出false。
在IE6—IE8之中,该属性的名字不是target,而是srcElement,因此经常可以看到下面这样的代码。
function hide(e) {
var target = e.target || e.srcElement;
target.style.visibility = 'hidden';
}
event.type,event.detail,event.timeStamp,event.isTrusted
以下属性与事件对象的其他信息相关。
(1)type
type
属性返回一个字符串,表示事件类型,大小写敏感。
var string = event.type;
(2)detail
detail
属性返回一个数值,表示事件的某种信息。具体含义与事件类型有关,对于鼠标事件,表示鼠标按键在某个位置按下的次数,比如对于dblclick事件,detail属性的值总是2。
function giveDetails(e) {
this.textContent = e.detail;
}
el.onclick = giveDetails;
(3)timeStamp
timeStamp
属性返回一个毫秒时间戳,表示事件发生的时间。
var number = event.timeStamp;
Chrome在49版以前,这个属性返回的是一个整数,单位是毫秒(millisecond),表示从Unix纪元开始的时间戳。从49版开始,该属性返回的是一个高精度时间戳,也就是说,毫秒之后还带三位小数,精确到微秒。并且,这个值不再从Unix纪元开始计算,而是从PerformanceTiming.navigationStart
开始计算,即表示距离用户导航至该网页的时间。如果想将这个值转为Unix纪元时间戳,就要计算event.timeStamp + performance.timing.navigationStart
。
下面是一个计算鼠标移动速度的例子,显示每秒移动的像素数量。
var previousX;
var previousY;
var previousT;
window.addEventListener('mousemove', function(event) {
if (!(previousX === undefined ||
previousY === undefined ||
previousT === undefined)) {
var deltaX = event.screenX - previousX;
var deltaY = event.screenY - previousY;
var deltaD = Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));
var deltaT = event.timeStamp - previousT;
console.log(deltaD / deltaT * 1000);
}
previousX = event.screenX;
previousY = event.screenY;
previousT = event.timeStamp;
});
(4)isTrusted
isTrusted
属性返回一个布尔值,表示该事件是否为真实用户触发。
var bool = event.isTrusted;
用户触发的事件返回true
,脚本触发的事件返回false
。
event.preventDefault()
preventDefault
方法取消浏览器对当前事件的默认行为,比如点击链接后,浏览器跳转到指定页面,或者按一下空格键,页面向下滚动一段距离。该方法生效的前提是,事件对象的cancelable
属性为true
,如果为false
,则调用该方法没有任何效果。
该方法不会阻止事件的进一步传播(stopPropagation
方法可用于这个目的)。只要在事件的传播过程中(捕获阶段、目标阶段、冒泡阶段皆可),使用了preventDefault
方法,该事件的默认方法就不会执行。
// HTML代码为
// <input type="checkbox" id="my-checkbox" />
var cb = document.getElementById('my-checkbox');
cb.addEventListener(
'click',
function (e){ e.preventDefault(); },
false
);
上面代码为点击单选框的事件,设置监听函数,取消默认行为。由于浏览器的默认行为是选中单选框,所以这段代码会导致无法选中单选框。
利用这个方法,可以为文本输入框设置校验条件。如果用户的输入不符合条件,就无法将字符输入文本框。
function checkName(e) {
if (e.charCode < 97 || e.charCode > 122) {
e.preventDefault();
}
}
上面函数设为文本框的keypress
监听函数后,将只能输入小写字母,否则输入事件的默认事件(写入文本框)将被取消。
event.stopPropagation()
stopPropagation
方法阻止事件在 DOM 中继续传播,防止再触发定义在别的节点上的监听函数,但是不包括在当前节点上新定义的事件监听函数。
function stopEvent(e) {
e.stopPropagation();
}
el.addEventListener('click', stopEvent, false);
将上面函数指定为监听函数,会阻止事件进一步冒泡到el节点的父节点。
event.stopImmediatePropagation()
stopImmediatePropagation
方法阻止同一个事件的其他监听函数被调用。
如果同一个节点对于同一个事件指定了多个监听函数,这些函数会根据添加的顺序依次调用。只要其中有一个监听函数调用了stopImmediatePropagation方法,其他的监听函数就不会再执行了。
function l1(e){
e.stopImmediatePropagation();
}
function l2(e){
console.log('hello world');
}
el.addEventListener('click', l1, false);
el.addEventListener('click', l2, false);
上面代码在el节点上,为click事件添加了两个监听函数l1和l2。由于l1调用了stopImmediatePropagation方法,所以l2不会被调用。
自定义事件和事件模拟
除了浏览器预定义的那些事件,用户还可以自定义事件,然后手动触发。
// 新建事件实例
var event = new Event('build');
// 添加监听函数
elem.addEventListener('build', function (e) { ... }, false);
// 触发事件
elem.dispatchEvent(event);
上面代码触发了自定义事件,该事件会层层向上冒泡。在冒泡过程中,如果有一个元素定义了该事件的监听函数,该监听函数就会触发。
由于IE不支持这个API,如果在IE中自定义事件,需要使用后文的“老式方法”。
CustomEvent()
Event构造函数只能指定事件名,不能在事件上绑定数据。如果需要在触发事件的同时,传入指定的数据,需要使用CustomEvent构造函数生成自定义的事件对象。
var event = new CustomEvent('build', { 'detail': 'hello' });
function eventHandler(e) {
console.log(e.detail);
}
上面代码中,CustomEvent构造函数的第一个参数是事件名称,第二个参数是一个对象,该对象的detail属性会绑定在事件对象之上。
下面是另一个例子。
var myEvent = new CustomEvent("myevent", {
detail: {
foo: "bar"
},
bubbles: true,
cancelable: false
});
el.addEventListener('myevent', function(event) {
console.log('Hello ' + event.detail.foo);
});
el.dispatchEvent(myEvent);
IE不支持这个方法,可以用下面的垫片函数模拟。
(function () {
function CustomEvent ( event, params ) {
params = params || { bubbles: false, cancelable: false, detail: undefined };
var evt = document.createEvent( 'CustomEvent' );
evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail );
return evt;
}
CustomEvent.prototype = window.Event.prototype;
window.CustomEvent = CustomEvent;
})();
事件的模拟
有时,需要在脚本中模拟触发某种类型的事件,这时就必须使用这种事件的构造函数。
下面是一个通过MouseEvent构造函数,模拟触发click鼠标事件的例子。
function simulateClick() {
var event = new MouseEvent('click', {
'bubbles': true,
'cancelable': true
});
var cb = document.getElementById('checkbox');
cb.dispatchEvent(event);
}
自定义事件的老式写法
老式浏览器不一定支持各种类型事件的构造函数。因此,有时为了兼容,会用到一些非标准的方法。这些方法未来会被逐步淘汰,但是目前浏览器还广泛支持。除非是为了兼容老式浏览器,尽量不要使用。
(1)document.createEvent()
document.createEvent方法用来新建指定类型的事件。它所生成的Event实例,可以传入dispatchEvent方法。
// 新建Event实例
var event = document.createEvent('Event');
// 事件的初始化
event.initEvent('build', true, true);
// 加上监听函数
document.addEventListener('build', doSomething, false);
// 触发事件
document.dispatchEvent(event);
createEvent方法接受一个字符串作为参数,可能的值参见下表“数据类型”一栏。使用了某一种“事件类型”,就必须使用对应的事件初始化方法。
事件类型 | 事件初始化方法 |
---|---|
UIEvents | event.initUIEvent |
MouseEvents | event.initMouseEvent |
MutationEvents | event.initMutationEvent |
HTMLEvents | event.initEvent |
Event | event.initEvent |
CustomEvent | event.initCustomEvent |
KeyboardEvent | event.initKeyEvent |
(2)event.initEvent()
事件对象的initEvent方法,用来初始化事件对象,还能向事件对象添加属性。该方法的参数必须是一个使用Document.createEvent()
生成的Event实例,而且必须在dispatchEvent方法之前调用。
var event = document.createEvent('Event');
event.initEvent('my-custom-event', true, true, {foo:'bar'});
someElement.dispatchEvent(event);
initEvent方法可以接受四个参数。
- type:事件名称,格式为字符串。
- bubbles:事件是否应该冒泡,格式为布尔值。可以使用event.bubbles属性读取它的值。
- cancelable:事件是否能被取消,格式为布尔值。可以使用event.cancelable属性读取它的值。
- option:为事件对象指定额外的属性。
事件模拟的老式写法
事件模拟的非标准做法是,对document.createEvent方法生成的事件对象,使用对应的事件初始化方法进行初始化。比如,click事件对象属于MouseEvent对象,也属于UIEvent对象,因此要用initMouseEvent方法或initUIEvent方法进行初始化。
(1)event.initMouseEvent()
initMouseEvent方法用来初始化Document.createEvent方法新建的鼠标事件。该方法必须在事件新建(document.createEvent方法)之后、触发(dispatchEvent方法)之前调用。
initMouseEvent方法有很长的参数。
event.initMouseEvent(type, canBubble, cancelable, view,
detail, screenX, screenY, clientX, clientY,
ctrlKey, altKey, shiftKey, metaKey,
button, relatedTarget
);
上面这些参数的含义,参见MouseEvent构造函数的部分。
模仿并触发click事件的写法如下。
var simulateDivClick = document.createEvent('MouseEvents');
simulateDivClick.initMouseEvent('click',true,true,
document.defaultView,0,0,0,0,0,false,
false,false,0,null,null
);
divElement.dispatchEvent(simulateDivClick);
(2)UIEvent.initUIEvent()
UIEvent.initUIEvent()
用来初始化一个UI事件。该方法必须在事件新建(document.createEvent方法)之后、触发(dispatchEvent方法)之前调用。
event.initUIEvent(type, canBubble, cancelable, view, detail)
该方法的参数含义,可以参见MouseEvent构造函数的部分。其中,detail参数是一个数值,含义与事件类型有关,对于鼠标事件,这个值表示鼠标按键在某个位置按下的次数。
var e = document.createEvent("UIEvent");
e.initUIEvent("click", true, true, window, 1);
参考链接
- Wilson Page, An Introduction To DOM Events
- Mozilla Developer Network, Using Firefox 1.5 caching
- Craig Buckler, How to Capture CSS3 Animation Events in JavaScript
- Ray Nicholus, You Don’t Need jQuery!: Events