要点
- 大多数JavaScript代码都是用来响应事件的。
- 要响应事件,可以编写一个事件处理程序并赋给某个元素的属性(例如,可将处理程序赋给元素的onclick属性)
- 事件处理程序(后文也称“处理程序”)是一个函数,在事件发生时执行
- 短时间内发生大量事件,浏览器将时间按发生的顺序存储到事件队列中,依次调用其处理程序(应保证事件处理程序简洁高效,否则将导致队列中其他事件无法得到及时处理)
- 处理事件的代码不同于从头到尾执行的代码:事件处理程序的运行时间和运行顺序都是不确定的,它们是异步的。
- (1)DOM事件(发生在DOM元素上的事件)导致一个
event
对象被传递给处理程序
event对象包含一些属性,这些属性提供了有关事件的细节信息,如事件的类型type
("click"
或"load"
)和目标target
(触发事件的对象) - (2)对于基于时间的事件(如
setTimeout()
),关联处理程序与事件的方法 不是将处理程序赋给属性,而是将处理程序传递给某个函数,且不产生event
对象
- 方法
getElementByTagName
返回一个NodeList
,其中包含0个、1个或更多的element
对象,NodeList类似于数组,你可以对其进行迭代。
为何需要事件
- 从前的编程方式通常都从上到下的、按顺序的、按部就班地执行的,这是线性(linear)的编码方式
- 然而,JavaScript代码通常不是这样编写的,大多数JavaScript代码都是事件响应式的,这是异步(asynchronous)的编码方式(事件处理程序的运行时间和运行顺序都是不确定的)
- 接下来,反思我们的编码方式,学习编写响应事件的代码及为何要这样做
由于JavaScript用于交互式的网页环境,在网页中随时都会有不可预测的事件发生(如网页加载完毕、用户点击了按钮、定时器到期):这种不可预测性意味着我们不可能提前预知接下来发生什么,进而决定了JavaScript不可能按照既定的顺序执行代码,而是被动地响应事件
事件处理程序(也称回调函数/监听器)
我们希望每当有事件发生时处理事件,这需要事件处理程序
事件处理程序就是一个函数,当事件发生时,其事件处理程序将被调用,因此,事件处理程序也称为回调函数或监听器
一个例子:如何创建事件处理程序
目标:网页上有一幅模糊的图像(一个
<img>
元素),点击图像后,替换为清晰的图像
其中,模糊的图像为"pictureblur.jpg"
,清晰的图像为"picture.jpg"
<body>
<img id="picture1" src="picture1blur.jpg">
</body>
其中有两个事件处理程序:
- 网页完全加载后的事件处理程序
init()
(赋给window.onload
属性)。
ps.别忘了这里要使用网页的DOM(要修改HTML的<img>
元素),因此要等网页(以及DOM)加载好后再运行我们的代码 - 图片被点击后的事件处理程序
showAnswer()
(赋给elem.onclick
属性)
window.onload = init;//网页加载完成后的事件处理程序init
function init() {
var image = document.getElementById("picture1");
image.onclick = showAnswer;//点击图片时的事件处理程序showAnswer
};
function showAnswer() {
var image = document.getElementById("picture1");
image.src = "picture1.jpg";
}
ps. 这里为什么要在showAnswer()
再次调用一遍document.getElementById("picture")
?
别忘了作用域规则:在函数内部创建的变量都是局部变量,因此在
showAnswer()
中必须重新获取<img>
元素
ps.这里在一个事件处理程序中(
init
)创建了其他事件处理程序(showAnswer
),这体现了开头所说的异步编程风格,这种做法在JavaScript很常见
事件(Event)的工作原理
- 浏览器将捕捉事件,对其进行分析,并检查是否有事件处理程序等待该事件发生
- 若短时间内发生过多事件,浏览器维护一个事件队列
- 浏览器只能逐个按顺序处理队列中的事件,因为只有一个队列和一个控制线程(因此,应该尽可能使事件处理程序简短而高效,否则会导致页面响应缓慢)
常用的事件
关于窗口的事件:
用window.onload
指定处理程序,网页加载完毕时触发
用window.unload
指定处理程序,用户关闭窗口/切换到其他网页时触发
用window.resize
指定处理程序,用户调整窗口大小时触发
关于元素对象的事件:
Elem.onclick
:单击元素时触发
Elem.onkeypress
:按下任何键时触发
Elem.onplay
:单击<video>
元素的播放按钮时触发
Elem.onpause
:单击<video>
元素的暂停按钮时触发
鼠标移动事件:
Elem.onmousemove
:在元素上移动鼠标时触发
Elem.onmouseover
:鼠标位于元素上方时触发
Elem.onmouseout
:鼠标从元素上方移开时触发
Elem.ondragstart
:用户开始拖动元素/选择的文本时触发
Elem.ondrop
:用户放下拖动的元素时触发
触屏设备上的事件:
Elem.touchstart
:用户触摸并按住元素时触发
Elem.touched
:用户停止触摸时触发
Elem.addEventListener()
方法可以向一个元素添加多个相同类型的事件处理程序,而不覆盖现有的事件处理程序
例如
object.touchstart = function(){myScript};
等效于
object.addEventListener("touchstart", myScript);
事件对象:获取事件有关细节(如触发本事件的对象)
问:会传递参数给事件处理程序吗?
答:会给处理程序传递参数,这个参数就是事件对象
有些事件(如DOM事件)会生成事件对象,其中包含事件的有关细节(单击事件包含有关单击位置的信息,按键事件包含有关按下的是哪个键的信息,等等)
一些基于时间的时间则不生成事件对象,详见后文
- 每当调用单击事件处理程序时:
- 都将导致一个
event
对象(事件对象)被创建 - 事件对象会被传递给事件处理程序,由事件对象可获取有关事件的细节
在事件处理程序中声明形参,事件对象会被传递给这个形参
image.onclick = showAnswer;//点击图片时的事件处理程序showAnswer
function showAnswer(eventObj) {
var image = eventObj.target;
image.src = image.id+".jpg";//image.id为"picture1"
}
事件对象的常用属性
eventObj.target
存储着触发事件的对象。通常是元素对象,也可为其他对象。
如:点击某<image>
元素触发了事件,则eventObj.target
指向该<image>
元素eventObj.type
指出事件的类型,是一个字符串,如"click"
或"load"
eventObj.keyCode
确定用户刚按下了哪个键。eventObj.clientX
确定用户单击的位置到浏览器窗口的左边缘的距离eventObj.clientY
确定用户单击的位置到浏览器窗口的上边缘的距离eventObj.timeStamp
事件的发生时间(较旧的IE版本不支持)eventObj.touches
一在触摸设备上,可使用我来确定用户使用了多少根手指来触摸屏幕。
Eg.鼠标移动事件(mousemove event)——实时显示坐标的程序
- 鼠标在元素上移动时触发此事件,使用元素的属性
onmousemove
指定处理程序; - 此事件会生成并传递一个事件对象
该event
对象其中包含如下属性:
clientX
和clientY
:鼠标相对于浏览器窗口左边缘和上边缘的距离,单位为像素。
screenX
和screenY
:鼠标相对于设备屏幕左边缘和上边缘的距离,单位为像素。
pagex
和pageY
:鼠标相对于网页左边缘和上边缘的距离,单位为像素。
例如:在图像上移动鼠标时,显示坐标
window.onload = init;
function init() {
var picture = document.getElementById("picture");
picture.onmousemove = showCoords;//鼠标移动事件的处理程序
}
function showCoords(eventObj) {//传入事件对象
var coords = document.getElementById("coords");//显示坐标的文本元素<p>
var x = eventObj.pageX;
var y = eventObj.pageY;
coords.innerHTML = "picture coordinates: " + x + ", " + y;
}
如何将处理程序与事件关联:两种情况
- 对于前面介绍的事件,将处理程序与事件关联的方法,总是将处理程序(回调函数)赋给某个属性,如
onload
、onclick
、onmousemove
等
但这种做法不适用于另一类事件:如基于时间的事件
- 对于基于时间的事件,将处理程序与事件关联的方法有很大不同:不是将处理程序赋给属性,而是将处理程序传递给某个函数,如
setTimeout()
、setInterval()
(调用函数时开始计时,到达设定的时间后触发时间处理程序)
并且,基于时间的事件不会生成事件对象
问:
setTimeout
为何不向事件处理程序传递一个事件对象?`
答:事件对象主要用于DOM事件处理程序;
setTimeout不向处理程序传递事件对象,因为时间事件并非由特定的元素触发。
基于时间的事件
setTimeout()
定时器
setTimeout(事件处理程序的函数名,计时时间,额外参数(触发事件时,被传递给处理程序))
- 额外参数可选:0个,1个或更多(IE8及更早浏览器不支持额外参数)
- 调用
setTimeout()
,将创建一个定时器,浏览器管理所有的定时器(可以同时有多个定时器) - 在倒计时结束后,将调用处理程序
- 这里,将处理程序与事件关联的方法与之前不同:向
setTimeout
传递了一个指向处理程序函数的引用(不是将处理程序赋给某个属性,而是调用函数setTimeout
并向它传递处理程序) - 这里将函数传递给另一个函数:准确的说是将指向事件处理程序(函数)的引用传递给了
setTimeout
(另一个函数) - 在C或Java等语言中,像这样将一个函数传递给另一个函数根本行不通;
但在JavaScript中,这行得通。能够传递函数提供了一种强大的功能,在JavaScript中传递函数的情形非常普遍
setInterval()
定时器:每隔一段时间触发一次
setTimeout(事件处理程序的函数名,间隔时间,额外参数(触发事件时,被传递给处理程序))
- 每隔一段时间触发一次,调用事件处理程序
- 要停止
setInterval
:setInterval
返回一个timer
对象,将其传递给另一个函数clearInterval
以停止该定时器
setTimeout
实际上是一个方法,严格地说,它应写作window.setTimeout
,但由于window是全局对象,可省略对象名,直接写作setTimeout,实际编写代码时经常这样做;
理论上,属性window.onload
时也可以省略window
,但大多数人都不会这样做,因为onload是一个常见的属性名(其他元素也可能有属性onload)。如果省略window,将让人不知道指的是哪个对象的onload属性。
通过给onload赋值,我们将一个处理程序关联到了相应的事件。但通过使用setTimeout,可以创建任意数量的定时器,并分别为每个定时器指定与之相关联的处理程序。
其他事件
除了有基于DOM的事件、定时器事件,还有很多不同类型的事件
- 在JavaScript中,大部分事件都是DOM事件(如单击元素触发的事件)
或定时器事件(使用setTimeout
或setInterval
创建的事件) - 此外,还有与API相关的事件,如
Geolocation
、LocalStorage
、Web Worker
等JavaScript API触发的事件 - 最后,还有一系列与I/O相关的事件,如使用
XmlHttpRequest
向Web服务请求数据时引发的事件,以及使用Web套接字引发的事件