前端面试基础知识总结(五):DOM & BOM

JavaScript 由下面三部分组成:

  • ECMAScript(ES5):描述了 JS 的语法和基本对象
  • 文档对象模型(DOM):处理网页内容的方法和接口
  • 浏览器对象模型(BOM):与浏览器交互的方法和接口

DOM 和 BOM 就是本章要重点介绍的内容。

DOM 大纲

DOM 节点

根据 W3C 的 HTML DOM 标准,HTML 文档中的所有内容都是节点:

  • 整个文档是一个文档节点
  • 每个 HTML 元素是元素节点
  • HTML 元素内的文本是文本节点
  • 每个 HTML 属性是属性节点
  • 注释是注释节点

DOM 将 HTML 文档视作树结构。这种结构被称为节点树:
Dom 节点树

DOM 方法和属性

DOM 方法是我们可以在节点(HTML 元素)上执行的动作。
DOM 属性是我们可以在节点(HTML 元素)设置和修改的值。

一些常用的 HTML DOM 方法:

  • 查找节点:getElementByIdgetElementsByTagNamegetElementsByClassNamequerySelectorAll
  • 创建节点:createAttributecreateElementcreateTextNode
  • 插入节点:appendChildinsertBefore
  • 删除节点:removeChild
  • 修改节点:replaceChild

一些常用的 HTML DOM 属性:

  • parentNode - 节点的父节点
  • childNodes - 节点的子节点
  • innerHTML - 节点的文本值
  • nodeValue - 节点的值
  • nodeType - 节点的类型(元素 1;属性 2;文本 3;注释 8;文档 9)
  • nodeName - 节点的名称

举个例子,添加一个元素:

1
2
3
4
5
6
7
8
9
10
11
<div id="div1">
<p id="p1">这是一个段落。</p>
<p id="p2">这是另一个段落。</p>
</div>
<script>
var para = document.createElement("p");
var node = document.createTextNode("这是一个新段落。");
para.appendChild(node);
var div1 = document.getElementById("div1");
div1.appendChild(para);
</script>

DOM 事件体系

DOM 事件机制

  • DOM0:el.onClick = function(){}
  • DOM2:el.addEventListener(event-name, callback, useCapture)useCapture默认为 false,即在让事件在冒泡传播时执行,为 true 则代表在捕获阶段执行
  • DOM3:在 DOM 2 级事件的基础上添加了更多的事件类型
    • UI 事件,当用户与页面上的元素交互时触发,如:load、scroll
    • 焦点事件,当元素获得或失去焦点时触发,如:blur、focus
    • 鼠标事件,当用户通过鼠标在页面执行操作时触发如:dblclick、mouseup
    • …(省略更多事件),同时 DOM3 级事件也允许使用者自定义一些事件。

(DOM 级别一共可以分为四个级别:DOM0 级、DOM1 级、DOM2 级和 DOM3 级。由于 DOM 1 级中没有事件的相关内容,所以没有 DOM 1 级事件。)

DOM 级别

DOM 是用来访问或操作 HTML 文档、XHTML 文档、XML 文档中的节点元素。
现在基本上所有的浏览器都都执行了 W3C 发布的 DOM 规范,所以在浏览器上就可以用 DOM 的这些 API。

  • DOM0:不是 W3C 规范。
  • DOM1:开始是 W3C 规范。专注于 HTML 文档和 XML 文档。
  • DOM2:对 DOM1 增加了样式表对象模型
  • DOM3:对 DOM2 增加了内容模型 (DTD 、Schemas) 和文档验证。

事件对象 Event

事件对象,当元素的某个事件行为被触发,不仅会把之前绑定的方法执行,还会给绑定的方法传递一个值(浏览器默认传递的)。这个值是个对象类型的值,里面存储了很多的属性和方法。

  • currentTarget:事件正在经过哪个元素,即事件绑定的元素
  • target:触发事件的具体目标
  • preventDefault():阻止默认行为
  • stopPropagation():阻止事件冒泡

举个例子:

1
2
3
<div id="outer">
<div id="inner">Click Me!</div>
</div>
1
2
3
4
document.getElementById('outer').addEventListener('click', function(event) {
console.log('target:', event.target.id);
console.log('currentTarget:', event.currentTarget.id);
});

