本文深入解析JavaScript中的两种核心事件模型:DOM0级与DOM2级。详细阐述DOM0级事件直接绑定及覆盖特性,并重点讲解DOM2级事件中addEventListener的使用、事件捕获与冒泡的执行顺序,以及如何通过stopPropagation控制事件流。
JavaScript事件模型概述
JavaScript主要包含两种事件模型:DOM0级和DOM2级。此前我对这两者的区别理解不够透彻,通过查阅相关资料后,现在对其机制有了清晰的认识。
DOM0级事件模型机制
一、DOM0级事件模型
DOM0级事件模型属于早期标准,所有浏览器均提供支持,且实现逻辑相对简单。具体代码示例如下:
<p id = 'click'>click me</p>
<script>
document.getElementById('click').onclick = function(event){
alert(event.target);
}
</script>
该模型直接在DOM对象上注册事件名称。例如,在p标签上绑定onclick事件,并在回调函数中输出点击的目标元素。解除绑定的方法更为直接,只需将null赋值给该事件处理函数即可,如下所示:
document.getElementById('click'_).onclick = null;
由此可知,在DOM0模型中,一个DOM对象只能注册一个同类型的事件处理函数。若尝试注册多个同类型函数,后注册的函数会覆盖之前的函数,导致先前的绑定失效。
var click = document.getElementById('click');
click.onclick = function(){
alert('you click the first function');
};
click.onclick = function(){
alert('you click the second function')
}
在上述代码中,我们为DOM对象绑定了两个onclick函数,但实际执行结果仅触发了第二个注册的函数,前一个函数因被覆盖而未执行。
DOM2级事件模型详解
二、DOM2级事件模型
1. 事件捕获与事件冒泡(capture, bubble)
需要注意的是,IE8及以下版本不支持此事件模型。事件捕获和冒泡的运行机制如下图所示:

如图所示,数字123代表事件捕获阶段,4567代表事件冒泡阶段。首先我们参考以下代码逻辑:
<div id = 'outer' style = 'margin: 100px 0 0 100px; width: 200px;height: 200px; background: red;'>
<div id="inner" style = 'margin-left:20px; width: 50px;height:50px; background: green;'></div>
</div>
假设点击ID为inner的div元素,事件流程如下:首先执行捕获阶段,路径为document→html→body→div(outer);随后执行冒泡阶段,路径为div(inner)→div(outer)→body→html→document。
事件捕获与冒泡原理
2. DOM2级的事件注册与解除
DOM2级事件注册与移除方法
在DOM2模型中,使用addEventListener和removeEventListener方法来注册和移除事件(IE8及更早版本不支持)。相比DOM0,这种方法的显著优势在于一个DOM对象可以注册多个同类型事件,且不会发生覆盖,所有绑定的函数会按顺序依次执行。
语法为addEventListener('事件名称', '事件回调', '捕获/冒泡')。具体示例如下:
<div id = 'outer' style = 'margin: 100px 0 0 100px; width: 200px;height: 200px; background: red;'>
<div id="inner" style = 'margin-left:20px; width: 50px;height:50px; background: green;'></div>
</div>
<script>
var click = document.getElementById('inner');
click.addEventListener('click',function(){
alert('click one');
},false);
click.addEventListener('click',function(){
alert('click two');
},false);
</script>
需注意,addEventListener的第一个参数是事件名称,与DOM0级不同,这里不需要加“on”前缀。第三个参数用于指定事件触发阶段:true表示捕获阶段,false表示冒泡阶段。
在上述代码中,我们为inner的div绑定了两个click事件函数,浏览器会依次执行这两个函数。 接下来演示如何利用事件流机制进行控制。
<div id = 'outer' style = 'margin: 100px 0 0 100px; width: 200px;height: 200px; background: red;'>
<div id="inner" style = 'margin-left:20px; width: 50px;height:50px; background: green;'></div>
</div>
<script>
var click = document.getElementById('inner');
var clickouter = document.getElementById('outer');
click.addEventListener('click',function(){
alert('inner show');
},true);
clickouter.addEventListener('click',function(){
alert('outer show');
},true);
</script>
这段代码使用了捕获事件。由于inner嵌套在outer内部,当使用捕获模式时,outer会先于inner捕获到事件。因此执行结果是outer先执行,随后是inner执行。
如果将outer的执行时机改为冒泡阶段呢?
alickouter.addEventListener('click',function(){
alert('outer show');
},false);
此时,执行顺序变为先inner后outer。同理,若将两者的事件触发时机均设为冒泡阶段,结果依然是先执行inner后执行outer。此外,还有一个问题:如果为inner注册两个click事件,一个在捕获阶段,另一个在冒泡阶段(即addEventListener的第三个参数分别设为false和true),执行顺序又是如何?
<script>
var click = document.getElementById('inner');
var clickouter = document.getElementById('outer');
click.addEventListener('click',function(){
alert('capture show');
},true);
click.addEventListener('click',function(){
alert('bubble show');
},false);
</script>
在这种情况下,首先执行的是capture show,其次是bubble show。但这一结果与注册顺序相关,先注册的函数先执行。观察事件捕获和冒泡示意图可知,最终具体的DOM对象只有一个。
那么,如果为outer和inner都注册了click事件,但不希望outer执行,该如何处理?此时需要用到stopPropagation函数。该函数用于阻止事件冒泡,即阻止事件继续向父级传播,从而使得后续注册的同类型事件的DOM对象不再执行。
例如在自制下拉框场景中,点击浏览器其他区域时需隐藏下拉框的options,这时就需要用到stopPropagation。代码如下:
<script>
var click = document.getElementById('inner');
var clickouter = document.getElementById('outer');
click.addEventListener('click',function(event){
alert('inner show');
event.stopPropagation();
},false);
clickouter.addEventListener('click',function(){
alert('outer show');
},false);
</script>
正常情况下,不添加stopPropagation时,执行顺序应为先inner后outer。但在inner的事件函数中添加stopPropagation后,执行完inner的逻辑后,事件冒泡在inner处终止,不再继续向上传播,因此outer的事件函数不会被执行。这可以理解为事件冒泡到inner后消失,后续函数不再运行。
由于事件捕获阶段没有提供阻止事件的函数,因此通常将第三个参数设置为事件冒泡(false)。
以上就是全部内容,希望对大家的学习有所帮助。