理解JS DOM事件

Posted by Rimin on 2019-06-10

DOM 是针对HTML和XML文档定义的一个API, DOM描绘了一个层次化的节点树,允许开发人员添加、移除和修改页面的某一部分。DOM脱胎于 Netscape 及微软公司创始的 DHTML(动态HTML), 但现在它已经成为表现和操作页面标记的真正的跨平台、语言的中立的方式。

发展

  • dom0:
    主要定义了HTML和XML文档的底层结构。在DOM0中,DOM由两个模块组成:DOM Core(DOM核心)和DOM HTML。
  • dom2:
    • DOM视图(DOM Views):定义了跟踪不同文档视图的接口
    • DOM事件(DOM Events):定义了事件和事件处理的接口
    • DOM样式(DOM Style):定义了基于CSS为元素应用样式的接口
    • DOM遍历和范围(DOM Traversal and Range):定义了遍历和操作文档树的接口
  • dom3:
    • DOM加载和保存模块(DOM Load and Save):引入了以统一方式加载和保存文档的方法
    • DOM验证模块(DOM Validation):定义了验证文档的方法
    • DOM核心的扩展(DOM Style):支持XML 1.0规范,涉及XML Infoset、XPath和XML Base

DOM2级和3级的目的在于扩展DOM API以满足操作XML的所有需求同时提供更好的错误处理及特性检测能力

事件对象

  • DOM中的事件对象

image

  • IE中的事件对象

image

可以看出还是有很大差别的,因此未来做到兼容,可以像类似下面这个函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
var EventUtil = {
addHandler: function (element, type, handler){
// some code
},
getEvent: function (event) {
return event ? event : window.event
},
getTarget: function (event) {
return event.target || event.srcElement
},
preventDefault: function (event) {
if(event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false
}
},
removeHandle: function(element, type, handler){
// some code
},
stopPropagation: function(event){
if(event.stopPropagation) {
event.stopPropagation()
} else {
event.cancelBubble = true
}
}
}

事件处理

  • DOM0级事件处理: dom0级事件会覆盖,即只能添加一个事件
    将一个函数赋值给一个事件处理程序属性。简单且具有跨浏览器优势。
1
2
3
4
var btn = document.getElementById('btn');
btn.onclick = function() {
console.log(this.id) // "btn"
}
  • DOM2级事件处理程序:定义了两个方法,好处是可以添加多个事件处理程序
    • addEventListener() 只能用下面的方法移除
    • removeEventListener()
1
2
3
4
5
6
7
8
9
btn.addEventListener('click', function(){
console.log(this.id);
}, false); // 表示在冒泡阶段调用该函数
btn.addEventListener('click', function(){
console.log("another event function of btn execute");
}, false); // 表示在冒泡阶段调用该函数

// 按顺序执行
// 匿名函数无法移除

如果给一个body中的子节点同时注册冒泡和捕获事件,事件触发会按注册的顺序执行

event.stopImmediatePropagation

  • event.stopImmediatePropagation作用在当前节点以及事件链上的所有后续节点上,目的是在执行完当前事件处理程序之后,停止当前节点以及所有后续节点的事件处理程序的运行

  • stopPropagation的在执行完绑定到当前元素上的所有事件处理程序之后,停止执行所有后续节点的事件处理程序

  • IE事件处理程序: 均添加到冒泡阶段

    • attachEvent()
    • detachEvent()
1
2
3
4
btn.attachEvent('onclick', function() {
console.log(this.id) // undefined
console.log(this) // window
});

注意和dom0级和dom2级事件处理程序的区别在于作用域。以及在添加多个事件处理程序时不是按顺序执行,而是以相反的顺序执行。(这里的顺序是《JavaScript高级程序设计》中的,但是经过实验发现ie10,ie9是按顺序的,而ie8以下才按相反顺序)

注意:只有DOM2级事件规定事件包括三个阶段:

1. 事件捕获阶段
2. 处于目标阶段
3. 事件冒泡阶段

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<body>
<div id="p">
parent
<div id="c">
child
</div>
</div>
<script type="text/javascript">
var p = document.getElementById('p'),
c = document.getElementById('c');
c.addEventListener('click', function () {
alert('子节点捕获')
}, true);

c.addEventListener('click', function () {
alert('子节点冒泡')
}, false);

p.addEventListener('click', function () {
alert('父节点捕获')
}, true);

p.addEventListener('click', function () {
alert('父节点冒泡')
}, false);
</script>
</body>

区别总结

  • dom0级

    • 只能在冒泡阶段触发
    • 只能绑定一个事件函数
    • 事件函数this 指向被点击的元素本身
    • 通过置空onclick属性解绑事件
  • dom2级事件

    • 非ie:
      • 可绑定多个事件函数
      • 事件函数this 指向被点击的元素本身
      • 多个事件函数的书法顺序和绑定顺序一样
    • ie
      • 事件函数this属性引用全局对象,因此要用 window.event
      • 多个事件函数的书法顺序和绑定顺序相反
  • dom3级事件:
    规定了以下几种事件:

    • UI事件当用户与页面上的元素交互时触发
    • 焦点事件当元素获得或者失去焦点时触发
    • 鼠标事件当用户通过鼠标在页面上执行操作时触发
    • 滚轮事件当使用鼠标滚轮或类似设备时触发
    • 文本事件当在文档中输入文本时触发
    • 键盘事件当用户通过键盘在页面上执行操作时触发
    • 合成事件当为IMEInput Method Editor输入法编辑器输入字符时触发
    • 变动事件当底层Dom结构发生变化时触发
    • 定义了自定义事件自定义事件不是由DOM原生触发的它的目的是让开发人员创建自己的事件

事件委托(代理)

事件委托利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。

  • 优点:
    • 提高document对象访问效率
    • 在页面中事件处理程序所花的时间少
    • 整个页面占用内存空间更少,能够提高整体性能。
    • 动态添加的元素无需额外注册事件。
    • 删除的元素无需注销事件,因为一般如果元素注册了事件删除后需手动删除以回收内存
  • 缺点(局限)
    • 事件冒泡的过程也需要耗时,越靠近顶层,事件的”事件传播链”越长,也就越耗时
    • 只能用于会冒泡的事件中,比如focus和blur本身是不会冒泡的
    • mouseover和mouseout虽然也有事件冒泡,但是处理它们的时候需要特别的注意,因为需要经常计算它们的位置,处理起来不太容易。
1
2
3
4
5
6
<div id="box">
<input type="button" id="add" value="添加" />
<input type="button" id="remove" value="删除" />
<input type="button" id="move" value="移动" />
<input type="button" id="select" value="选择" />
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
window.onload = function(){
var oBox = document.getElementById("box");
oBox.onclick = function (ev) {
var ev = ev || window.event;
var target = ev.target || ev.srcElement;
if(target.nodeName.toLocaleLowerCase() == 'input'){
switch(target.id){
case 'add' :
alert('添加');
break;
case 'remove' :
alert('删除');
break;
case 'move' :
alert('移动');
break;
case 'select' :
alert('选择');
break;
}
}
}

}
// 通过绑定事件到父元素上再因为冒泡而判断target来判断点击的是哪个子元素

参考:

《JavaScript高级程序设计》

js中的事件委托或事件代理详解

JavaScript 事件委托详解