在网上经常会看到 ECMAScript,DOM,BOM 这几个概念,今天我们就来缕一缕它们到底是什么东西,其实简单来说,如下
- 核心(
ECMAScript),提供核心语言功能 - 文档对象模型(
Document Object Model,简称DOM),提供访问和操作网页内容的方法和接口 - 浏览器对象模型(
Broser Object Model,简称BOM),提供与浏览器交互的方法和接口
简单的来说,DOM 和 BOM 并不属于 JavaScript(ECMAScript)语言的一部分,DOM 和 BOM 是 JavaScript 的运行平台(浏览器)提供的,比如在 Node.js 当中就没有 DOM 和 BOM,JavaScript 类型分为两大类,『原生类型』和『对象类型』,而 DOM 和 BOM 都是『对象类型』,下面我们就一个一个来进行了解
BOM
BOM(Browser Object Model)即浏览器对象模型,主要是指一些浏览器内置对象如 window,location,navigator,screen,history 等对象,用于完成一些操作浏览器的特定 API,主要用于描述这种对象与对象之间层次关系的模型,浏览器对象模型提供了独立于内容的、可以与浏览器窗口进行互动的对象结构
BOM 由多个对象组成,其中代表浏览器窗口的 window 对象是 BOM 的顶层对象,其他对象都是『该对象的子对象』
BOM提供了独立于内容而与浏览器窗口进行交互的对象- 由于
BOM主要用于管理窗口与窗口之间的通讯,因此其核心对象是window BOM由一系列相关的对象构成,并且每个对象都提供了很多方法与属性BOM缺乏标准,JavaScript语法的标准化组织是ECMA,DOM的标准化组织是W3CBOM最初是Netscape浏览器标准的一部分
BOM 结构如下图所示

从上图可以看出 DOM 是属于 BOM 的一个属性,window 对象是 BOM 的顶层(核心)对象,所有对象都是通过它延伸出来的,所以可以称它们为 window 的子对象,由于 window 是顶层对象,因此调用它的子对象时可以不显示的指明 window 对象,比如如下两种写法均可
1 | document.title = '123' |
BOM 导图
BOM 部分主要是针对浏览器的内容,下面是一些比较常用的对象
window,是全局对象,很多关于浏览器的脚本设置都是通过它location,则是与地址栏内容相关,比如想要跳转到某个页面,或者通过URL获取一定的内容navigator,通常判断浏览器类型都是通过这个对象screen,常常用来判断屏幕的高度宽度等history,访问浏览器的历史记录,如前进、后台、跳转到指定位置
具体关系可以如下图所示

window 对象
window 对象在浏览器中具有双重角色,它既是 ECMAscript 规定的全局 global 对象,又是 JavaScript 访问浏览器窗口的一个接口,所有浏览器都支持 window 对象,它表示浏览器窗口
- 如果文档包含框架(
frame或iframe标签),浏览器会为HTML文档创建一个window对象,并为每个框架创建一个额外的window对象 - 没有应用于
window对象的公开标准,不过所有浏览器都支持该对象 - 所有
JavaScript全局对象、函数以及变量均自动成为window对象的成员 - 全局变量是
window对象的属性,全局函数是window对象的方法
下面是一些常用的 window 方法
window.innerHeight,浏览器窗口的内部高度window.innerWidth,浏览器窗口的内部宽度window.open(),打开新窗口window.close(),关闭当前窗口
window 的子对象
下面我们来看看一些 window 的子对象
Navigator 对象
浏览器对象,通过这个对象可以判定用户所使用的浏览器,包含了浏览器相关信息
1 | navigator.appName // Web 浏览器全称 |
Screen 对象
屏幕对象,一般不太常用,一些常见属性如下
screen.availWidth,可用的屏幕宽度screen.availHeight,可用的屏幕高度
History 对象
浏览历史对象,包含了用户对当前页面的浏览历史,但我们无法查看具体的地址,只能简单的用来前进或后退一个页面,也可以使用 go() 实现在用户的浏览记录中跳转
1 | history.go(-1) // 等价于 history.back() |
Location 对象
location 对象提供了当前窗口加载的文档的相关信息,还提供了一些导航功能,事实上这是一个很特殊的对象,location 既是 window 对象的属性,又是 document 对象的属性,window.location 对象用于获得当前页面的地址(URL),并把浏览器重定向到新的页面,常用属性和方法有
1 | location.href // 获取 URL |
弹出框
可以在 JavaScript 中创建三种消息框,警告框、确认框、提示框
1 | // 警告框 |
DOM
DOM 是一套对文档的内容进行抽象和概念化的方法,当网页被加载时,浏览器会创建页面的文档对象模型(Document Object Model),而 HTML DOM 模型会被构造为对象的树,如下图所示

