Published on

深入理解DOM、BOM和事件模型

Authors
  • avatar
    Name
    青雲
    Twitter

当网页被加载和解析时,浏览器会创建两个非常重要的客户端模型来操作网页和与用户的交互,这两个模型分别是 BOM(Browser Object Model)和 DOM(Document Object Model)。与这两个模型的交互通常是通过事件模型(Event Model)实现的。BOM、DOM和事件模型在Web开发中紧密相连,共同构成了创建互动Web应用程序的基础。

DOM

DOM(Document Object Model)是对HTML和XML文档的编程接口。在web浏览器中,DOM被实现为一个树状结构,表示页面结构。DOM为程序员提供了改变文档结构、样式和内容的能力。简单地说,DOM是连接网页文档和脚本语言的桥梁,它使得开发者能够用脚本语言动态地访问和更新文档的内容、结构和样式。

特点和功能

BOM 主要特点如下:

  1. 文档结构表示:DOM提供了一个与语言和平台无关的接口,使得程序可以动态地访问和更新文档的内容、结构和样式。
  2. 节点树结构:文档被描述为一棵节点树,每一个HTML元素,属性,甚至注释都是一个节点。这些节点之间有父子和兄弟关系。
  3. 交互性:DOM可以响应用户的操作,例如鼠标点击和键盘事件,并且可以被脚本语言(比如JavaScript)更新,以改变文档展示。

DOM API 提供了丰富的功能:

  1. 节点遍历和操作:例如 document.getElementByIddocument.querySelectorelement.appendChildelement.removeChild 等方法。
  2. 属性操作:可以通过 DOM 节点对象获取、设置、移除 HTML 属性。
  3. 事件处理:如 addEventListenerremoveEventListener 方法,允许你响应用户行为或系统触发的事件。
  4. 样式操作:通过 element.style 来读取或改写元素的样式。

DOM 应用场景示例

动态内容更新

使用 JavaScript 来动态更新页面内容。例如,当用户提交表单后,可以不刷新页面即时显示提交结果。

document.getElementById('result').textContent = '提交成功!';

表单验证

在用户提交表单之前,用 JavaScript 进行客户端的表单验证。

if (document.getElementById('email').value.includes('@')) {
  // 邮箱验证通过
} else {
  // 提示用户邮箱不符合要求
}

页面布局变化

将布局相关的更改如显示或隐藏元素,响应弹窗等都可以通过DOM进行。

document.getElementById('popup').style.display = 'none'; // 隐藏弹窗
document.getElementById('popup').style.display = 'block'; // 显示弹窗

动画和视觉效果

通过 DOM 操作类和样式属性来创建动画和视觉效果。

element.classList.add('fade-in');

BOM

BOM 是一系列对象,代表了浏览器的窗口及其表现和功能。简而言之,BOM 让你可以使用 JavaScript 与浏览器对话。尽管没有官方规范,但所有现代浏览器都实现了类似的 BOM,其中最核心的对象是 window,它代表了浏览器窗口,并充当了 JavaScript 全局执行环境的一部分。

特点和功能

BOM 主要特点如下:

  1. 浏览器交互:BOM提供了一组对象和方法,可以让开发者与Web浏览器进行交互,而不限于文档内容。
  2. 浏览器特定对象:BOM包含很多针对浏览器的对象,比如window、location、navigator、history等,它们提供了控制浏览器行为的方法和属性。
  3. 无标准化:每个浏览器可能实现BOM的细节有所不同,因为BOM没有统一的标准(尽管有努力使其更加一致)。

BOM 主要提供了与浏览器窗口交互的方法和属性,其中包括:

  1. 窗口尺寸和滚动信息:例如 window.innerWidthwindow.innerHeight、window.scrollXwindow.scrollY
  2. 导航控制:例如 window.location 对象允许得到当前页面地址,可以改变 window.location.href 跳转到新页面。
  3. 计时器功能:例如 setTimeoutsetInterval 函数,以及它们的相对应取消函数 clearTimeoutclearInterval
  4. 弹框功能:如 alertconfirmprompt 函数。
  5. 跨域通信:如使用 window.postMessage 方法。
  6. 本地存储:如 localStoragesessionStorage 对象。
  7. 浏览器信息和性能数据:如 navigatorperformance 对象。

其中,window 对象也是 JavaScript 许多内置对象(如 ArrayObject)的全局对象,在客户端 JavaScript 中,window 对象同时充当 ECMAScript 的 Global 对象。

BOM 应用场景示例

页面导航

使用 window.location 对象来获取或修改当前页面的 URL,或实现页面跳转。

window.location.href = 'http://example.com'; // 页面重定向

浏览器窗口控制

打开新窗口、关闭窗口、调整窗口大小等。

window.open('http://example.com', '_blank'); // 在新标签页中打开链接
window.close(); // 关闭当前窗口

本地数据存储

BOM 的 localStorage 和 sessionStorage 对象允许你在用户浏览器中存储键值对数据。

localStorage.setItem('key', 'value'); // 持久本地存储
sessionStorage.setItem('key', 'value'); // 会话级存储

