事件是一种异步编程的实现方式,本质上是程序各个组成部分之间的通信。DOM支持大量的事件,本节介绍DOM的事件编程。
DOM的事件操作(监听和触发),都定义在EventTarget
接口。Element
节点、document
节点和window
对象,都部署了这个接口。此外,XMLHttpRequest
、AudioNode
、AudioContext
等浏览器内置对象,也部署了这个接口。
该接口就是三个方法,addEventListener
和removeEventListener
用于绑定和移除监听函数,dispatchEvent
用于触发事件。
addEventListener
方法用于在当前节点或对象上,定义一个特定事件的监听函数。
// 使用格式
target.addEventListener(type, listener[, useCapture]);
// 实例
window.addEventListener(‘load’, function () {…}, false);
request.addEventListener(‘readystatechange’, function () {…}, false);
addEventListener
方法接受三个参数。
type
:事件名称,大小写敏感。listener
:监听函数。事件发生时,会调用该监听函数。useCapture
:布尔值,表示监听函数是否在捕获阶段(capture)触发(参见后文《事件的传播》部分),默认为false
(监听函数只在冒泡阶段被触发)。老式浏览器规定该参数必写,较新版本的浏览器允许该参数可选。为了保持兼容,建议总是写上该参数。下面是一个例子。
function hello() {
console.log('Hello world');
}
var button = document.getElementById(‘btn’);
button.addEventListener(‘click’, hello, false);
上面代码中,addEventListener
方法为button
元素节点,绑定click
事件的监听函数hello
,该函数只在冒泡阶段触发。
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
传递了一个参数。
removeEventListener
方法用来移除addEventListener
方法添加的事件监听函数。
div.addEventListener('click', listener, false);
div.removeEventListener('click', listener, false);
removeEventListener
方法的参数,与addEventListener
方法完全一致。它的第一个参数“事件类型”,大小写敏感。
注意,removeEventListener
方法移除的监听函数,必须与对应的addEventListener
方法的参数完全一致,而且必须在同一个元素节点,否则无效。
dispatchEvent
方法在当前节点上触发指定事件,从而触发监听函数的执行。该方法返回一个布尔值,只要有一个监听函数调用了Event.preventDefault()
,则返回值为false
,否则为true
。
target.dispatchEvent(event)
dispatchEvent
方法的参数是一个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语言允许在元素标签的属性中,直接定义某些事件的监听代码。
"doSomething()">
onclick="console.log('触发事件')">
上面代码为body
节点的load
事件、div
节点的click
事件,指定了监听函数。
使用这个方法指定的监听函数,只会在冒泡阶段触发。
注意,使用这种方法时,on-
属性的值是将会执行的代码,而不是“监听函数”。
"doSomething()">
“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代码为
// Hello
var id = ‘doc’;
var para = document.getElementById(‘para’);
function hello(){
console.log(this.id);
}
para.addEventListener(‘click’, hello, false);
执行上面代码,点击p节点会输出para。这是因为监听函数被“拷贝”成了节点的一个属性,使用下面的写法,会看得更清楚。
para.onclick = hello;
如果将监听函数部署在Element节点的on-属性上面,this不会指向触发事件的元素节点。
id="para" onclick="hello()">Hello
pElement.setAttribute('onclick', 'hello()');
执行上面代码,点击p节点会输出doc。这是因为这里只是调用hello函数,而hello函数实际是在全局作用域执行,相当于下面的代码。
para.onclick = function(){
hello();
}
一种解决方法是,不引入函数作用域,直接在on-属性写入所要执行的代码。因为on-属性是在当前节点上执行的。
id="para" onclick="console.log(id)">Hello
id="para" onclick="console.log(this.id)">Hello
上面两行,最后输出的都是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()”>