JavaScript 事件机制详解:事件流、处理程序与事件对象

作者:流萤时间:2026-10-23 00:20:07浏览:659

本文系统梳理 JavaScript 事件机制,深入解析事件冒泡与捕获的事件流原理,对比 HTML、DOM0、DOM2 及 IE 兼容的事件处理程序实现方式,并详细列举 DOM 与 IE 中事件对象的关键属性与方法,帮助开发者掌握跨浏览器事件处理的最佳实践。

JavaScript 事件机制概述

JavaScript 与 HTML 的交互主要通过事件机制来完成。所谓事件,是指文档或浏览器窗口中发生的特定交互瞬间。开发者可以通过预定(或处理)事件,确保在事件发生时执行对应的代码逻辑。这种机制在传统软件工程中对应观察者模式,它实现了页面行为与外观之间的松散耦合。本文将围绕 JavaScript 事件的基础知识展开介绍。

事件流原理

一、事件流

事件流描述了在页面中接收事件的先后顺序。

事件冒泡

事件冒泡是指事件从触发该事件的最具体元素(即文档中嵌套层次最深的节点)开始,逐级向上传播至较不具体的节点(最终到达文档)。以点击页面中的按钮为例,click 事件会按照 button、body、html、document 的顺序进行传播。简而言之,事件冒泡意味着事件从底层触发元素沿 DOM 树向上冒泡,直至 document 对象。


<html>
 <head>
  <title>Test</title>
 </head>
 <body>
  <button id="myBtn">A Btn</button>
 </body>
</html>

事件捕获

与事件冒泡相反,事件捕获的理念是较不具体的节点应更早接收到事件,而最具体的节点最后接收。沿用上述点击按钮的例子,click 事件会按照 document、html、body、button 的顺序传播。也就是说,事件捕获是指事件从 document 对象开始沿 DOM 树向下传播,直到实际的目标元素。

DOM事件流

“DOM2级事件”规范规定的事件流程包含三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。首先发生的是事件捕获,这为截获事件提供了机会;接着是实际的目标接收到事件;最后是冒泡阶段,开发者可以在该阶段对事件做出响应。

继续以点击按钮为例,在 DOM 事件流中,捕获阶段 click 事件从 document 向下传递至 body 元素(注意,实际目标 button 在捕获阶段不会接收到事件);目标阶段,button 元素接收到 click 事件;最后,在冒泡阶段,事件又传播回文档。

事件处理程序实现方式

二、事件处理程序

事件是用户或浏览器自身执行的某种动作,而响应某个事件的函数被称为事件处理程序或事件。

HTML事件处理程序

这里的 HTML 事件处理程序指的是直接在 HTML 元素内部通过属性(attribute)定义的事件处理程序,请参考以下代码示例。这样定义的事件处理程序会创建一个封装着元素属性值的函数,this 值等于事件的目标元素。通过这种方法指定事件处理程序存在诸多缺点,因此不推荐使用。


<button onclick="alert('HaHa~')">Btn-1</button>
<button onclick="alert('event.type')">Btn-2</button>
<button onclick="handler()">Btn-3</button>
<script type="text/javascript">
 function handler() {
  alert("Haha~");
 }
</script>

DOM0级事件处理程序

通过 JavaScript 指定事件处理程序的传统方式是将一个函数赋值给一个事件处理程序属性,请参考以下代码示例。通过这种方式指定的事件处理程序是在元素的作用域中运行,this 引用的是当前元素。这种方式添加的事件处理程序会在事件流的冒泡阶段被处理。若要删除事件,直接令 onclick 的值为空即可。


var btn = document.getElementById("myBtn");
btn.onclick = function() {
 console.log("this.id"); // "myBtn"
};
// 删除事件处理程序
btn.onclick = null;

DOM2级事件处理程序