DOM 模型将整个文档(XML 文档和 HTML 文档)看成一个树形结构,并用 document 对象表示该文档,DOM 规定 HTML 文档中的每个成分都是一个节点(node)
- 文档节点(
Document),代表整个文档 - 元素节点(
Element),文档中的一个标记 - 文本节点(
Text),标记中的文本 - 属性节点(
Attr),代表一个属性,元素才有属性 - 注释节点(
Comment),表示注释
JavaScript 可以通过 DOM 创建动态的 HTML
- 可以改变页面中的所有
HTML元素及其属性 - 可以改变页面中的所有
CSS样式 - 可以对页面中的所有事件做出反应
节点类型
使用 NodeType 属性来表明节点类型
| 节点类型 | 描述 | |
|---|---|---|
| 1 | Element |
代表元素,普通元素节点,比如 div,p 等 |
| 2 | Attr |
代表属性 |
| 3 | Text |
代表元素或属性中的文本内容,文本节点 |
| 4 | CDATASection |
代表文档中的 CDATA 部分(不会由解析器解析的文本) |
| 5 | EntityReference |
代表实体引用 |
| 6 | Entity |
代表实体 |
| 7 | ProcessingInstruction |
代表处理指令 |
| 8 | Comment |
代表注释,注释节点 |
| 9 | Document |
代表整个文档(DOM 树的根节点) |
| 10 | DocumentType |
向为文档定义的实体提供接口 |
| 11 | DocumentFragment |
代表轻量级的 Document 对象,能够容纳文档的某个部分 |
| 12 | Notation |
代表 DTD 中声明的符号 |
节点关系