用户设备信息获取

使用 navigator 对象来获取用户的设备信息,如浏览器版本、语言偏好等。

var userAgent = navigator.userAgent; // 获取用户代理信息

多媒体控制

通过 BOM 中的 screen 对象,可以获得屏幕的信息,比如分辨率,进而进行多媒体内容的适配。

var screenH = screen.height;
var screenW = screen.width;

DOM和BOM关系

BOM 和 DOM 是紧密联系的,但它们关注的层面有所不同。BOM 更关注浏览器窗口以及浏览器特性,而 DOM 更关注文档本身的内容。

  • 关系:DOM其实可以看作是BOM的一部分。BOM通过window对象的document属性提供对DOM的访问,即window.document。
  • 交互:虽然DOM集中于文档内容,而BOM集中于浏览器功能,但它们经常一起工作以提供丰富的网页应用交互。例如,使用BOM中的addEventListener来监听DOM中某个节点的事件。
  • 全局对象:在浏览器环境中,全局对象是window,而全局对象也是BOM的顶层对象,DOM的document对象就是window对象的属性之一。
  • 环境依赖性:BOM的某些部分(尤其早期)是环境依赖的,不同浏览器可能有不同的实现,而DOM则是努力保持跨浏览器一致的。

事件模型

事件模型是Web编程中的一个核心概念,它描述了事件如何在DOM中创建、传递和被接受。在Web浏览器中,事件模型允许用户与HTML文档内的元素交互,如点击按钮、敲击键盘、移动鼠标等都会触发事件。JavaScript可以监听这些事件并执行响应的回调函数。

事件模型分类

事件模型可以大体分为三类:传统的事件模型、 DOM 事件模型和 IE 事件模型

传统事件模型

这是早期浏览器实现的事件模型,也可以称之为旧式事件模型。在这种模型中,事件通常只支持冒泡机制,并且使用比较受限的方式添加事件监听器(如直接在HTML属性中或通过JavaScript属性赋值)。

<!-- HTML属性中 -->
<button onclick="alert('Clicked!')">Click me</button>

<!-- JavaScript属性赋值 -->
var btn = document.getElementById('myButton');
btn.onclick = function() {
  alert('Clicked!');
};

这种模型下,一个事件类型对应的事件处理函数只能有一个,后绑定的处理函数将会覆盖前面的。

W3C事件模型

随着 Web 技术的进步,W3C 推出了更为标准和完整的事件模型,支持事件捕获和冒泡两种机制,也允许为一个事件类型添加多个监听器。这种模型使用 addEventListener() 和 removeEventListener() 方法进行事件处理函数的绑定和解绑。

var btn = document.getElementById('myButton');
btn.addEventListener('click', function(event) {
  alert('Clicked!');
}, false); // 第三个参数表示事件监听器是否在捕获阶段触发,false代表在冒泡阶段。

W3C事件模型的一个关键特性是能够控制事件的传播方式,并允许文件内多个处理程序响应同一事件。

IE事件模型

IE 事件模型类似于传统事件模型,但与 W3C 事件模型有显著区别。IE事件模型特有的特点和方法主要在早期版本的Internet Explorer浏览器中使用,在现代浏览器中已经逐步被标准的W3C事件模型取代。

  1. 只支持事件冒泡:IE事件模型不支持事件捕获,事件处理的传播是从目标元素开始向上冒泡至顶层的document对象。
  2. 事件处理程序的注册与移除:IE 提供了自己的方法attachEvent和detachEvent用于绑定和移除事件处理函数。
  3. 事件对象的获取:在IE事件模型中,事件对象不是作为参数传递给事件处理函数的。相反,它通过window.event全局对象获得。
var btn = document.getElementById('myButton');
btn.attachEvent('onclick', function() {
  alert('Clicked in IE!');
});

上述代码展示了如何在IE事件模型中为一个按钮元素绑定点击事件。

跨浏览器事件处理

为了处理不同浏览器中的差异性,一般的做法是编写条件代码来确保事件能够在所有浏览器中一致地被处理:

var btn = document.getElementById('myButton');
var handleClick = function() {
  alert('Clicked!');
};

if (btn.addEventListener) { // 检查标准方法是否存在
  btn.addEventListener('click', handleClick, false);
} else if (btn.attachEvent) {  // 否则,使用IE的方法
  btn.attachEvent('onclick', handleClick);
}

详解

事件流

事件流描述了从页面中接收事件的顺序。有两种主要的事件流模式:

  1. 事件冒泡(Bubbling): 在这种模式下,在具体元素上触发的事件会一直向上传播到文档的根元素。例如,一个按钮点击事件会从按钮元素自身开始,一直向上冒泡到<body><html>一直到document对象。
  2. 事件捕获(Capturing): 事件捕获模式与事件冒泡相反,事件开始于document对象,向下通过DOM树传递到最具体的元素。

注意,不是所有事件都会冒泡。某些事件如focusblurload 不会冒泡,但可以在捕获阶段被捕获。

事件处理程序