“DOM2级事件”定义了两个方法用于指定和删除事件处理程序:addEventListener() 和 removeEventListener()。所有 DOM 节点中都包含这两个方法。这两个方法都接收 3 个参数:要处理的事件、处理函数、布尔值。最后的布尔值为 true 时表示在捕获阶段调用事件处理程序,为 false 时表示在冒泡阶段调用处理程序。与 DOM0 级方法一样,这里添加的事件处理程序也是在其依附的元素的作用域中运行。DOM2 级方法添加事件处理程序的优势是可以添加多个事件处理程序。这些事件处理程序会按照它们被添加的顺序触发。下面是代码示例:


var btn = document.getElementById("myBtn");
// 添加,触发点击事件时先输出"myBtn"再输出"HaHa~"
btn.addEventListener("click", function() {
 console.log(this.id);
}, false);
btn.addEventListener("click", function() {
 console.log("HaHa~");
}, false);

通过 addEventListener() 添加的事件只能通过 removeEventListener() 来删除。删除时传入的参数与添加时使用的参数应该保持一致。这也意味着通过 addEventListener() 添加的匿名函数将无法删除,因为无法将添加时传递的匿名函数传给 removeEventListener(),即便在删除的时候写了一个一模一样的函数,但此时这个函数只是一个新的匿名函数。请看下面代码示例:


var btn = document.getElementById("myBtn");
// 无法删除匿名函数
btn.addEventListener("click", function() {
 console.log(this.id);
}, false);
btn.removeEventListener("click", function() {
 console.log(this.id);
}, false);

// 正确的添加和删除方式
function handler() {
 console.log(this.id);
}
btn.addEventListener("click", handler, false);
btn.removeEventListener("click", handler, false);

大多数情况下,都是将事件处理程序添加到事件流的冒泡阶段,这样可以最大限度地兼容各种浏览器。最好只在需要在事件到达目标之前截获它的时候才将事件处理程序添加到捕获阶段。JS高级程序设计上给出的建议是,如果不是特别需要,不建议在事件捕获阶段注册事件处理程序。

IE事件处理程序

IE 实现了与 DOM 中类似的两个方法:attachEvent() 和 deleteEvent()。这两个方法接收两个参数,事件处理程序名称和事件处理程序。注意,第一个参数是事件处理程序名称而不是事件名称,也就是说在注册点击事件的处理程序时应该传入 “onclick” 而不是 “click”,这里跟 DOM 的方法有些差别。另外,这两个方法注册的事件处理程序是在全局作用域中运行而不是元素作用域,this 的值指向 window。还有一点需要特别小心,通过 attachEvent() 方法也可以添加多个事件处理程序,但是它们的执行顺序却不是按照它们被添加的顺序,而是完全相反,跟 DOM 方法截然不同。下面是代码示例:


var btn = document.getElementById("myBtn");
function handler1() { // ... }
function handler2() { // ... }
// 添加,触发点击事件时先执行handler2再执行handler1
btn.attachEvent("onclick", handler1);
btn.attachEvent("onclick", handler2);
// 删除
btn.deleteEvent("onclick", handler1);
btn.deleteEvent("onclick", handler2);

事件对象详解

三、事件对象

在触发 DOM 上的某个事件时,会产生一个事件对象 event,这个对象中包含所有与事件有关的信息,包括导致事件的元素、事件的类型以及其他与特定事件相关的信息。

DOM中的事件对象

兼容 DOM 的浏览器会将一个 event 对象传入事件处理程序中,无论指定事件处理程序时用的是 DOM0 还是 DOM2 的方法,都会传入 event 对象。event 对象只有在事件处理程序执行期间才会存在,一旦事件处理程序执行完毕,event 对象就会被销毁。下面是代码示例:


var btn = document.getElementById("myBtn");
btn.onclick = function(event) {
 console.log(event.type); // "click"
}
btn.addEventListener("click", function(event) {
 console.log(event.type);
}, false);

event 对象包含与创建它的特定事件有关的属性和方法,触发的事件类型不一样,可用的属性方法也有所不同。但是所有的事件都会有下列的属性或方法:

  • bubbles: 布尔值,表示事件是否冒泡
  • cancelable: 布尔值,表示是否可以取消事件的默认行为
  • currentTarget: 元素,事件处理程序当前正在处理事件的那个元素
  • defaultPrevented: 布尔值,表示是否调用过 preventDefault() 方法
  • detail: 整数,与事件相关的细节信息
  • eventPhase: 整数,调用事件处理程序的阶段,1 表示捕获阶段,2 表示目标阶段,3 表示冒泡阶段
  • preventDefault(): 函数,取消事件的默认行为,cancelable 为 true 时可以调用该方法
  • stopImmediatePropagation(): 函数,取消事件的进一步捕获或冒泡,同时阻止任何事件处理程序被调用
  • stopPropagation(): 函数,取消事件的进一步捕获或冒泡,bubbles 为 true 时可以调用这个方法
  • target: 元素,事件的目标
  • trusted: 布尔值,为 true 时表示事件是浏览器生成的,否则表示事件是通过 JS 创建的
  • type: 字符串,被触发的事件类型
  • view: 与事件关联的抽象视图,等同于发生事件的 window 对象

下面代码示例展示了上述部分属性的用法,也可以帮助我们进一步理解事件流。假设页面中有一个按钮 “myBtn”。当点击按钮时,this 和 currentTarget 都等于 body 元素,因为事件处理程序是注册在 body 元素上。target 的值却等于按钮元素,因为它是 click 事件的真正目标。由于按钮上没有注册事件处理程序,结果 “click” 事件冒泡到了 document.body 那里才得到处理。


document.body.onclick = function(event) {
 console.log(event.currentTarget === document.body); // true
 console.log(this === document.body); // true
 console.log(event.target === document.getElementById("myBtn")); // true
};

再看一个例子,下面代码中,stopPropagation() 方法取消了事件的进一步捕获或冒泡。当我点击按钮时,本来应该会因为事件冒泡机制触发按钮和 body 元素上的点击事件处理程序,输出 “From Bth …” 和 “From Body …”。现在点击事件在按钮元素上触发之后就被阻止继续在 DOM 层次中的传播,因此 body 上的事件处理程序不会被触发。


var btn = document.getElementById("myBtn");
btn.onclick = function(event) {
 console.log("From Bth ...");
 event.stopPropagation(); // 停止事件传播
};
document.body.onclick = function() {
 console.log("From Body ...");
};

IE中的事件对象

在 IE 中,使用 DOM0 的方法添加事件处理程序时,event 对象作为 window 对象的一个属性存在。如果是通过 attachEvent() 方法添加,则 event 对象是作为参数传入事件处理函数。下面是代码示例:


var btn = document.getElementById("myBtn");
btn.onclick = function() {
 var event = window.event;
 console.log(event.type); // "click"
};
btn.attachEvent("onclick", function(event) {
 console.log(event.type); // "click"
});

IE 的 event 对象同样也包含与创建它的事件相关的属性和方法,这些属性和方法也会因为事件类型的不同而有所差异。但所有事件对象都会包含下列属性:

  • cancelBubble: 布尔值,可读可写,默认为 false。将其设置为 true 时取消事件冒泡
  • returnValue: 布尔值,可读可写,默认为 true。将其设置为 false 时取消事件的默认行为
  • srcElment: 元素,事件的目标元素,与 DOM 中的 target 属性相同
  • type: 字符串,事件类型

在 IE 中,事件处理程序的作用域是根据指定它的方式来确定,this 的值不一定是指向事件的目标元素。因此,使用 srcElement 属性更具保险。请看下面代码实例,第一种方式中 this 的值为目标元素,而第二种方式,前面讲过这种方式的事件处理程序是在全局作用域中执行,因此 this 的值为 window。


var btn = document.getElementById("myBtn");
btn.onclick = function() {
 console.log(window.event.srcElement === this); // true
}
btn.attachEvent("onclick", function(event) {
 console.log(event.srcElement === this); // false
});

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持本站!

作者最新文章