事件流
JavaScript与HTML之间的交互是通过事件实现的。事件,就是文档或浏览器窗口中发生的一些特定的交互瞬间。可以使用侦听器(或处理程序)来预定事件,以便事件发生时执行相应的代码。这种在传统软件工程中被成为观察员模式的模型,支持页面的行为(js代码)与页面的外观(HTML和CSS代码)之间的松散耦合。
事件最早是在IE3和Netscape Navigator2(以下简称网景)中出现的,当时作为分担服务器运算负载的一种手段。在IE4和网景4发布时,这两种浏览器都提供了相似不相同的API,这些API并存经过了好几个主要版本,DOM2级规范开始尝试以一种符合逻辑的方式来标准化DOM事件。IE9、Firefox、Opera、Safari和Chrome全部已实现了DOM2级事件模块的核心部分。IE8是最后一个仍然使用其专有事件系统的主要浏览器。
定义与由来
当浏览器发展到第四代时,浏览器开发团队遇到了一个很有意思的问题:页面的哪一部分会拥有某个特定的事件?想象一下,在一张纸上画一个同心圆,如果你把手指放在圆心上,那么你的手指指向的不是一个圆,而是纸上所有的圆。两家公司的浏览器开发团队看待浏览器事件方面还是一致的。如果你单击了某个按钮,他们都认为单击事件不仅仅发生在按钮上。换句话说,在单击按钮的同时,你也单击了按钮的容器元素,甚至也单击了整个页面。
事件流描述的是从页面中接受事件的顺序。但有意思的是,IE和网景开发团队居然提出了差不多完全相反的事件流的概念。IE的事件流是事件冒泡,而网景的事件流是事件捕获流。
事件冒泡
IE的事件流叫做事件冒泡(event bubbling),即事件开始时由最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到较为不具体的节点(文档)。以下面的HTML页面为例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Event Bubbling Example</title>
</head>
<body>
<div id="myDiv">Click Me</div>
</body>
</html>
如果单击了页面中的<div>
元素,那么这个click事件会按照如下顺序传播:
- <div>
- <body>
- <html>
- document
也就是说,click事件首先在<div>元素上发生,而这个元素就是我们单击的元素。然后,click事件沿DOM数向上传播,在每一级节点上都会发生,直至传播到document对象。
所有浏览器都支持事件冒泡,但在具体实现上还是有一些差别。IE5.5及更早版本会跳过<html>元素直接到document对象。IE9、Chrome、Safari则将事件一直冒泡到window对象。
事件捕获
网景团队提出的另一种事件流叫做事件捕获(event capturing)。事件捕获的思想是不太具体的节点应该更早接收到事件,而最具体的节点应该最后接收到事件。事件捕获的用意在于在事件到达预定目标之前捕获它。如果仍以之前HTML代码演示捕获例子,那么单击click元素就会以下列顺序触发click事件:
- document
- <html>
- <body>
- <div>
在事件捕获过程中,document对象首先接收到click事件,然后事件沿DOM数依次向下,一直传播到事件具体目标,即<div>元素。
虽然事件捕获是网景唯一支持的事件流模型,但IE9、Safari、Chrome、Opera和Firefox目前也都支持这种事件流模型。尽管”DOM2级事件“规范要求事件应该从document对象开始传播,但这些浏览器都是从window对象开始捕获事件的。
由于老版本浏览器不支持,很少有人使用事件捕获,一般都使用事件冒泡。
DOM事件流
DOM2级事件规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段、事件冒泡阶段。首先发生的是事件捕获,为截获事件提供了机会。然后是实际的目标接收到事件。最后一个阶段是冒泡阶段,可以在这个阶段对事件做出响应。以前面简单的HTML页面为例,单击<div>元素会按照下图所示顺序触发事件。
在DOM事件流中,实际的目标<div>元素在捕获阶段不会接收到事件。这意味在捕获阶段,事件从document到<html>再到<body>后就停止了。下一个阶段是”处于目标“阶段,于是事件在<div>上发生,并在事件处理中被看成冒泡阶段的一部分。然后,冒泡阶段发生,事件又传播回文档。
IE8及更早版本不支持DOM事件流。
事件处理程序
事件就是用户或浏览器自身执行的某种动作。诸如:click、load和mouseover,都是事件的名字。而响应某个个事件的函数叫做事件处理程序(或事件侦听器)。
其实JS与HTML的交互就是事件处理程序执行的结果。
事件处理程序的名字以"on"开头,因此click事件的事件处理程序(click事件侦听器)就是onclick,load事件的事件处理程序(load事件侦听器)就是onload。
为事件指定处理程序的方式有好几种(面试)
HTML事件处理程序
某个元素支持的每种事件,都可以使用一个与相应事件处理程序同名的HTML特性来指定。这个特性的值应该是能够执行的JavaScript代码。如:
<input type="button" value="Click Me" onclick="alert('Clicked')">
当单击这个按钮时,就会显示一个警告框。这个操作是通过指定onclick特性一些JavaScript代码作为它的值来定义的。由于这个值是JavaScript,因此不能在其中使用未经转义的HTML语法字符。为了避免使用HTML实体,这里使用单引号,如果想要使用双引号,可以这样改写:
<input type="button" value="Click Me" onclick="alert("Clicked")">
在HTML中定义的事件处理程序可以包含要执行的具体动作,也可以调用在页面其他地方定义的脚本:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Event Bubbling Example</title>
<script src="./js/fuck.js"></script>
</head>
<body>
<input type="button" value="Click Me" onclick="showMessage()">
<script>
function showMessage() {
alert("hello world");
}
</script>
</body>
</html>
单击按钮调用showMessage()函数,这个函数是在一个独立<script>元素中定义的,当然也可以被包含在一个外部文件中。事件处理程序中的代码在执行时,有权访问全局作用域中任何代码。
这样指定事件处理程序具有一些独到之处。首先,这样会创建一个封装着元素属性值的函数。这个函数中有一个局部变量event,也就是事件对象:
<input type="button" value="Click Me" onclick="console.log(event.type)">
<!-- 输出click -->
event为事件对象,我们打印出来看看里面有啥
<input type="button" value="Click Me" onclick="console.log(event)">
null: MouseEvent {isTrusted: true, screenX: 261, screenY: 236, clientX: 38, clientY: 17, …}
altKey: false
bubbles: true
button: 0
buttons: 0
cancelable: true
cancelBubble: false
clientX: 38
clientY: 17
composed: true
ctrlKey: false
currentTarget: null
defaultPrevented: false
detail: 1
eventPhase: 0
fromElement: null
isTrusted: true
layerX: 38
layerY: 17
metaKey: false
movementX: 0
movementY: 0
offsetX: 28
offsetY: 5
pageX: 38
pageY: 17
path: Array(5) [input, body, html, …]
relatedTarget: null
returnValue: true
screenX: 261
screenY: 236
shiftKey: false
sourceCapabilities: InputDeviceCapabilities {firesTouchEvents: false}
srcElement: input
target: input
timeStamp: 3469.8000000207685
toElement: input
type: "click"
view: Window {postMessage: , blur: , focus: , …}
which: 1
x: 38
y: 17
__proto__: MouseEvent {screenX: <accessor>, screenY: <accessor>, clientX: <accessor>, …}
通过event变量,可以直接访问事件对象,你不用自己定义它,也不用从函数的参数列表中读取。在这个函数内部,this值等于事件的目标元素。如:
<input type="button" value="Click Me" onclick="console.log(this.value)">
<!-- 输出 "Click Me"-->
关于这个动态创建的函数,另一个有意思的地方是它扩展作用域的方式,在这个函数内部,可以像访问局部变量一样访问document及该元素本身的成员。这个函数使用with像下面这样扩展作用域:
function() {
with(document) {
with(this) {
// 元素属性值
}
}
}
这样一来,事件处理程序要访问自己的属性就容易多了。
<input type="button" value="Click Me" onclick="alert(value)">
<!-- 输出 "Click Me" -->
总结(面试):
这种在HTML中指定事件处理程序有两个缺点。
时差问题
用户可能会在HTML元素一出现在页面上就触发相应的事件,但当时的事件处理程序有可能尚不具备执行条件。
比如,将click事件处理函数放在HTML下方,页面最底部,用户在函数解析完成前激活了click事件,就会引发错误。为此,很多HTML事件处理程序都会被封装在一个try-catch块中,以便错误不会浮出水面,如:
<input type="button" value="Click Me" onclick="try{showMessage();}catch(ex){}">
这样,如果在showMessage()函数有定义之前单击按钮,用户不会看到JavaScript错误,因为在浏览器有机会处理错误之前,错误就被捕获了。
作用域问题
另一个缺点是,这样扩展事件处理程序的作用域链在不同浏览器导致不同结果。不同JavaScript引擎遵循的标识符规则略有差异,很可能会在访问非限定对象成员时出错。
分离原则
通过HTML指定事件处理程序最后一个缺点是HTML与JavaScript代码紧密耦合。如果要更换事件处理程序,就要改动HTML代码和JavaScript代码,而这正是开发人员摒弃HTML事件处理程序,转而使用JavaScript指定事件处理程序的原因所在。
不要使用HTML指定事件处理程序
DOM0级事件处理程序
通过JavaScript指定事件处理方式的传统方式,就是将一个函数赋值给一个事件处理程序属性。这种为事件处理程序赋值的方法是在第四代Web浏览器中出现。
- 简单
- 具有跨浏览器优势
每个元素(包括window和document)都有自己的事件处理程序属性,这些属性通常全部小写,例如onclick。将这种属性的值设置为一个函数,就可以指定事件处理程序。
var btn = document.getElementById("myBtn");
btn.onclick = function() {
alert("Clicked");
};
在此,我们通过文档对象取得了一个按钮的引用,然后为它指定了onclick事件处理程序。但要注意,在这些代码运行以前不会指定事件处理程序,因此如果这些代码在页面中位于按钮后面,就有可能在一段时间内怎么单击都没有反应。
使用DOM0级方法指定的事件处理程序被认为是元素的方法,因此,这时候的事件处理程序是在元素的作用域中运行;换句话说,程序中的this引用当前元素。
var btn = document.getElementById("myBtn");
btn.onclick = function() {
alert(this.id); // "myBtn"
};
单击按钮显示的是元素的ID,这个ID是通过this.id取得的。不仅仅是ID,实际上可以在事件处理程序中通过this访问元素的任何属性和方法。
以这种方式添加的事件处理程序会在事件流的冒泡阶段被处理。
也可以删除通过DOM0级方法指定的事件处理程序。
btn.onclick = null; // 删除事件处理程序
此时单击按钮不会有任何动作发生。
个人理解:没有动作发生因为没有事件处理程序,但是的确存在这个事件被触发。只是我们没有对这个事件发生时“做些什么”而已。
如果你使用HTML指定事件处理程序,那么onclick属性的值就是一个包含着在同名HTML特性中指定的代码的函数。而将相应的属性设置为null,也可以删除以这种方式指定的事件e处理程序。
DOM2级事件处理程序
"DOM2级事件"定义了两个方法,用于处理指定和删除事件处理程序的操作:addEventListener()和removeEventListener()。所有DOM节点中都包含这两个方法,并且它们都接受3个参数:要处理的事件名、作为事件处理程序的函数和一个布尔值。最后这个布尔值参数如果是true,表示在捕获阶段调用事件处理程序;如果是false,表示在冒泡阶段调用事件处理程序。
按钮上为click事件添加事件处理程序:
<input type="button" id="myBtn" value="Click Me">
var btn = document.getElementById("myBtn");
btn.addEventListener("click", function () {
alert(this.id);
}, false);
上面的代码为一个按钮添加了onclick事件处理程序,而且该事件会在冒泡阶段被触发(因为最后一个参数false)。与DOM0级方法一样,这里添加的事件处理程序也是在其依附的元素的作用域中运行。使用DOM2级方法添加事件处理程序的主要好处是可以添加多个事件处理程序。
var btn = document.getElementById("myBtn");
btn.addEventListener("click", function () {
alert(this.id);
}, false);
btn.addEventListener("click",function() {
alert("Hello World!");
}, false)
调用的事件处理程序会按照添加它们的顺序触发,因此首先会显示ID,而后显示”Hello World!“消息。
通过addEventListener()添加的事件处理程序只能使用removeEventListener()来移除。
var btn = document.getElementById("myBtn");
btn.addEventListener("click", function () {
alert(this.id);
}, false);
btn.removeEventListener("click", function () {
alert(this.id);
})
这样行吗?当然不行,两个方法定义的事件处理函数根本就不是”一个东西“,即它们是两个不相干的函数对象。
我们需要使用函数表达式。
var btn = document.getElementById("myBtn");
var handler = function() {
alert(this.id);
};
btn.addEventListener("click",handler,false);
btn.removeEventListener("click",handler,false);
大多数情况,都是将事件处理程序添加到事件流的冒泡阶段,这样可以最大限度的兼容各种浏览器。
不是特别需要,不建议在事件捕获阶段注册事件处理程序。
IE事件处理程序(IE兼容方法)
IE实现了与DOM中类似的两个方法:attachEvent()
和detachEvent()
这两个方法接受相同的两个参数:事件处理程序名称和事件处理函数。由于IE8及更早版本只支持事件冒泡,所以通过attachEvent()添加的事件处理程序都会被添加到冒泡阶段。
使用attachEvent()为按钮添加一个事件处理程序:
btn.attachEvent("onclick",function() {
alert("Clicked");
});
注意,参数从"click"变为了"onclick"。
IE中使用attachEvent()与使用DOM0级方法的主要区别在于事件处理程序的作用域。在使用DOM0级方法的情况下,事件处理程序会在其所属元素的作用域内运行;在使用attachEvent()方法的情况下,事件处理程序会在全局作用域下与西宁,因此this等于window。
btn.attachEvent("onclick",function() {
alert(this === window);
});
在编写跨浏览器的代码时,记住。
与addEventListener()类似,attachEvent()方法也可以用来为一个元素添加多个事件处理程序。
btn.attachEvent("onclick",function() {
alert(this === window);
});
btn.attachEvent("onclick",function() {
alert("Hello World!");
});
但是!这些事件处理程序不是以添加它们的顺序来执行的,而是以相反的顺序被触发
先是看到Hello World!,然后才是true。
使用detachEvent()移除事件处理程序和removeEventListener()是差不多的。
var btn = document.getElementById("myBtn");
var handler = function() {
alert("hello world!")
}
btn.attachEvent("onclick",handler);
btn.detachEvent("onclick",handler);
支持IE事件处理程序的浏览器有IE和Opera
跨浏览器的事件处理程序
var EventUtil = {
addHandler:function(element,type,handler) {
if (element.addEventListener) {
element.addEventListener(type,handler,false);
} else if (element.attachEvent) {
element.attachEvent("on"+typeo,handler);
} else {
element["on"+type] = handler;
}
},
removeHandler:function(element,type,handler) {
if (elemen.removeEventListener) {
element.removeEventListener(type,handler,false);
} else if (element.detachEvent) {
element.detachEvent("on"+type,handler);
} else {
element["on"+type] = null;
}
}
}
这连个方法都会检测传入元素是否存在DOM2级方法。如果存在,使用该方法:传入事件类型、事件处理程序函数和第三个参数false(表示冒泡阶段)。如果存在的是IE的方法,则采取第二种方案。
为了在IE8级更早版本中运行,此时的事件类型必须加上"on"前缀。
最后一种可能是使用DOM0级方法(在现在浏览器中,不会执行这里的代码)。此时,我们使用的是方括号语法来讲属性名指定为事件处理程序,或者将属性设置为null。
var btn = document.getElementById("myBtn");
var handler = function() {
alert("Clicked");
}
EventUtil.addHandler(btn,"click",handler);
EventUtil.removeHandler(btn,"click",handler);
addHandler()和removeHandler()没有考虑所有的浏览器问题,例如在IE中的作用域问题,不过,使用它们添加和移除事件处理程序还是足够了。
此外要注意,DOM0级对每个事件只支持一个事件处理程序。不过现在只支持DOM0级的浏览器应该很少了。
事件对象
在触发DOM上的事件时,会产生一个事件对象event,这个对象中包含所有与事件有关的信息。包活导致事件的元素、事件的类型及其他与特定事件相关的信息。例如,鼠标操作导致的事件对象中,会包含鼠标位置的信息,键盘操作导致的事件对象中,会包含与按下的键有关的信息。
所有的浏览器都支持event对象,但支持方式不同。
DOM中的事件对象
兼容DOM的浏览器会将一个event对象传入到事件处理程序中。无论指定事件处理程序时使用什么方法(DOM0级或DOM2级),都会传入event对象。
var btn = document.getElementById("myBtn");
btn.onclick = function(event) {
alert(event.type); // "click"
};
btn.addEventListener("click",function(event) {
alert(event.type); // "click<"
},false);
event属性始终都会包含被触发的事件类型,例如"click"。
在通过HTM特性指定事件处理程序时,变量event中保存着event对象。
<input type="button" id="myBtn" value="Click Me" onclick="alert(event.type)">
以这种方式提供event对象,可以让HTML特性事件处理程序与JavaScript函数执行相同的操作。
event对象包含与创建它的特定事件有关的属性和方法。触发的事件类型不一样,可用的属性和方法也不一样。不过所有属性都会有一些共有的成员。
/*
属性/方法 类型 读/写 说明
bubbles Boolean 只读 表明事件是否冒泡
cancelable Boolean 只读 表明是否可以取消事件的默认行为
currentTarget Element 只读 其事件处理程序当前正在处理事件的那个元素
defaultPrevented Boolean 只读 为true表示已经调用了preventDefault()
detail Integer 只读 与事件有关的细节信息
eventPhase Integer 只读 调用事件处理程序的阶段:1捕获阶段、2"处于目标"、3表示冒泡阶段
preventDefault Function 只读 取消事件的默认行为。如果cancleable是true,则可以使用这个方法
stopImmediatePropagation() Function 只读 取消事件的进一步捕获或冒泡,同时阻止任何事件处理程序被调用
stopPropagation() Function 只读 取消事件的进一步捕获或冒泡。如果bubbles为true,则可以使用这个方法
target Element 只读 事件的目标
trusted Boolean 只读 为true表示目标是浏览器生成的。为false表示事件是由开发人员通过JavaScript创建的
type String 只读 被触发的事件的类型
view AbstractView 只读 与事件关联的抽象视图。等同于发生事件的window对象。
*/
在事件处理程序内部,对象this始终等于cureentTarget的值,而target则只包含事件的实际目标。
var btn = document.getElementById("myBtn");
btn.onclick = function(event) {
alert(event.currentTarget === this); // true
alert(event.target === this); // true
}
这个例子检测了currentTarget和target与this的值。由于click事件的目标是按钮,因此这三个值是相等的。
如果事件处理程序存在于按钮的父节点中(例如document.body),那么情况就有所变化。
document.body.onclick = function(event) {
alert(event.currentTarget === document.body); // true
alert(this === document.body); // true
alert(event.target === document.getElementById("myBtn")); // true
}
当单击这个例子中的按钮时,this和currentTarget都等于document.body,因此事件处理程序是注册到这个元素上的。然而,target元素却等于按钮元素,因为它是click事件真正的目标(用户操作点击了按钮)。只是按钮上没有事件处理程序,没哟函数执行。结果click事件冒泡到了body,在那里事件得到处理。
在需要通过一个函数处理多个事件时,可以使用type属性。
var btn = document.getElementById("myBtn");
var handler = function(event) {
switch(event.type) {
case "click":
alert("Clicked");
break;
case "mouseover":
event.target.style.backgroundColor = "red";
break;
case "mouseouot":
event.target.style.backgroundColor = "";
break;
}
};
btn.onclick = handler;
btn.onmouseover = handler;
btn.onmouseout = handler;
例子定义了名为handler的函数,用于处理三种事件:click、mouseover、mouseout。
当单击按钮时,出现一个警告框。当按钮移动到按钮上面时,背景颜色变化成红色。当鼠标移动出按钮时,背景颜色回复默认值。
这里通过检测event.type属性,让函数能够确定发生了什么事件,并执行相应操作。
要阻止特定事件的默认行为,可以使用preventDefault()方法。例如,链接的默认行为就是在被单击时导航到其href指定的URL。如果想阻止链接导航这一默认行为,那么通过onclick事件处理程序可以取消它。
var link = document.getElementById("myLink");
link.onclick = function(event) {
event.preventDefault();
};
另外只有cancelable属性设置为true的事件,才可以使用preventDefault()来取消去默认行为。
另外,stopPropagation()方法用于立即停止事件在DOM层次中的传播,即取消进一步的事件捕获或冒泡。
例如,直接添加到一个按钮的事件处理程序可以调用stopPropagation(),从而避免触发注册在document.body上面的事件处理程序。
var btn = document.getElementById("myBtn");
btn.onclick = function(event) {
alert("Clicked");
event.stopPropagation();
};
document.body.onclick = function(event) {
alert("Body clicked");
}
事件在目标阶段被触发后,不会传播到document.body,因此就不会触发注册在这个元素上的onclick事件处理对象。
事件对象的eventPhase属性,可以用来确定事件当前位于事件流的哪个阶段。
- 捕获阶段:1
- 目标阶段:2
- 冒泡阶段:3
var btn = document.getElementById("myBtn");
btn.onclick = function(event) {
console.log(event.eventPhase); // 1
}
document.body.addEventListener("click",function(event) {
console.log(event.eventPhase);
},true); // 2
document.body.onclick = function(event) {
console.log(event.eventPhase);
}; // 3
首先执行的事件处理程序是在捕获阶段触发的添加到document.body的那一个,结果会弹出一个警告框显示表示eventPhase的1。接着,会触发在按钮上注册的事件处理程序,此时的eventPhase值为2。最后一个被触发的事件处理程序,是在冒泡阶段执行的添加到document.body上的那一个,显示eventPhase的值为3。
当eventPhase等于2时,this、target、currentTarget始终相等。
只有事件处理程序执行期间,event对象才会存在;一旦事件处理程序执行完成,event就会销毁。
IE中的事件对象
与访问DOM中的event对象不同,要访问IE中的event对象有几种不同方式,取决与执行事件处理程序的方法。在使用DOM0级方法添加事件处理程序时,event对象作为window对象的一个属性存在。
var btn = document.getElementById("myBtn");
btn.onclick = function(event) {
var event = window.event;
alert(event.type); // "click"
};
在此,我们通过window.event取得了event对象,并检测了被触发事件的类型。
如果使用attachEvent()添加的,那么就会有一个event对象作为参数被传入事件处理程中。
var btn = document.getElementById("myBtn");
btn.attachEvent("onclick",function(event) {
alert(event.type); // "click"
});
如果是通过HTML特性指定:
<input type="button" id="myBtn" value="Click Me" onclick="alert(event.type)">
IE的event对象同样也包含与创建它的事件相关的属性和方法。其中很多属性和方法都有对应的或者相关的DOM属性和方法。与DOM的event对象一样,这些属性和方法也会因为事件类型的不同而不同,但所有事件对象都有这些:
/*
属性/方法 类型 读/写 说明
cancelBubble Boolean 读/写 默认值false,将其设置为true可以取消事件冒泡
returnValue Boolean 读/写 默认值为true,将其设置为false可以取消事件的默认行为
srcElement Element 只读 事件的目标
type String 只读 被触发的事件的类型
/*
因为事件处理程序的作用域是根据它的方式来确定的,所以不能认为this会始终等于事件目标。故而,最好还是使用event.srcElement比较保险。
var btn = document.getElementById("myBtn");
btn.onclick = function(event) {
alert(window.event.srcElement === this); // true
};
btn.attachEvent("onclick",function(event) {
alert(event.srcElement === this); // false
});
在第一个事件处理程序中(使用DOM0级方法),srcElement属性等于this,但在第二个事件处理程序中,这两者的值不同。
returnValue属性相当于DOM中的preventDefault()方法,它们的作用都是取消给定事件的默认行为,只要将returnValue设置为false,就可以阻止默认行为。
var link = document.getElementById("myLink");
link.onclick = function() {
window.event.returnValue = false;
};
相应的,cancelBubbles属性与DOM中的stopPropagation()方法相同,都是用来停止事件冒泡的。由于IE不支持事件捕获,因为只能取消事件冒泡;但stopPropagation()可以同时取消事件捕获和冒泡。例如:
var btn = document.getElementById("myBtn");
btn.onclick = function() {
alert("Clicked");
window.event.cancelBubble = true;
};
document.body.onclick = function() {
alert("Body clicked");
};
跨浏览器事件对象
虽然DOM和IE中的event对象不同,但基于它们之间的相似性依旧可以拿出跨浏览器的方案来。
IE中event对象的全部信息和方法DOM对象中都有,只不过实现方式不一样。
var EventUtil = {
addHandler: function (element, type, handler) { // 跨浏览器添加事件处理程序
// 省略
},
getEvent: function (event) { // 跨浏览器得到事件对象
return event ? event : window.event;
},
getTarget: function (event) { // 跨浏览器得到事件目标
return event.target || event.srcElement;
},
preventDefault: function (event) { // 跨浏览器阻止事件传播(IE为取消事件冒泡)
if (event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
}
},
removeHandler: function (element, type, handler) { // 跨浏览器移除事件处理程序
// 省略
},
stopPropagation: function (event) { // 跨浏览器组织默认行为
if (event.stopPropagation) {
event.stopPropagation();
} else {
event.cancelBubble = true;
}
}
};
事件类型
Web浏览器中可能发生的事件有很多类型。不同的事件类型具有不同的信息,而”DOM3及事件“规定了以下几类事件。
- UI(User Interface)事件,当用户与页面上的元素交互时触发;
- 焦点事件,当元素获得或失去焦点时触发;
- 鼠标事件,当用户通过鼠标在页面上执行操作时触发;
- 滚轮事件,当使用鼠标(或类似设备)时触发;
- 文本事件,当在文档中输入文本时触发;
- 键盘事件,当用户通过键盘在页面上执行操作时触发;
- 合成事件,当为IME(Input Method Editor 输入法编辑器)输入字符时触发;
- 变动(mutation)事件,当底层DOM结构发生变化时触发。
除了这几类事件之外,HTML5也定义了一组事件,而有些浏览器还会在DOM和BOM中实现其他专有事件。这些专有的事件一般都是根据开发者需求定制的。
UI事件
UI事件指的是那些不一定与用户操作有关的事件。这些事件在DOM规范出现之前,都是以这种或那种形式存在的,而在DOM规范中保留是为了向后兼容。现有的UI事件如下。
- DOMActivate:表示元素已经被用户操作(通过鼠标或键盘)激活,这个事件在DOM3级事件中被废弃。
- load:当页面完全加载后在window上面触发,当所有框架都加载完毕时在框架集上面触发,当图像加载完毕时在<Img>元素上面触发,或者当嵌入的内容加载完毕时在<object>元素上面触发。
- unload:当页面完全卸载后在window上面触发,当所有框架都卸载后在框架集上面触发,或者当嵌入的内容卸载完毕后<object>元素上面触发。
- abort:在用户停止下载过程时,如果嵌入的内容没有加载完,则在<object>元素上面触发。
- error:当发生JavaScript错误时在window上面触发,当无法加载图像时在<img>元素上面触发,无法加载嵌入内容时<object>上触发,或者当一或多个框架无法加载时在框架集上触发。
- select:当用户选择<input>或<textarea>中一或多个字符时被触发。
- resize:当窗口或框架的大小变化时在window或框架上面触发。
- scroll:当用户滚动带滚动条的元素中的内容时,在该元素上面触发。<body>元素中包含所加载页面的滚动条。
多数这些事件斗鱼window对象或表单控件相关。
load事件
JavaScript中最常用 的一个事件就是load。当页面完全加载后(包括所有图像、JavaScript文件、CSS文件等外部资源),就会触发window上面的load事件。有两种定义onload事件处理程序的方式。
EventUtil.addHandler(window,"load",function(event) {
alert("Loaded!");
});
这是通过JavaScript来指定事件处理程序的方式,使用了之前跨浏览器的EventUtil对象。同样的,这里也给事件传入了一个event对象。这个event对象中不包括有关这个事件的任何附加信息,但在兼容DOM浏览器中,event.target属性的值会被设置为document,而IE并不会为这个事件设置srcElement属性。
第二种指定onload事件处理程序的方式是为body元素添加一个onload特性。
<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
</head>
<title>Load Event Example</title>
<body onload="alert('Loaded!')">
</body>
</html>
一般来说,在window上面发生的任何事件都可以在body元素中通过相应的特性来指定,因为在HTML中无法访问window元素。实际上,这只是为了保证向后兼容的一种权宜之计。建议使用JavaScript方式。
图像上面也可以触发load事件。
<img src="smile.gif" onload="alert('Image loaded.')">
这样当图像加载完毕后会显示一个警告框。同样的,也可以使用JavaScript。
var img = document.getElementById("myImage");
EventUtil.addHandler(img,"load",function(event) {
event = EventUtil.getEvent(event);
alert(EventUtil.getTarget(event).src);
})
这里,使用JavaScript指定了onload事件处理程序。同时也传入了event对象,尽管它也不包含什么有用的信息。不过,事件的目标是<img>元素,因此可以通过src属性访问并显示该信息。
在创建新的<img>元素时,可以为其指定一个事件处理程序,以便图像加载完毕后给出提示。此时,最重要的是要在指定src属性之前指定事件。
EventUtil.addHandler(window,"load",function() {
var image = document.createElement("img");
EventUtil.addHandler(image,"load",function(event) {
event = EventUtil.getEvent(event);
alert(EventUtil.getTarget(event).src);
});
document.body.appendChild(image);
image.src = "smile.gif";
});
首先为window指定了onload事件处理程序,原因在于,向DOM中添加一个新元素,所以确保页面已经加载完毕——如果在页面加载前操作document.body会导致错误。然后,创建一个新的图像元素,并设置了其onload事件处理程序。最后又将这个图像添加到页面中,还设置了它的src属性。这里有一点需要注意:新图像元素不一定要从添加到文档后才开始下载,只要设置了src属性就会开始下载。
还有一些元素也以非保准的方式支持load事件。在IE9+,Firefox,opera,chrome,safari3+及更高版本中,<script>元素也支持load事件。以便开发者确定动态加载的JavaScript文件是否加载完毕。只有在设置了<script>元素的src属性并将该元素添加到文档后,才会开始下载JavaScript文件,这和img元素不同。换句话说,对于<script>元素而言,指定src属性和指定事件处理程序的先后顺序不重要了。
EventUtil.addHandler(window,"load",function() {
var script = document.createElement("script");
EventUtil.addHandler(script,"load",function(event) {
alert("Loaded");
});
script.src = "example.js";
document.body.appendChild(script);
});
unload事件
与load事件对应的是unload事件,这个事件在文档被完全卸载后触发。只要用户从一个页面切换到另一个页面,就会发生unload事件,而利用这个事件最后情况是清除引用,比避免内存泄漏。
EventUtil.addHandler(window,"unload",function(event) {
alert("Unloaded");
});
此时生成的event对象在兼容DOM的浏览器中只包含target属性(值为document)。ie8及之前版本则为这个事件对象提供了srcElement属性。
指定事件处理程序的第二种方式,也是为body元素添加一个特性。
<body onunload="alert('Unloaded!')">
无论使用哪种方式,都要小心编写onunload事件处理程序中的代码。既然unload事件是在一切都被卸载后触发,那么页面加载后存在的某些对象此时就不一定存在了,此时,操作DOM节点或者元素的样式就会导致错误。
resize事件
当浏览器窗口被调整到一个新的高度或宽度时,就会触发resize事件。这个事件在window上面触发,因此可以通过JavaScript或者body元素中的onresize特性来指定事件处理程序。不过,还是推荐JavaScript方式。
EventUtil.addHandler(window,"resize",function(event) {
alert("Resized");
});
关于何时触发resize事件,不同浏览器有不同机制。IE,Safari,Chrome,Opera会在浏览器变化了1像素时就触发resize事件,然后随着变量不断重复触发。Firefox只会在用户停止调整窗口大小时触发resize事件。由于存在差异,注意不要在这个事件处理程序中加入大计算量代码,因为这些代码有可能被频繁执行,从而导致浏览器反应明显变慢。
浏览器窗口最小化或最大化也会触发resize事件
scroll事件
虽然scroll事件是在window对象上发生的,但他实际表示的是页面中相应元素的变化。在混杂模式下,可以通过<body>元素的scrollLeft和scrollTop来监控这一变化。
EventUtil.addHandler(window,"scroll",function(event) {
if (document.compatMode == "CSS1Compat") {
alert(document.documentElement.scrollTop);
} else {
alert(document.body.scrollTop);
}
});
以上代码指定的事件处理程序会输出页面的垂直滚动位置——根据呈现模式不同使用了不同的元素。
与resize事件类似,scroll事件也会在文档被滚动期间重复被触发,尽量保持事件处理程序代码简单。
焦点事件
焦点事件会在页面元素获得或失去焦点时触发。利用这些事件并与document.hasFocus()方法及document.activeElement属性配合,可以知晓用户在页面上的行踪。