# DOM
DOM(文档对象模型)是针对 HTML 和 XML 文档的一个 API(应用程序编程接口), 将文档表示成一个层次化的节点树, 并且允许开发者去操纵这些节点。各家浏览器按照 DOM 标准实现了这套 API,使其成为跨平台、语言无关的表示和操作网页的方式。
1998 年 10 月 DOM 1 级规范成为 W3C 的推荐标准, 之后又有 DOM 2 级, DOM 3 级标准。
# 节点层级
在 DOM 中 HTML 文档被表示为一个由节点构成的层级结构。节点分很多类型,每种类型对应着文档中不同的信息或标记。
document节点表示每个文档的根节点。- 在 HTML 中根节点的唯一子结点是
<html>元素,称之为文档元素documentElement。所有其他元素都存在于这个元素之内,每个文档只能有一个文档元素。 - HTML 中的每段标记都可以表示为这个树形结构中的一个节点。
🌰 例如:
<html>
<head>
<title>Sample Page</title>
</head>
<body>
<p>Hello World!</p>
</body>
</html>

# 节点类型
DOM 中总共有 12 种节点类型。但是其中有一些已经被弃用,有一些是只针对 XML 的。
下面 👇 我们针对于 HTML 文档,介绍一些开发中常用的节点类型。
# Node 类型
在 DOM 中,所有节点类型都继承 Node 类型。因此,所有类型都共享相同的基本属性和方法。
# nodeType 属性
每个节点都有 nodeType 属性,表示该节点的类型。节点类型由定义在 Node 对象上的 12 个数值常量表示:
Node.ELEMENT_NODE= 1Node.ATTRIBUTE_NODE= 2Node.TEXT_NODE= 3Node.CDATA_SECTION_NODE= 4Node.ENTITY_REFERENCE_NODE= 5Node.ENTITY_NODE= 6Node.PROCESSING_INSTRUCTION_NODE= 7Node.COMMENT_NODE= 8Node.DOCUMENT_NODE= 9Node.DOCUMENT_TYPE_NODE= 10Node.DOCUMENT_FRAGMENT_NODE= 11Node.NOTATION_NODE= 12
可通过与这些常量比较,来确定节点类型:
if (someNode.nodeType == Node.ELEMENT_NODE) {
alert("Node is an element.");
}
# 节点关系
文档中的所有节点都与其他节点有关系。这些关系可以形容为家族关系。
- 🌰 例如,在 HTML 中,
<body>元素是<html>元素的子元素,而<html>元素则是<body>元素的父元素。<head>元素是<body>元素的同胞元素,因为它们有共同的父元素<html>。
每个节点都有一个 childNodes 属性,其中包含一个 NodeList 的实例,是一个类数组对象。该对象保存的是一个节点列表。
- 可以使用中括号
[],或使用item()方法访问NodeList中的元素。 NodeList对象中保存的并不是 DOM 结构的快照,实际 DOM 结构的变化会自动地在NodeList中反映出来。
let firstChild = someNode.childNodes[0];
let secondChild = someNode.childNodes.item(1);
let count = someNode.childNodes.length;
hasChildNodes() 方法来查询节点是否有子节点,方法返回一个布尔值。相比查询 childNodes 的 length 属性,这个方法更方便。
var foo = document.getElementById("foo");
if (foo.hasChildNodes()) {
console.log("该节点有子节点");
}
每个节点都有一个 parentNode 属性,指向其 DOM 树中的父元素。childNodes 中的所有节点都有同一个父元素,因此它们的 parentNode 属性都指向同一个节点。
childNodes 列表中的每个节点都是同一列表中其他节点的同胞节点。使用 previousSibling 和 nextSibling 可以获取一个节点的同胞节点。
NodeList 中第一个节点的 previousSibling 属性是 null,最后一个节点的 nextSibling 属性也是 null。
firstChild 和 lastChild 分别指向 childNodes 中的第一个和最后一个子节点。
someNode.firstChild的值始终等于someNode.childNodes[0],而someNode.lastChild的值始终等于someNode.childNodes[someNode.childNodes.length-1]。- 如果只有一个子节点,则
firstChild和lastChild指向同一个节点。 - 如果没有子节点,则
firstChild和lastChild都是null。

