DOM 级别
此文中涉及到DOM0
和DOM2
级事件,故先行解释
文档对象模型(
DOM
)是一个平台,一个中立于语言的应用程序编程接口(API
),允许程序访问并更改文档的内容、结构和样式。
DOM
被分为不同的部分(核心、XML
及HTML
)和级别(DOM Level 1/2/3
)
DOM
分类
-
DOM
核心:针对任何结构化文档的标准模型 -
DOM XML
:只针对XML
文档的标准模型 -
DOM HTML
:只针对HTML
文档的标准模型
DOM
级别是描述DOM
对象,方法和行为的规范集合。基于先前级别构建的DOM
规范的更高级别。变化发生在两个方面:
- 增加一个全新的规格类别(例如,3级添加了“验证”和“加载和保存”规范,这在2级中不存在)
- 修改现有规范类别(例如更新“核心”规范)
DOM
级别
-
DOM0
级别:不是W3C
规范。而仅仅是对在Netscape Navigator 3.0
和Microsoft Internet Explorer 3.0
中的等价功能性的一种定义。DOM1
级别建立于此功能性之上 -
DOM1
级别:专注于HTML
和XML
文档模型。它含有文档导航和处理功能。于1998年10月1日称为W3C推荐标准
-
DOM2
级别:对DOM1
级别添加了样式表对象模型,并定义了操作附于文档至上的样式信息的功能性。同时还定义了一个事件模型,并提供了对XML
命名空间的支持。作为一项W3C推荐标准
,于2000年11月13日发布 -
DOM3
级别:规定了内容模型(DTD
和Schemas
)和文档验证。同时规定了文档加载和保存、文档查看、文档格式化和关键事件
注:DOM1
级标准中未定义事件相关的内容,故没有所谓的DOM1
级别事件模型
事件
JavaScript
和HTML
的交互是通过事件实现的。事件,就是文档或浏览器窗口中发生的一些特定的交互瞬间。可以使用侦听器(或处理程序)来预定事件,以便事件发生时执行相应的代码。
网页中的每个元素都可以产生某些可以触发JavaScript
函数的事件。比如说,我们可以在用户点击某按钮时产生一个事件来触发某个函数。事件在HTML
中定义
事件举例:
- 鼠标点击
- 页面或图像载入
- 鼠标悬浮于页面的某个热点至上
- 在表单中选取输入框
- 确认表单
- 键盘按键
事件流
事件流描述的是从页面中接受事件的顺序
如图2.1,目标节点是<td>
,当点击<td>
事件的传播如下所示:
[图片上传失败...(image-5bef-1534760955972)]
事件冒泡
事件开始时由最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到较为不具体的节点(文档)。
所由的现在浏览器都支持事件冒泡,但在具体是线上还是有一些差别。IE5.5
及更在版本中的事件冒泡会跳过<html>
元素(从<body>
直接跳到document
)。IE9
、Firefox
、Chrome
和Safari
则将事件一直冒泡到window
对象。
即上图中的3阶段 ,事件传播顺序:<td> - <tr> - <tbody> - <table> - <body> - <html> - <document> - <window>
事件捕获
不太具体的节点更早接收事件,而最具体的元素最后接收事件,与事件冒泡相反
即上图中的1阶段 ,事件传播顺序:<window> - <document> - <html> - <body> - <table> - <tbody> - <tr> - <td>
DOM 事件流
“DOM2
级事件”规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。首先发生的事件捕获,为截获事件提供了机会。然后是实际的目标接收到事件。最后一个阶段是冒泡阶段,可以在这个阶段对事件做出响应。
IE9
、Opera
、Firefox
、Chrome
和Safari
都支持DOM
事件流;IE8
及更早版本不支持DOM
事件流。
即上图中的1,2,3个阶段
事件处理程序
事件就是用户或浏览器自身执行的某种动作。诸如
click
、load
和mouseover
,都是事件的名字。而响应某个事件的函数就叫做事件处理程序(或事件侦听器)。事件处理程序的名字以on
开头,因此click
事件的事件处理程序就是onclick
,load
事件的事件处理程序就是onload
。为事件指定处理程序的方式有好几种。
HTML事件处理程序
元素支持的每个元素都可以使用一个相应事件处理程序同名的HTML
属性指定。这个属性的值应该是可以执行的JavaScript
代码,我们可以为一个button
添加click
事件处理程序
<input type="button" value="Click Here" onclick="alert('Clicked');">
在HTML
事件处理程序中可以包含要执行的具体动作,也可以调用在页面其他地方定义的脚本,刚才的例子可以写成如下形式:
<input type="button" value="Click Here" onclick="showMessage();">
在HTML
中指定事件处理程序书写很方便,但有两个缺点:
- 存在时差问题。因为用户可能会在
HTML
元素一出现在页面上就触发相应的事件,但当时的事件处理程序有可能尚不具备执行条件。
以上面的例子说明,假设showMessage()
函数是在按钮下方、页面的最底部定义的。如果用户在页面解析showMessage()
函数之前就单击了按钮,就会引发错误。为此,很多HTML
事件处理程序都会被封装在一个try-catch
块中,以便错误不会浮出水面,如下面的例子所示:
<input type="button" value="Click Here" onclick="try{showMessage();}catch(ex){}">
这样,如果在showMessage()
函数有定义之前单机了按钮,用户将不会看到JavaScript
错误,因为在浏览器有机会处理错误之前,错误就被捕获了。
-
HTML
代码和JavaScript
代码紧密耦合。如果要更换事件处理程序,就要改动这两个地方:HTML
代码和JavaScript
代码。
DOM0 级事件处理程序
每个元素(包括window
和document
)都有自己的事件处理程序属性,这些属性全部小写,例如onclick
。将这种属性的值设置为一个函数,就可以指定事件处理程序。
使用DOM0
级方法指定的事件处理程序被认为是元素的方法。因此,这时候的事件处理程序是在元素的作用域中进行;换句话说,程序中的this
引用当前元素。例如:
var btn = document.getElementById("myBtn");
btn.onclick = function(){
alert(this.id); // "myBtn"
}
也可以删除通过DOM0
级方法指定的事件处理程序,只要像下面这样将事件处理程序属性的值设为null
即可:
btn.onclick = null; // 删除事件处理程序
将事件处理程序设置为null
后,再单击按钮将不会有任何动作发生。
这种做法的缺点:一次只能定义一个事件处理程序,不能同时定义多个事件处理程序,后定义的事件处理程序会覆盖前面定义的事件处理程序。
DOM2 级事件处理程序
DOM2
级事件定义了两个方法,用于处理指定和删除事件处理程序的操作:addEventListener()
和removeEventListener()
。所有DOM
节点中都包含这两个方法,并且它们都接受3个参数:要处理的事件名、作为事件处理程序的函数和一个布尔值。最后这个布尔值参数如果是true
,表示再捕获阶段调用事件处理程序;如果是false
,表示在冒泡阶段调用事件处理程序;不写的情况下默认为false
。
在按钮上为click
事件添加事件处理程序,如下:
var btn = document.getElementById("myBtn");
btn.addEventListener("click", function(){
alert(this.id);
}, 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()
来移除;移除时传入的参数与添加处理程序时使用的参数相同。这也意味着通过addEventListener()
添加的匿名函数将无法移除。因为匿名函数体虽然方法体一样,但是句柄却不同是,是完全不同的函数,所以应写成:
var btn = document.getElementById("myBtn");
var handler = function(){
alert(this.id);
};
btn.addEventListener("click", handler, false);
btn.addEventListener("click", handler, false);
IE 事件处理程序
IE
实现了与DOM
中类似的两个方法:attachEvent()
和detachEvent()
。这两个方法接受相同的两个参数:事件处理程序名称与事件处理程序函数。由于IE8
及更早版本只支持事件冒泡,所以通过attachEvent()
添加的事件处理程序都会被添加到冒泡阶段。
使用attachEvent()
添加的事件可以通过detachEvent()
来移除,条件是必须提供相同的参数。与DOM
方法一样,这也意味着添加的匿名将不能被移除。不过,只要能够将相同函数的引用传给detachEvent()
,就可以移除相应的事件处理程序。
使用attachEvent()
为按钮添加一个事件处理程序,如下所示:
var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function(){
alert("Clicked");
});
attachEvent()
与addEventListener()
的区别:
参数个数不同。
attachEvent()
的有两个参数,添加的事件只能发生在冒泡阶段;而addEventListener()
有三个参数,都最后一个参数决定添加的事件处理程序是在冒泡阶段还是捕获阶段(一般为了兼容浏览器都设置为冒泡阶段)第一个参数意义不同。
attachEvent()
的第一个参数是事件处理程序名称,例如onclick
;而addEventListener()
方法中第一个参数是事件处理类型,例如click
3.事件处理程序的作用域不同。在使用DOM2
级方法的情况下,事件处理程序会在其所属元素的作用域内运行;在使用attachEvent()
方法的情况下,事件处理程序会在全局作用域中运行,因此this
等于window
。如下:
var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function(){
alert(this === window); // true
});
- 为一个事件添加多个事件处理程序时,执行顺序不同。与
addEventListener()
类似,attachEvent()
也可以为一个元素添加多个事件处理程序,不过,这些事件处理程序不是以添加它们的顺序执行的,而是以相反的顺序被触发。当添加的事件处理程序较多时则顺序无规律。
跨浏览器的事件处理程序
就不同浏览器的差异,对事件处理程序封装,此方法接收三个参数:要操作的元素、事件名称、事件处理程序函数。jQuery
创始人John Resig
做法如下:
function addEvent(node, type, handler){
if(!node){
return false;
}
if(node.addEventListener){
node.addEventListener(type, handler, false);
return true;
}else if(node.attachEvent){
node['e' + type + handler] = handler;
node[type + handler] = function(){
node['e' + type + handler](window.event);
};
node.attachEvent('on' + type, node[type + handler]);
return true;
}
return false;
}
在取消事件处理程序的时候,如下:
function removeEvent(node, type, handler){
if(!node){
return false;
}
if(node.removeEventListener){
node.removeEventListener(type, handler, false);
return true;
}else if(node.detachEvent){
node.detachEvent('on' + type, node[type + handler]);
node[type + handler] = null;
return true;
}
return false;
}
事件对象
在触发
DOM
上的某个事件时,会产生一个事件对象event
,这个对象中包含着所有与事件有关的信息。包括导致事件的元素、事件的类型以及其他与特定事件相关的信息。例如,鼠标操作导致的事件对象中,会包含鼠标位置的信息,而键盘操作导致的事件对象中,会包含与按下的键有关的信息。所有的浏览器都支持event
对象,但支持方式不同。
DOM中的事件对象
兼容DOM
的浏览器会将一个event
对象传入到事件处理程序中。
event
对象包含与创建它的特定事件有关的属性和方法。触发的事件类型不一样,可用的属性和方法也不一样。不过所有事件都会有下表列出的成员。
在事件处理程序内部,对象
this
始终等于currentTarget
的值,而target
则只包含事件的实际目标。如果直接将使事件处理程序指定给了目标元素,则this
、currentTarget
和target
包含相同的值。
阻止事件的默认行为
要阻止特定事件的默认行为,可用使用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(e){
alert('Clicked');
e.stopPropagation()
};
document.body.onclick = function(e){
alert('Body clicked');
};
调用stopPropagation()
后事件不会传到document.body
,因此不会触发注册在document.body
上的onclick
事件处理程序。
只有在事件处理程序执行期间,
event
对象才会存在;一旦事件处理程序执行完成,event
对象就会被销毁。
IE 中的事件对象
IE
中的event
对象有几种不同的方式,取决于指定事件处理程序的方法。直接为DOM
元素添加事件处理程序时,event
对象作为window
对象的一个属性存在,如下所示:
var btn = document.getElementById("myBtn");
btn.onclick = function(){
var e = window.event;
alert(e.type); // "click"
};
可是,如果事件处理程序是使用attachEvent()
添加的,那么就会有一个event
对象作为参数被传入事件处理程序函数中,如下所示:
var btn = document.getElementById("myBtn");
btn.attachEvent('onclick', function(event){
alert(event.type); // "click"
});
像这样使用attachEvent()
的情况下,也可以提供window
对象来访问event
对象,就像使用DOM0
级方法一样。不过为方便起见,同一个对象也会作为参数传递。
如果是通过HTML
特性指定的事件处理程序,那么还可以通过一个名叫event
的变量来访问event
对象(与DOM
中的 事件模型相同),如下所示:
<input type="button" value="Click Me" onclick="alert(event.type)">
IE
的event
对象同样也包含与创建它的事件相关的属性和方法。其中很多属性和方法都有对应的或者相关的DOM
属性和方法。与DOM
的event
对象一样,这些属性和方法也会因为事件类型的不同而不同,但所有事件对象都会包含下表所列的属性和方法。
注:使用returnValue
达到了阻止默认行为的目的,cancelBubble
设为true
可阻止事件冒泡。
跨浏览器的事件对象
虽然DOM
和IE
的event
对象不同,但基于它们的相似性,我们还是可以写出跨浏览器的事件对象方案
function getEvent(e){
return e || window.event;
}
function getTarget(e){
return e.target || e.srcElement;
}
function preventDefault(e){
if(e.preventDefault){
e.preventDefault();
}else {
e.returnValue = false;
}
}
function stopPropagation(e){
if(e.stopPropagation){
e.stopPropagation();
}else {
e.cancelBubble = true;
}
}
事件冒泡的应用
事件代理,又称为事件委托。
例如:要求当点击每一个元素li
时控制台展示该元素的文本内容。不考虑兼容,如下所示
<ul class="ct">
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<script>
var ct = document.querySelector('.ct');
ct.addEventListener('click', function(e){
var target = e.target;
if(target.tagName.toLowerCase() === 'li'){
console.log(target.innerText);
}
})
</script>
事件类型
Web
浏览器中可能发生的事件有很多类型,DOM3
级事件规定了以下几类事件:
-
UI
(User Interface
,用户界面)事件,当用户与页面上的元素交互时触发 - 焦点事件,当元素获得或失去焦点时触发
- 鼠标事件,当用户通过鼠标在页面上执行操作时触发
- 滚轮事件,当使用鼠标滚轮(或类似设备)时触发
- 文本事件,当在文档中输入文本时触发
- 键盘事件,当用户通过键盘在页面上执行操作时触发
- 合成事件,当为
IME
(Input Method Editor
,输入法编辑器)输入字符时触发 - 变动(
mutation
)事件,当底层DOM
结构发生变化时触发
此处不想述,具体的可以查看JavaScript
程序设计
参考:
- W3C DOM
- DOM 分类
- DOM 规范
- JavaScript 高级程序设计
- JavaScript 事件流
- DOM 事件结构