| 属性 | 描述 |
|---|---|
nodeType |
返回节点类型的数字值(1 ~ 12) |
nodeName |
节点名称 |
nodeValue |
节点值 |
parentNode |
父节点 |
parentElement |
父节点标签元素 |
childNodes |
所有子节点 |
children |
第一层子节点 |
firstChild |
第一个子节点,Node 对象形式 |
firstElementChild |
第一个子标签元素 |
lastChild |
最后一个子节点 |
lastElementChild |
最后一个子标签元素 |
previousSibling |
上一个兄弟节点 |
previousElementSibling |
上一个兄弟标签元素 |
nextSibling |
下一个兄弟节点 |
nextElementSibling |
下一个兄弟标签元素 |
childElementCount |
第一层子元素的个数(不包括文本节点和注释) |
ownerDocument |
指向整个文档的文档节点 |
一些判断节点之间关系的方法
hasChildNodes(),包含一个或多个节点时返回truecontains(),如果是后代节点返回trueisSameNode()和isEqualNode(),传入节点与引用节点的引用为同一个对象返回truecompareDocumentPostion(),确定节点之间的各种关系
节点操作
这一部分主要涉及节点相关的一些操作,也是在平常开发当中经常会遇到的地方,所以我们会介绍的稍微详细一些
| 操作 | 描述 |
|---|---|
nodeName |
访问元素的标签名 |
tagName |
访问元素的标签名 |
createElement() |
创建节点 |
appendChild() |
末尾添加节点,并返回新增节点 |
insertBefore() |
参照节点之前插入节点,参数有两个,要插入的节点和参照节点 |
insertAfter() |
参照节点之后插入节点,参数有两个,要插入的节点和参照节点 |
replaceChild() |
替换节点,参数有两个,要插入的节点和要替换的节点(被移除) |
removeChild() |
移除节点 |
cloneNode() |
克隆,一个布尔值参数,true 为深拷贝,false 为浅拷贝 |
importNode() |
从文档中复制一个节点,参数有两个,要复制的节点和布尔值(是否复制子节点) |
有一个比较特殊的 insertAdjacentHTML(),作用是插入文本,参数有两个,插入的位置和要插入文本
beforebegin,在该元素前插入afterbegin,在该元素第一个子元素前插入beforeend,在该元素最后一个子元素后面插入afterend,在该元素后插入
childNodes
用来获取子节点,注意,返回的是一个『类数组对象』
1 | var childs = oDiv.childNodes |
这里存在一个坑,比如如下
1 | <div> |
当 div 中没有文本节点的时候,此时应该为 4 个节点,但是 IE9,Chrome,FireFox 会认为存在 9 个节点,而 IE8 则认为只有 4 个节点,这是因为高级浏览器会把空文本节点也当作为一个节点,标签前后的空文本也会被算作一个节点,而且对于注释的前后算不算空文本节点,每个浏览器的解释也有不相同,所以我们在使用节点的时候,一定要过滤,比如判断节点的 nodeType 是不是 1(普通元素节点)
1 | // 得到真正的标签子节点 |
另外,如果要改变文本节点的内容(nodeType 为 3),需要改变其 nodeValue 属性
1 | oDiv.childNodes[0].nodeValue = '张三' |
parentNode
parentNode 属性表示父节点,任何节点的 parentNode 的 nodeType 一定为 1,也就是说父节点一定是标签节点
previousSibling 和 nextSibling
表示一个兄弟节点,需要注意的是,其可能是文本或者注释节点,而原生 JavaScript 当中并没有提供 prevAll(),nextAll(),siblings() 等方法,如果不存在兄弟节点,则会返回 null,所以可以利用这个特性来写一个方法
1 | // prev |
而 siblings() 方法则可以使用双重循环来实现,比如下面这个方法
1 | function toggleActive() { |
创建节点
使用 document.createElement('标签名') 来创建一个节点,需要注意的是,创建出来的节点是不存在与 DOM 树上的,即孤儿节点,需要手动添加至 DOM 树中
1 | var oBox = document.getElementById('div') |
一个需要注意的地方,JavaScript 中存储 DOM 节点的变量是动态的,比如如下例子
1 | var oBox = document.getElementById('box') |
解决方法很简单,用一个变量将 length 存储起来即可
1 | for (var i = 0; l = oDiv.length, i < l; i++) { |
插入节点
appendChild()
常用的方法是使用 appendChild() 来追加至元素的末尾,需要注意的地方就是,如果节点已经存在(比如 DOM 树中已经存在),而不是新创建的,这个时候则会移动该节点(不会克隆)
insetBefore()
接收两个参数,一个是新创建的元素,另一个为参照点
1 | oBox.insetBefore('新创建的元素', '参照元素') |
这样插入的元素会以参照的元素依次往上添加(即添加的为 3,2,1参照),如果想让顺序变为正序,使用 oBox.childNodes[0] 为参照点即可
需要注意,如果使用
childNodes[0]来做参照删除元素的话,会存在空白节点
删除节点
节点不能自己删除,如果想要删除节点,必须使用父元素参照
1 | '父元素'.removeChild('删除的元素') |
如果不知道父元素是谁,则可以使用
1 | '需要删除的元素'.parentNode.removeChild('需要删除的元素') |
替换节点
使用 replaceChild() 方法,一般使用的不是很多
1 | '父元素'.replaceChild('新节点', '旧节点') |
比如 oBox.replaceChild(div1, div2) 结果是将 div1 节点处的内容替换至 div2 处(div1 处的节点内容就不存在了)
克隆节点
比较常用的方式是使用 innerHTML 的方式来进行克隆(亦或是修改),但是执行效率没有 DOM 原生方法速度快,原生的方法是 cloneNode([true]),可以追加一个布尔值参数 true,表示深度克隆,克隆其所有的子节点
对象类型
部分内容截取自 知乎 - justjavac 的回答,这里主要涉及到 ECMAScript 中的对象和 DOM/BOM 对象,我们在文章的开头部分提到过,JavaScript 类型分为原生类型和对象类型,而 DOM 和 BOM 都是对象类型,下面我们就来深入的了解一下
比如 HTML 中的段落 p 映射为 JavaScript 对象是 HTMLParagraphElement,顾名思义 Paragraph 就是英语段落的意思,我们看看 HTMLParagraphElement 的继承关系
1 | HTMLParagraphElement |
所有的 DOM 和 BOM 没有任何特殊之处,都是一个 Object 的子类
1 | // 创建一个 div dom |
当我们创建了一个 DOM Object 后,我们就可以把这个 DOM Object 当作一个普通的 JavaScript 对象来使用,说他特殊,大概是因为 DOM 的属性和方法会被引擎映射到 HTML 标签上,有些会,有些不会,有些只能从 HTML 到 DOM 映射,有些只能从 DOM 到 HTML 映射,如果但从对象的角度讲,特殊的不是 DOM 和 BOM,而是另一个值
1 | Object.create(null) |
所有的 JavaScript 对象都是继承自 Object,即使我们经常创建的空对象也是 Object 的子类,如下
1 | let obj = { } |
如下图所示

但是 Object.create(null) 却是实实在在的空对象

在引擎(V8)内部,DOM 对象映射为 C++ Object

而每个 HTML 标签都对应一个 DOM Object,我们看如下代码:
1 | var div = document.createElement('div') // 创建一个 div dom |
可能我们会觉得最终输出的是 null 或者 undefined 亦或是抛出异常,但是这行代码会输出 1234,DOM 如何与 JavaScript Object 关联在一起的规范定义在 WebIDL Level 1 当中,WebIDL 就是 Web Interface Definition Language 的缩写,DOM Object 在引擎内部是一个 C++ Object,当 JavaScript 操作这个 DOM 时,引擎使用一个 wrapper object,也就是 JavaScript Object

Wrapper Object 和 DOM Object 的关系是 n : 1(n >= 0)

其中(n >= 0)
- 当
n == 0时,此DOM Object不能通过JavaScript访问 - 当
n == 1时,此DOM Object只有一个JavaScript Object可以访问 - 当
n > 1时,此DOM Object可以通过多个JavaScript访问
举个例子
1 | div = document.createElement('div') |
上面代码的对应关系图