所以,当你点击 inner 元素时,控制台将会打印:

1
2
target: inner // 实际触发事件的元素
currentTarget: outer // 绑定事件的元素

DOM 事件流

  1. 事件捕获阶段:自上而下向目标节点传播
  2. 目标阶段
  3. 事件冒泡阶段:
    • 自下而上向 window 对象;
    • 部分事件不支持冒泡:UI 事件、焦点事件、鼠标事件。

dom 事件传播机制

事件委托

事件代理是指多个子元素的同一类型的监听逻辑,合并到父元素上通过一个监听函数来管理。主要优势是减少内存消耗。

举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<ul id="list">
<li class="item">item 1</li>
<li class="item">item 2</li>
<li class="item">item 3</li>
<!-- ... -->
<li class="item">item n</li>
</ul>
<script>
document.getElementById("list").addEventListener("click", function (e) {
var target = e.target;
if (target.className === "item") {
console.log("the content is: ", target.innerHTML);
// ...事件处理
}
});
</script>

在上面这个例子中,假设有 n(n 很大)个列表项,采取事件委托的方式,只需要在父元素上绑定监听事件函数,内存消耗小很多。

自定义事件

创建自定义事件:event = new CustomEvent(typeArg, customEventInit);

  • typeArg 事件名
  • customEventInit 事件设置
    • detail 是一个与 event 相关的值,对象
    • bubbles 一个布尔值,表示该事件能否冒泡。
    • cancelable 一个布尔值,表示该事件是否可以取消

触发事件:dispatchEvent(event)

1
2
3
4
5
6
7
8
9
10
let custom = new CustomEvent("test_event", {
detail: { e_name: " this is a test " },
});
// 某个dom元素监听自定义事件
let div = document.createElement("div");
div.addEventListener("test_event", function (e) {
console.log(e.detail.e_name);
});
// 触发自定义事件(dispatchEvent 除非事件的参数是必填项,切必须为事件对象)
div.dispatchEvent(custom); // this is a test

BOM

浏览器对象模型(Browser Object Model (BOM))尚无正式标准。

由于现代浏览器已经(几乎)实现了 JavaScript 交互性方面的相同方法和属性,因此常被认为是 BOM 的方法和属性。

Window 对象

所有浏览器都支持 window 对象。它表示浏览器窗口。所有 JavaScript 全局对象、函数以及变量均自动成为 window 对象的成员。

1
2
3
window.document.getElementById("header");
// 与上面相同
document.getElementById("header");

Window 尺寸

如何获得浏览器的尺寸?

兼容的 JavaScript 方案(涵盖所有浏览器,包含 IE8 及以下版本的浏览器):

1
2
3
4
5
6
7
8
// 浏览器宽度
var w=window.innerWidth
|| document.documentElement.clientWidth
|| document.body.clientWidth;
// 浏览器高度
var h=window.innerHeight
|| document.documentElement.clientHeight
|| document.body.clientHeight;

如何记忆各种尺寸?

网络上广为流传的这张图可谓是眼花缭乱。原本我以为自己已经完全掌握了。可每次写代码时还总有不确定的,经常需要把下图拿出来再三确认。这一次我就好好梳理一下他们之间的关系:

示意图

分析上图,距离属性一共有两个维度:

  • 类型:client 、 offset、scroll
  • 属性:
    • 位置:top、left
    • 大小:width、height

两个拼在一起,就组成一个完整的距离属性。

类型 位置(top、left) 大小(width、height)
client border的距离 client
offset 元素相对于 offsetParent 边界框的距离 client(+border+滚动条)
scroll border+不可见滚动区域 offset(+不可见滚动区域)
备注 / client < offset < scroll

下图是更为精简的示意图:

示意图

最常用的是以下两个:
scrollTop:距离顶部的滚动距离
clientHeight:可视区域的高度

示意图

面试题

document.ready 和 onload 的区别

  1. document.ready
    • 在 DOM 加载完成后就可以可以对 DOM 进行操作。
    • 可以写多个.ready,可以执行多次
    • 执行更快
  2. document.onload
    • 在 document 文档加载完成后就可以可以对 DOM 进行操作,document 文档包括了加载图片等其他信息。
    • 只能执行一次
    • 要等待图片加载,执行更慢

参考