DOM(Document Object Model) 即文档对象模型,是针对HTML和XML文档的一个API,DOM描绘了一个层次化的节点树,允许开发人员进行添加、移除和修改页面的某一部分。
一、DOM事件的级别
-
DOM0 点击事件的JS中写法是:
element.onclick=function(){ }
,如果在HTML中就是onclick
属性上加一个JS语句。
删除DOM0事件处理程序,只要将对应事件属性置为null即可,即element.onclick=null
。 -
DOM2 新增的点击事件JS中写法是:
element.addEventListener('click', function(){}, false)
。最后一个参数为true
的时候表示在捕获阶段调用程序,如果是false,表示在冒泡阶段调用事件处理程序,不填则默认为false
。
删除DOM2事件处理程序,用removeEventListener
实现。
IE中的DOM2级事件处理使用了attachEvent
来实现,IE9以下版本只支持冒泡事件,所以attachEvent
添加的事件都是冒泡阶段。attachEvent
添加的事件第一个参数是onclick
而非标准事件中的click
。使用detachEvent
实现删除事件。 -
DOM3 定义方式没变,知识新增了很多事件类型,包括UI事件,鼠标事件,焦点事件,滚轮事件等等。如:
element.addEventListener('keyup', function(){}, false)
因为DOM1主要专注于HTML文档和XML文档,没有涉及事件处理,所以事件处理直接从DOM0跳到DOM2。
二、DOM事件模型
DOM事件模型分为两类:一类是IE所使用的冒泡型事件(Bubbling);另一类是DOM标准定义的冒泡型与捕获型(Capture)的事件。除IE外的其他浏览器都支持标准的DOM事件处理模型。
冒泡型事件处理模型(Bubbling)
如上图所示,冒泡型事件处理模型在事件发生时,首先在最精确的元素上触发,然后向上传播,直到根节点。反映到DOM树上就是事件从叶子节点传播到根节点。捕获型事件处理模型(Captrue)
相反地,捕获型在事件发生时首先在最顶级的元素上触发,传播到最低级的元素上。在DOM树上的表现就是由根节点传播到叶子节点。标准的DOM事件处理模型
标准的事件处理模型分为三个阶段:
(1) 父元素中所有的捕获型事件(如果有)自上而下地执行
(2) 目标元素的冒泡型事件(如果有)
(3) 父元素中所有的冒泡型事件(如果有)自下而上地执行
三、DOM事件流
DOM标准采用捕获+冒泡。两种事件流都会触发DOM的所有对象,从document对象开始,也在document对象结束。
标准DOM事件流包括三个阶段:事件捕获阶段,处于目标阶段和事件冒泡阶段
- 事件捕获阶段:实际目标(<div>)在捕获阶段不会接收事件。也就是在捕获阶段,事件从document到<html>再到<body>就停止了。上图中为1~3。
- 处于目标阶段:事件在<div>上发生并处理。但是事件处理会被看成是冒泡阶段的一部分。
- 冒泡阶段:事件又传播回文档。
四、DOM事件捕获的具体流程
首先接收的是window,然后是ducument,再是html标签(js获取html节点用
document.documentElement
),然后是body,最后随着节点父子关系一级一级往下传,直到目标元素。冒泡则相反,从目标元素到window一级一级往上。
通过代码来描述事件捕获过程:
<div id="ev" style='width:100px;height: 100px;background: blue'></div>
// 把打印顺序搅乱,以免误以为是因为执行顺序影响
<script>
var ev = document.getElementById('ev');
ev.addEventListener('click', function (e) {
console.log('ev captrue');
}, true);
window.addEventListener('click', function (e) {
console.log('window captrue');
}, true);
document.addEventListener('click', function (e) {
console.log('document captrue');
}, true);
document.body.addEventListener('click', function (e) {
console.log('body captrue');
}, true);
document.documentElement.addEventListener('click', function (e) {
console.log('html captrue');
}, true);
// 打印结果如下:
// window captrue
// document captrue
// html captrue
// body captrue
// ev captrue
</script>
通过代码来描述事件冒泡过程:
<div id="ev" style='width:100px;height: 100px;background: blue'></div>
// 把打印顺序搅乱,以免误以为是因为执行顺序影响
<script>
var ev = document.getElementById('ev');
ev.addEventListener('click', function (e) {
console.log('ev captrue');
}, false);
window.addEventListener('click', function (e) {
console.log('window captrue');
}, false);
document.addEventListener('click', function (e) {
console.log('document captrue');
}, false);
document.body.addEventListener('click', function (e) {
console.log('body captrue');
}, false);
document.documentElement.addEventListener('click', function (e) {
console.log('html captrue');
}, false);
// 打印结果如下:
// ev captrue
// body captrue
// html captrue
// document captrue
// window captrue
</script>
五、Event对象的常见应用
-
event.preventDefault()
:阻止默认行为。常用的情况就是给一个<a>标签绑定了click事件,响应函数中设置了event.preventDefault()
,就阻止了链接跳转的行为。 -
event.stopPropagation()
:阻止冒泡行为。比如:一个父级元素绑定了一个事件,子元素绑定了另一个事件,如果想父级元素做一件事,子元素做一件事,两件事是分开的,互不影响,就需要给子元素事件中设置event.stopPropagation()
,否则子元素事件执行时,按照冒泡的原则,父级元素事件也会响应。 -
event.stopImmediatePropagation
:除了该事件的冒泡行为被阻止之外(event.stopPropagation
方法的作用),该元素绑定的后序相同类型事件的监听函数的执行也将被阻止。如一个元素上绑定了2个click事件a和b,如果给a函数中添加event.stopImmediatePropagation
后,则阻止click事件冒泡,并且阻止了b函数的执行。 -
event.currentTarget
:当事件遍历DOM时,标识事件的当前目标(类似于this)。如下例子:
<p>1</p>
<p>2</p>
<p>3</p>
<script>
var ps = document.getElementsByTagName('p');
for (var i = 0; i < ps.length; i++) {
ps[i].addEventListener('click', func, false);
}
function func(e) {
console.log(e.currentTarget); // 打印所点击对应的<p>节点
// 该函数用作事件处理器时: this === e.currentTarget
}
</script>
-
event.target
:表示一个触发事件的对象的引用,常用来实现事件委托。如:
<ul id="ul">
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<script>
document.getElementById("ul").addEventListener('click', function(){
console.log(event.target); // 当点击1时,打印<li>1</li>
console.log(event.currentTarget); // 当点击1或2或3时,都打印整个<ul>
});
</script>
六、自定义事件
以上所讲都是DOM一些自带的事件,当然也可以自己定义一些事件。
- ① 自定义一个事件:new Event()
- ② 给一个dom节点绑定自定义的事件
- ③ 使用dispatchEvent() 分派事件
<div id="ev"></div>
<script>
var ev = document.getElementById('ev');
var evt = new Event('test');
ev.addEventListener('test', function () {
console.log('test dispatch');
});
setTimeout(function () {
ev.dispatchEvent(evt);
}, 3000);
// 打开页面3面后打印 'test dispatch'
</script>