# 操纵节点
appendChild() 方法用于在 childNodes 列表末尾添加节点。方法返回新添加的节点。
let returnedNode = someNode.appendChild(newNode);
alert(returnedNode == newNode); // true
alert(someNode.lastChild == newNode); // true
如果把文档中已经存在的节点传给 appendChild(),则这个节点会从之前的位置被转移到新位置。
// 假设someNode有多个子节点
let returnedNode = someNode.appendChild(someNode.firstChild);
alert(returnedNode == someNode.firstChild); // false
alert(returnedNode == someNode.lastChild); // true
使用 insertBefore() 方法把节点放到 childNodes 中的特定位置。接收两个参数:
- 要插入的节点
- 参照节点
调用方法后,要插入的节点会变成参照节点的前一个同胞节点,并被返回。如果参照节点是 null,则 insertBefore() 与 appendChild() 效果相同。
// 作为最后一个子节点插入
returnedNode = someNode.insertBefore(newNode, null);
alert(newNode == someNode.lastChild); // true
// 作为新的第一个子节点插入
returnedNode = someNode.insertBefore(newNode, someNode.firstChild);
alert(returnedNode == newNode); // true
alert(newNode == someNode.firstChild); // true
// 插入最后一个子节点前面
returnedNode = someNode.insertBefore(newNode, someNode.lastChild);
alert(newNode == someNode.childNodes[someNode.childNodes.length - 2]); // true
使用 replaceChild() 方法将一个节点替换成另一个节点。要替换的节点会被返回并从文档树中完全移除。接收两个参数:
- 要插入的节点
- 要替换的节点
// 替换第一个子节点
let returnedNode = someNode.replaceChild(newNode, someNode.firstChild);
// 替换最后一个子节点
returnedNode = someNode.replaceChild(newNode, someNode.lastChild);
// 替换第一个子节点
let returnedNode = someNode.replaceChild(newNode, someNode.firstChild);
// 替换最后一个子节点
returnedNode = someNode.replaceChild(newNode, someNode.lastChild);
使用 removeChild() 方法删除一个节点,被移除的节点会被返回。接收一个参数,即要移除的节点。
// 删除第一个子节点
let formerFirstChild = someNode.removeChild(someNode.firstChild);
// 删除最后一个子节点
let formerLastChild = someNode.removeChild(someNode.lastChild);
cloneNode() 方法会返回与调用它的节点一模一样的节点。接收一个布尔值参数,表示是否深复制。
- 传入
true参数,会进行深复制,即复制节点及其整个子 DOM 树。 - 传入
false,则只会复制调用该方法的节点。
注意,cloneNode() 方法不会复制添加到 DOM 节点的 JavaScript 属性,比如事件处理程序。这个方法只复制 HTML 属性。
<ul>
<li>item 1</li>
<li>item 2</li>
<li>item 3</li>
</ul>
let deepList = myList.cloneNode(true);
alert(deepList.childNodes.length); // 3
let shallowList = myList.cloneNode(false);
alert(shallowList.childNodes.length); // 0
# Document 类型
Document 类型是 JavaScript 中表示文档节点的类型。HTMLDocument 继承自 Document,表示 HTML 文档。Document 类型的节点有以下特征:
nodeType等于9;nodeName值为"#document";nodeValue值为null;parentNode值为null;ownerDocument值为null;- 子节点可以是
DocumentType(最多一个 )、Element( 最多一个 )、ProcessingInstruction或Comment类型;
文档对象 document 是 HTMLDocument 的实例。document 是 window 对象的属性,可以作为全局变量访问。
# 文档子节点
document.documentElement属性,始终指向 HTML 页面中的<html>元素。document.body属性,直接指向<body>元素。document.doctype属性,直接指向 HTML 文档的<!doctype>标签。
<!DOCTYPE html>
<html>
<head></head>
<body></body>
</html>
let html = document.documentElement; // 取得对 <html> 的引用
let body = document.body; // 取得对 <body> 的引用
let doctype = document.doctype; // 取得对 <!DOCTYPE html> 的引用
alert(html === document.childNodes[1]); // true
alert(body === document.childNodes[1].childNodes[1]); // true
alert(doctype === document.childNodes[0]); // true
alert(doctype === document.firstChild); // true
# 文档信息
title属性包含<title>元素中的文本。不过,修改title属性并不会改变<title>元素。URL属性包含当前页面的完整 URL。domain属性包含页面的域名。referrer属性包含链接到当前页面的那个页面的 URL。如果当前页面没有来源,则referrer属性包含空字符串。
console.log(document.title); // 第 14 章 DOM-图灵社区
console.log(document.URL); // https://www.ituring.com.cn/book/tupubarticle/32500
console.log(document.domain); // www.ituring.com.cn
console.log(document.referrer); // https://www.ituring.com.cn/book/tupubarticle/32498
# 元素获取
使用 DOM 最常见的情形可能就是获取某个或某组元素的引用,然后对它们执行某些操作。
getElementById() 方法接收一个参数,即要获取元素的 ID,如果找到了则返回这个元素,如果没找到则返回 null。
<div id="myDiv">Some text</div>
let div = document.getElementById("myDiv"); // 取得对这个<div>元素的引用
getElementsByTagName() 方法接收一个参数,即要获取元素的标签名 ( 不区分大小写 ),返回包含零个或多个匹配元素的 HTMLCollection 对象。
let images = document.getElementsByTagName("img");
alert(images.length); // 图片数量
alert(images[0].src); // 第一张图片的 src 属性
HTMLCollection 对象提供 namedItem() 方法。可以获得列表中,具有指定 name 属性节点。这提供了除索引之外的另一种获取列表项的方式。为了使用便利,HTMLCollection 对象还支持直接使用中括号 ["name"] 来获取。
<div name="a"></div>
<div name="b"></div>
<div name="c"></div>
let divList = document.getElementsByTagName("div");
divList.namedItem("a") === divList["a"]; // true
getElementsByName() 方法会返回包含具有给定 name 属性的所有元素的 NodeList 对象。
<ul>
<li>
<input type="radio" value="red" name="color" id="colorRed">
<label for="colorRed">Red</label>
</li>
<li>
<input type="radio" value="green" name="color" id="colorGreen">
<label for="colorGreen">Green</label>
</li>
<li>
<input type="radio" value="blue" name="color" id="colorBlue">
<label for="colorBlue">Blue</label>
</li>
</ul>
let inputList = document.getElementsByName("color");
console.log(inputList);
// NodeList(3) [ input#colorRed, input#colorGreen, input#colorBlue ]
# Element 类型
Element 类型描述了所有元素所普遍具有的方法和属性。
Element 类型的节点具有以下特征:
nodeType等于1;nodeName值为元素的标签名;nodeValue值为null;parentNode值为Document或Element对象;- 子节点可以是
Element、Text、Comment、ProcessingInstruction、CDATASection、EntityReference类型;
Element 类型本身提供 nodeName 或 tagName 属性来获取元素的标签名 ( 全大写 )
// <div id="myDiv"></div>
let div = document.getElementById("myDiv");
alert(div.tagName); // "DIV"
alert(div.tagName == div.nodeName); // true
# HTMLElement 类型
HTMLElement 类型继承自 Element 类型,用以表示 HTML 元素。所有 HTML 元素都是 HTMLElement 或其子类型的实例。
提供如下属性,用来获取 HTML 元素上对应的属性值,也可以用来修改属性值。
id属性title属性className属性,相当于class属性( 因为class是 ECMAScript 关键字,所以不能直接用这个名字 )style属性,返回一个CSSStyleDeclaration对象,包含该元素的所有style属性及其属性值。
<div
id="myDiv"
style="height: 300px; width: 200px;"
class="div_1 div_2 div_3"
title="hahaha"
></div>
let div = document.getElementById("myDiv");
console.log(div.id); // "myDiv"
console.log(div.className); // "div_1, div_2, div_3"
console.log(div.title); // "hahaha"
console.log(div.style); // CSS2Properties { height → "300px", width → "200px" }
// 可以使用下列代码修改元素的属性:
div.id = "someOtherId";
div.className = "div_4, div_5, div_6";
div.title = "lalala";
# 操控属性
getAttribute() 方法用以取得 HTML 元素的上的指定属性值。
也能取得不是 HTML 语言正式属性的自定义属性的值。根据 HTML5 规范的要求,自定义属性名应该前缀 data- 以方便验证。
<div id="myDiv" class="div_1 div_2 div_3" data-my-attribute="hello!"></div>
let div = document.getElementById("myDiv");
console.log(div.getAttribute("id")); // "myDiv"
console.log(div.getAttribute("class")); // "div_1, div_2, div_3"
console.log(div.getAttribute("data-my-attribute")); // "hello!"
getAttribute() 方法的返回值都是字符串形式。
- 访问
style属性时,返回的是 CSS 字符串。HTMLElement实例的style属性返回的是一个CSSStyleDeclaration对象。 - 访问事件属性时,返回的是字符串形式的,事件处理回调函数的源代码。
setAttribute() 方法用以给指定的属性设置属性值。适用于 HTML 属性,也适用于自定义属性。接收两个参数:
- 要设置的属性名
- 属性值
如果属性已经存在,则 setAttribute() 会以指定的值替换原来的值;如果属性不存在,则 setAttribute() 会以指定的值创建该属性。
div.setAttribute("id", "someOtherId");
div.setAttribute("class", "ft");
div.setAttribute("title", "Some other text");
removeAttribute() 用于从元素中删除属性。
div.removeAttribute("class");
# attributes 属性
Element 类型的 attributes 属性包含一个 NamedNodeMap 对象。元素的每个属性都表示为一个 Attr 类型节点,并保存在这个 NamedNodeMap 对象中。
Attr 类型节点的 nodeName 属性是对应属性的名字,nodeValue 是属性的值。
NamedNodeMap 对象包含下列方法:
getNamedItem(name),返回nodeName属性等于name的节点。提供中括号[]访问属性的简写形式;removeNamedItem(name),删除nodeName属性等于name的节点;setNamedItem(node),向对象中添加node节点。以此方式,给元素添加一个新属性。item(pos),返回索引位置pos处的节点;
// 取得元素 id 属性的值
let id = element.attributes.getNamedItem("id").nodeValue;
// 使用中括号访问属性的简写形式:
id = element.attributes["id"].nodeValue;
// 通过将 nodeValue 设置为新值,来设置属性值
element.attributes["id"].nodeValue = "someOtherId";
通常开发者更喜欢使用 getAttribute()、removeAttribute() 和setAttribute() 方法。
attributes 属性最有用的场景是需要迭代元素上所有属性。
attributes.length属性的值表示元素具有的属性数量。
// 以下代码能够迭代一个元素上的所有属性,
// 并以 attribute1="value1" attribute2="value2" 的形式生成格式化字符串:
function outputAttributes(element) {
let pairs = [];
for (let i = 0, len = element.attributes.length; i < len; ++i) {
const attribute = element.attributes[i];
pairs.push(`${attribute.nodeName}="${attribute.nodeValue}"`);
}
return pairs.join(" ");
}
# 创建元素
使用 document.createElement() 方法创建新元素。接收一个参数,即要创建元素的标签名 ( 不区分大小 )
新创建的元素还没有添加到文档树,可以使用 appendChild()、insertBefore() 或 replaceChild() 将其插入文档树。
let div = document.createElement("div");
div.id = "myNewDiv";
div.className = "box";
document.body.appendChild(div);
# 元素后代
前面提过,childNodes 属性包含元素所有的子节点,
要取得某个元素的子节点和其他后代节点,可以使用元素的 getElementsByTagName() 方法。元素上调用这个方法与在文档上调用是一样的,只不过搜索范围限制在当前元素之内。
let ul = document.getElementById("myList");
let items = ul.getElementsByTagName("li");
# Text 类型
# Comment 类型
# DOM 编程
# 动态加载脚本
动态脚本就是在页面初始加载时不存在,之后又通过 DOM 包含的脚本。有两种方式通过 <script> 动态为网页添加脚本:
- 引入外部文件
- 直接插入源代码。
<script>元素上有一个text属性,可以用来添加 JavaScript 代码。
// 引入外部文件
let script = document.createElement("script");
script.src = "foo.js";
document.body.appendChild(script);
// 直接插入源代码
let script = document.createElement("script");
script.text = "function sayHi(){alert('hi');}";
document.body.appendChild(script);
这个过程可以抽象为一个函数:
function loadScript(url) {
let script = document.createElement("script");
script.src = url;
document.body.appendChild(script);
}
loadScript("client.js");
⚠️ 注意,通过 innerHTML 属性创建的 <script> 元素永远不会执行。浏览器会创建 <script> 元素,以及其中的脚本文本,但解析器会给这个 <script> 元素打上永不执行的标签。
# 动态加载样式
CSS 样式在 HTML 页面中可以通过两个元素加载。
<link>元素包含 CSS 外部文件。<style>元素添加嵌入样式。提供styleSheet.cssText用于添加 CSS 规则。
// <link> 元素包含 CSS 外部文件
let link = document.createElement("link");
link.rel = "stylesheet";
link.type = "text/css";
link.href = "styles.css";
let head = document.getElementsByTagName("head")[0];
head.appendChild(link);
// <style> 元素添加嵌入样式
let style = document.createElement("style");
style.type = "text/css";
style.styleSheet.cssText = "body{background-color:red}";
let head = document.getElementsByTagName("head")[0];
head.appendChild(style);
# 实时的 NodeList
在使用 NodeList 对象,以及它的子类,例如 HTMLCollection 对象时,要注意它们都是「 实时的 」,文档结构的变化会实时地在它们身上反映出来。
🌰 例如,下面的代码会导致无穷循环:
let divs = document.getElementsByTagName("div");
for (let i = 0; i < divs.length; i++) {
let div = document.createElement("div");
document.body.appendChild(div);
}
任何时候要迭代 NodeList,最好初始化一个变量保存当时查询时的长度:
let divs = document.getElementsByTagName("div");
for (let i = 0, len = divs.length; i < len; ++i) {
let div = document.createElement("div");
document.body.appendChild(div);
}
最好限制操作 NodeList 的次数。因为每次查询都会搜索整个文档,所以最好把查询到的 NodeList 缓存起来。
# MutationObserver 接口
使用 MutationObserver 接口,可以观察整个文档,某个元素,元素属性、文本的变化,并在 DOM 被修改时异步执行回调。
# DOM 扩展
2008 年以前,大部分浏览器对 DOM 的扩展是专有的。此后,W3C 开始着手将这些已成为事实标准的专有扩展编制成正式规范。
但是,这些扩展并不属于 DOM 的标准,不属于 DOM Level 中的任何一级。它们只是作为功能扩展的非核心 API。
# Selectors API
Selectors API 规定了,浏览器原生支持根据 CSS 选择符匹配查询 DOM 元素。
# querySelector() 方法
querySelector() 方法接收 CSS 选择符参数,返回匹配该模式的第一个后代元素,如果没有匹配项则返回 null。
在 Document 上使用 querySelector() 方法时,会从文档元素开始搜索;在 Element 上使用 querySelector() 方法时,则只会从当前元素的后代中查询。
// 取得<body>元素
let body = document.querySelector("body");
// 取得ID为"myDiv"的元素
let myDiv = document.querySelector("#myDiv");
// 取得类名为"selected"的第一个元素
let selected = document.querySelector(".selected");
// 取得类名为"button"的图片
let img = document.body.querySelector("img.button");
# querySelectorAll() 方法
querySelectorAll() 方法,也接收一个用于查询的参数,返回包含所有匹配的节点的 NodeList 静态实例,也就是它是文档结构的「 快照 」而非「 实时 」的查询。避免了使用 NodeList 对象可能造成的性能问题。
- 如果没有匹配项,则返回空的
NodeList实例。
// 取得ID为"myDiv"的<div>元素中的所有<em>元素
let ems = document.getElementById("myDiv").querySelectorAll("em");
// 取得所有类名中包含"selected"的元素
let selecteds = document.querySelectorAll(".selected");
// 取得所有是<p>元素子元素的<strong>元素
let strongs = document.querySelectorAll("p strong");
# matches()
matches() 方法接收一个 CSS 选择符参数,如果元素匹配则该选择符返回 true,否则返回 false。
这个方法可以方便地检测某个元素会不会被 querySelector() 或 querySelectorAll() 方法返回
目前,所有主流浏览器都支持 matches()。
<div id="haha" class="a b c"></div>
document.getElementById("haha").matches(".a"); // true
document.getElementById("haha").matches(".b"); // true
document.getElementById("haha").matches(".a,.b"); // true
document.getElementById("haha").matches(".d"); // false
# Traversal API
IE9 之前的版本不会把元素间的空格当成空白节点,而其他浏览器则会。这样就导致了 childNodes 属性上的差异。为了弥补这个差异,同时不影响 DOM 规范,W3C 通过新的 Element Traversal 规范。
大多数时候,开发者想遍历的是 Element 类型节点。Element Traversal API 为 DOM 元素添加了 5 个属性,为遍历 Element 类型节点提供便利。开发者不用担心空白文本节点的问题了。
childElementCount,返回Element类型的子元素数量;firstElementChild,指向第一个Element类型的子元素;lastElementChild,指向最后一个Element类型的子元素;previousElementSibling,指向前一个Element类型的同胞元素;nextElementSibling,指向后一个Element类型的同胞元素;
过去要以跨浏览器方式遍历特定元素的所有子元素,代码大致是这样写的:
let parentElement = document.getElementById("parent");
let currentChildNode = parentElement.firstChild;
while (currentChildNode) {
// 判断是不是 Element 类型节点。
if (currentChildNode.nodeType === 1) {
processChild(currentChildNode);
}
currentChildNode = currentChildNode.nextSibling;
}
使用 Element Traversal 属性之后,以上代码可以简化如下:
let parentElement = document.getElementById("parent");
let currentChildElement = parentElement.firstElementChild;
while (currentChildElement) {
processChild(currentChildElement);
currentChildElement = currentChildElement.nextElementSibling;
}
# HTML5 扩展
在所有以前的 HTML 规范中,从未出现过描述 JavaScript 接口的情形,HTML 就是一个纯标记语言。
然而,HTML5 规范却包含了与标记相关的大量 JavaScript API 定义。浏览器如果支持 HTML5 就必须提供这些 JavaScript API。
HTML5 覆盖的范围极其广泛,其中一部分 API 是和 DOM 相关的扩展,这一节只介绍这一部分。
# CSS 类扩展
getElementsByClassName() 方法接收一个参数,即包含一个或多个 class 名的字符串 ( 空格隔开 ),返回类名中包含相应 class 名的元素的 NodeList。
在 document 上调用 getElementsByClassName() 返回文档中所有匹配的元素,而在特定元素上调用 getElementsByClassName() 则返回该元素后代中匹配的元素。
// 取得所有类名中包含 "username" 和 "current" 元素
// 这两个类名的顺序无关紧要
let allCurrentUsernames = document.getElementsByClassName("username current");
// 取得ID为 "myDiv" 的元素子树中所有包含 "selected" 类的元素
let selected = document
.getElementById("myDiv")
.getElementsByClassName("selected");
前面提到过,可以通过 className 属性实现添加、删除和替换 class 名。但 className 是一个字符串,所以每次操作之后都需要重新设置这个值才能生效。
// 要删除"user"类
let targetClass = "user";
// 把类名拆成数组
let classNames = div.className.split(/\s+/);
// 找到要删除类名的索引
let idx = classNames.indexOf(targetClass);
// 如果有则删除
if (idx > -1) {
classNames.splice(i, 1);
}
// 重新设置类名
div.className = classNames.join(" ");
HTML5 给所有元素增加 classList 属性。保存的是一个 DOMTokenList 实例。与其他 DOM 集合类型一样,DOMTokenList 也有 length 属性表示自己包含多少项,也可以通过 item() 或中括号 [] 取得个别的元素。
此外,DOMTokenList 还增加了以下方法。
add(name),向类名列表中添加指定的字符串值name。如果这个值已经存在,则什么也不做。contains(name),返回布尔值,表示给定的name是否存在。remove(name),从类名列表中删除指定的字符串值name。toggle(name),如果类名列表中已经存在指定的name,则删除;如果不存在,则添加。
// 删除"disabled"类
div.classList.remove("disabled");
// 添加"current"类
div.classList.add("current");
// 切换"user"类
div.classList.toggle("user");
// 检测类名
if (div.classList.contains("bd")) {
// 执行操作
}
// 迭代类名
for (let class of div.classList) {
doStuff(class);
}
# HTMLDocument 扩展
HTML5 扩展了 HTMLDocument 类型,增加了更多功能。
readyState 是 IE4 最早添加到 document 对象上的属性 HTML5 将这个属性写进了标准。
document.readyState 属性用以判断文档是否加载完毕。有两个可能的值:
loading,表示文档正在加载;complete,表示文档加载完成;
「 加载完成 」的意思是,浏览器已完全加载 HTML,并构建了 DOM 树。并且加载完成了所有外部资源,图片,样式等。
if (document.readyState == "complete") {
// 执行操作
}
document.compatMode 属性指示浏览器当前处于什么渲染模式。
- 标准模式:
"CSS1Compat" - 混杂模式:
"BackCompat"
if (document.compatMode == "CSS1Compat") {
console.log("Standards mode");
} else {
console.log("Quirks mode");
}
HTML5 增加了 document.head 属性,指向文档的 <head> 元素。
# 自定义属性
HTML5 允许用户给元素指定自定义的属性。需要以 data- 为前缀。
<div id="myDiv" data-appId="12345" data-myname="Nicholas"></div>
通过元素的 dataset 属性可以访问自定义元素,属性值是一个 DOMStringMap 的实例,是一个键值对映射。
- 通过
data-后面的字符串作为键来访问属性值。 - 属性
data-myname、data-myName可以通过myname访问,但要注意data-my-name、data-My-Name要通过myName来访问。
// 本例中使用的方法仅用于示范
let div = document.getElementById("myDiv");
// 取得自定义数据属性的值
let appId = div.dataset.appId;
let myName = div.dataset.myname;
// 设置自定义数据属性的值
div.dataset.appId = 23456;
div.dataset.myname = "Michael";
// 有"myname"吗?
if (div.dataset.myname) {
console.log("Hello, ${div.dataset.myname}");
}
# 插入标记
读取 innerHTML 属性时,会返回元素所有后代的 HTML 字符串。
在写入 innerHTML 时,则会将提供的字符串解析为 DOM 树,并替代元素中原来包含的所有节点。
注意,通过 innerHTML 插入的 <script> 标签是不会执行的。
div.innerHTML = "<p>Hello World!</p>";
读取 outerHTML 属性时,会返回调用它的元素(及所有后代元素)的 HTML 字符串。
在写入 outerHTML 属性时,调用它的元素会被传入的 HTML 字符串经解释之后生成的 DOM 树取代。
// 新的 <p> 元素会取代DOM树中原来的 <div> 元素。
div.outerHTML = "<p>This is a paragraph.</p>";
使用本节介绍的方法可能会导致内存问题。比如,如果被移除的子树元素中之前有关联的事件处理程序,那它们之间的绑定关系会滞留在内存中。如果这种替换操作频繁发生,页面的内存占用就会持续攀升。
同时,HTML 解析器的构建与解构也比较消耗性能。因此最好限制使用 innerHTML 和 outerHTML 的次数。
for (let value of values) {
ul.innerHTML += "<li>${value}</li>"; // 别这样做!
}
// 这段代码,每次循环都要先读取 innerHTML,然后再写入。要访问两次 innerHTML。非常消耗性能。
// 最好通过循环先构建一个独立的字符串,最后再一次性把生成的字符串赋值给 innerHTML
let itemsHtml = "";
for (let value of values) {
itemsHtml += "<li>${value}</li>";
}
ul.innerHTML = itemsHtml;
# scrollIntoView()
scrollIntoView() 方法存在于所有 HTML 元素上,可以滚动浏览器窗口,或容器元素,使调用这个方法的元素进入视口 Viewport。
参数如下:
alignToTop是一个布尔值,默认为true:true:窗口滚动后元素的顶部与视口顶部对齐。false:窗口滚动后元素的底部与视口底部对齐。
scrollIntoViewOptions是一个选项对象:behavior:定义过渡动画,可取的值为"smooth"和"auto",默认为"auto"。block:定义垂直方向的对齐,可取的值为"start"、"center"、"end"和"nearest",默认为"start"。inline:定义水平方向的对齐,可取的值为"start"、"center"、"end"和"nearest",默认为"nearest"。
// 让页面滚动,使得元素进入视口
document.forms[0].scrollIntoView();
document.forms[0].scrollIntoView(true);
// 将元素平滑地滚入视口
document.forms[0].scrollIntoView({ behavior: "smooth", block: "start" });
# 专有扩展
除了已经标准化的,各家浏览器还有很多未被标准化的专有扩展。这些扩展有可能未来会被纳入到标准之中。
# children 属性
children 属性是一个 HTMLCollection,只包含元素的 Element 类型的子节点。
let childCount = element.children.length;
let firstChild = element.children[0];
# contains() 方法
contains() 方法用以确定一个元素是不是包含另一个元素。
- 在祖先元素上调用,参数是待确定的目标节点。
- 返回布尔值。
document.documentElement.contains(document.body);
# 插入标记
HTML5 将 IE 发明的 innerHTML 和 outerHTML 纳入了标准,但还有两个属性没有入选。这两个剩下的属性是 innerText 和 outerText。
innerText 属性对应元素中包含的所有文本内容,无论文本在子树中哪个层级。
- 读取值时,
innerText会按照深度优先的顺序。将子树中所有文本节点的值拼接起来。 - 写入值时,
innerText会移除元素的所有后代并插入一个包含该值的文本节点。
<div id="content">
<p>This is a <strong>paragraph</strong> with a list following it.</p>
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
</div>
console.log(document.getElementById("content").innerText);
// This is a paragraph with a list following it.
// Item 1
// Item 2
// Item 3
// 注意:不同浏览器对待空格的方式不同
outerText 与 innerText 是类似的,只不过作用范围还包含调用它的节点。
- 读取文本值时,
outerText与innerText实际上会返回同样的内容。 - 写入文本值时,
outerText不止会移除所有后代节点,而是会替换整个元素。
div.outerText = "Hello world!";
// 等价于
let text = document.createTextNode("Hello world!");
div.parentNode.replaceChild(text, div);