事件处理程序即事件监听器(Event Listeners),它们是响应事件的回调函数。可以通过JavaScript为元素添加事件处理程序:

element.addEventListener('click', function(event) {
  // 处理点击事件
});

这里,addEventListener 方法用于注册一个事件监听器,它包含两个参数:事件类型(如 click)和事件处理函数。

事件对象

当事件发生时,浏览器会生成一个事件对象(Event),它被作为参数传递给事件处理函数。这个对象包含了有关事件的信息,如触发事件的元素、事件类型、鼠标位置等信息。

element.addEventListener('click', function(event) {
  console.log(event.target); // 输出事件的目标元素
});

阻止事件默认行为

默认行为是浏览器对于某些事件的内置响应行为。例如,点击链接会跳转到另一页面。使用事件对象可以取消这些默认行为:

element.addEventListener('click', function(event) {
  event.preventDefault();
  // 剩余的处理逻辑
});

停止事件传播

有时候你可能并不希望事件继续传播,无论是冒泡还是捕获。你可以使用stopPropagation方法来停止事件进一步传播:

element.addEventListener('click', function(event) {
  event.stopPropagation();
  // 处理点击事件
});

自定义事件

除了浏览器提供的标准事件外,还可以创建并触发自定义事件:

const event = new CustomEvent('my-event', { detail: { some: 'data' }});
element.dispatchEvent(event);

在这里,CustomEvent 构造函数用于创建自定义事件,dispatchEvent 方法用于将事件分派到目标对象。

事件委托

事件委托是一种常见的模式,它基于事件的冒泡原理。通过在父元素上设置监听器,可以管理子元素的事件,这样就无需在每个子元素上单独设置监听器。 优点:

  • 内存占用少:不需要为每个子元素绑定事件,只需在父元素上绑定一次。
  • 动态元素管理:对于动态添加的元素,无需单独绑定事件,当前已有的事件委托将自动应用。
document.getElementById('parent').addEventListener('click', function(event) {
  if (event.target.tagName === 'BUTTON') {
    // 处理按钮点击
  }
});

面试实战

面试题一:什么是 DOM?

答案:DOM(文档对象模型)是一个跨平台的、语言无关的接口,它将HTML或XML文档表示为树状结构。每个节点都是文档的一部分,例如文档的标记(tags)、属性、文本等。通过DOM提供的API,可以在脚本语言如JavaScript中操作文档结构、样式和内容。

面试题二:如何添加、移除、替换或插入DOM节点?

答案:可以使用以下DOM API进行节点操作:

  • 添加节点:使用appendChild()insertBefore()方法。
  • 移除节点:使用removeChild()方法。
  • 替换节点:使用replaceChild()方法。
  • 插入节点:使用parentNode.insertBefore(newNode, referenceNode),其中newNode是要插入的新节点,而referenceNode是参照节点。

面试题三:BOM 是什么?

答:BOM(Browser Object Model)是与浏览器交互的一套API,它提供了与浏览器窗口交互的方法和对象,包括 windowlocationnavigatorhistoryscreen等对象。这些对象允许开发者控制浏览器窗口的行为,如页面重定向、浏览历史导航、用户屏幕的检测以及执行定时操作等。

面试题四:如何检测用户的浏览器类型?

答案:可以通过BOM的navigator对象的userAgent属性来检测用户的浏览器类型。userAgent字符串包含了代表浏览器的名称、版本和其他详细信息的描述。但由于userAgent可以被用户或第三方扩展等修改,因此并非一个全然可靠的浏览器检测方法。

面试题五:如何获取URL的查询字符串参数?

答:可以使用BOM中的 location 对象访问URL,并使用 location.search 获得查询字符串,然后可以用JS解析查询字符串获取参数。

面试题六:描述事件冒泡和事件捕获

答案:事件冒泡和事件捕获是DOM事件流的两个阶段:

  • 事件捕获阶段:从最不具体的节点(通常是window对象)开始,然后沿DOM树向下直到目标元素,途中每个节点都会触发捕获事件。
  • 事件冒泡阶段:从事件的实际目标开始,然后沿DOM树向上传播到最不具体的节点。

默认情况下,所有的事件处理器都在冒泡阶段执行,但可以在使用addEventListener时,通过设置第三个参数为true来指定事件处理器在捕获阶段执行。

面试题七:如何阻止事件默认行为和冒泡?

答:你可以在事件处理函数中调用事件对象的 preventDefault() 方法来取消事件的默认行为。使用 stopPropagation() 方法可以阻止事件的进一步冒泡。对于旧版IE,可以设置 event.returnValue = falseevent.cancelBubble = true

面试题八:解释什么是事件委托,以及为何要使用它

事件委托是一种利用事件冒泡原理来优化事件处理的技术。由于在冒泡阶段,事件会从目标元素传播到父节点,所以我们可以只在父元素上设置一个事件监听器来管理类型相同的所有事件。这样做的好处有:

  • 减少内存消耗:不必为每个子元素都添加事件监听器。
  • 更容易管理事件:对于动态添加的子元素,不需要再额外添加事件监听器,因为它们自然会触发父元素上的事件监听器。