update test 更新文章 1. 测试新主题 挖坑 挖坑 update config 更新文章 1. 简易FaaS平台 更新文章 1. 修复错误 更新文章:Promise信任问题 update theme 1. 合并js post: update notedly fix: update faas feature: change theme * fix: comment * feature: pgp * fix: delete local file post: update darkmode update: update dependencies fix: navbar in post incorrect height * pre code adapt to dark mode update new post useCallback update dependencies new post tiny router * add static files update vue tiny router 添加备案 * 更新依赖 add post Add ignore file
21 KiB
DOM(文档对象模型,Document Object Model),是针对 HTML 和 XML 文档的一个 API。DOM 脱胎于 Netscape 以及微软创始的 DHTML。
IE 中所有 DOM 对象都是以 COM 对象的形式实现的,与原生 JavaScript 对象的行为或活动特点不一致。
节点层次
节点之间的关系构成了层次,而所有页面标记则表现为一个以特点节点为根节点的树形结构。也就是文档树。
文档节点是每个文档的根节点,文档节点只有一个子节点,称之为文档元素。在 HTML 中,文档元素始终都是<html>
元素。
Node 类型
总共有 12 中节点类型,这些节点类型都继承自一个基类型。DOM1 级定义了一个 Node 接口,该接口将由 DOM 中所有的节点类型实现。Node 接口在 JavaScript 中是作为 Node 类型实现的;除 IE 外(IE8 及以下),其他所有浏览器都可以访问到这个类型。
每个节点都有一个nodeType
属性,用于表明节点类型。在 Node 类型中分别定义了 12 个数值常量来表示:
节点类型常量
常量 | 值 | 描述 |
---|---|---|
Node.ELEMENT_NODE |
1 |
一个 元素 节点,例如 p 和 div 。 |
Node.TEXT_NODE |
3 |
Element 或者 Attr 中实际的 文字 |
Node.CDATA_SECTION_NODE |
4 |
一个 CDATASection ,例如 <!CDATA[[ … ]]> 。 |
Node.PROCESSING_INSTRUCTION_NODE |
7 |
一个用于XML文档的 ProcessingInstruction ,例如 <?xml-stylesheet ... ?> 声明。 |
Node.COMMENT_NODE |
8 |
一个 Comment 节点。 |
Node.DOCUMENT_NODE |
9 |
一个 Document 节点。 |
Node.DOCUMENT_TYPE_NODE |
10 |
描述文档类型的 DocumentType 节点。例如 <!DOCTYPE html> 就是用于 HTML5 的。 |
Node.DOCUMENT_FRAGMENT_NODE |
11 |
一个 DocumentFragment 节点 |
已经弃用的节点类型
常量 | 值 | 描述 |
---|---|---|
Node.ATTRIBUTE_NODE |
2 | 元素 的耦合属性 。在 DOM4 规范里Node 接口将不再实现这个元素属性。 |
Node.ENTITY_REFERENCE_NODE |
5 | 一个 XML 实体引用节点。 在 DOM4 规范里被移除。 |
Node.ENTITY_NODE |
6 | 一个 XML <!ENTITY ...> 节点。 在 DOM4 规范中被移除。 |
Node.NOTATION_NODE |
12 | 一个 XML <!NOTATION ...> 节点。 在 DOM4 规范里被移除. |
通过比较nodeType
属性中包含的上述常量,就可以确定节点的类型。由于早期的 IE 没有公开 Node 类型,为了兼容所有浏览器,可以使用nodeType
属性的数值量进行对比:
if (someNode.nodeType === 1) {
console.log(`this is an element`)
}
要详细的查看节点的信息,还有nodeName
和nodeValue
属性。这两个属性的值完全取决于节点的类型。对于 Element 节点,nodeName
就是其标签名,而nodeValue
始终为null
。
let c = document.body.appendChild(document.createComment('test'));
c.nodeName // "#comment"
c.nodeValue // "test"
节点关系
每个节点都有一个childNodes
属性,其中保存着一个 NodeList 对象,这个对象是一个类数组对象,用于保存一组有序的节点。NodeList 对象的独特之处在于,它实际上是基于 DOM 结构动态查询的结果,而不是静态的。
childNodes
会返回所有子节点,不仅仅只是 Element 元素,可以使用children
来访问子元素。
div.childNodes
// NodeList(9) [text, span, text, div, text, div, text, img, text]
虽然 NodeList 对象是一个类数组,但是可以通过数组方法slice()
将其直接转换为数组。
let test = Array.prototype.slice.call(div.childNodes, 0);
Object.prototype.toString.call(test)
// "[object Array]"
但是 IE 早期无效,在早期 IE 中只能将其遍历转换为数组。因为其是类数组,所以 NodeList 是可迭代的(但是我的 IE 11 貌似不能用使用for/of
)。
function converToArray(nodes) {
let arr = null;
try {
arr = Array.prototype.slice.call(nodes.childNodes, 0);
} catch(e) {
arr = [];
for (let i in nodes) {
arr.push(nodes[i]);
}
}
return arr;
}
每个节点都有一个父节点,可以使用parentNodes
属性来访问。父节点是唯一的,所有childNodes
返回的节点都有同一个父节点。同样的,与childNodes
相同的是,父节点不一定就是 Element,可以使用parentElement
返回父元素节点。
let html = document.querySelector('html');
html.parentElement;
// null
html.parentNode;
// #document
所有的子节点相互之间都是兄弟/同胞节点,可以通过使用previousSibling
和nestSibling
属性来访问相邻的同胞节点。
父节点的firstChild
和lastChild
分别指向 childNodes 列表中的第一个和最后一个节点。也就是说,lastChild
等于childNodes.length - 1
。
除了确定childNodes.length
之外,还可以使用hasChildNodes()
方法来确定是否拥有子节点。
所有节点的最后一个属性是ownerDocument
,该属性指向整个文档的文档节点,也就是document
。这种关系表示的任何节点都属于它所在的文档,任何节点都不能同时存在与两个或以上的文档中。有了该属性,就不必层层回溯到顶端,而是可以直接访问文档节点。
所有节点类型都继承自 Node,但并不是每种节点都有子节点。
节点关系的小结:
- 父节点是唯一的;
childNodes
返回所有类型的子节点,children
返回子元素节点;childNodes
返回的是类数组;parentNodes
返回父节点,parentElement
返回父元素节点;previousSibling
和nestSibling
属性来访问相邻的同胞节点;firstChild
和lastChild
属性来访问第一个和最后一个子节点;hasChildNodes()
方法来确定是否拥有子节点;ownerDocument
直达document
。
操作节点
因为节点关系指针都是只读的,所以 DOM 提供了一系列的操作节点的方法。最常用的方法是生成节点:append()
和appendChild()
。他们都用于向 childNodes 列表后添加一个节点。
append()
和appendChild()
的区别是:
append()
方法允许添加 DOMString 对象,而appendChild()
只允许添加 Node 对象;append()
可以一次添加多个节点和字符串,而appendChild()
只能追加一个节点;append()
没有返回值,appendChild()
返回追加的 Node 对象。
除此之外,两个方法其他部分都是相同的。
如果添加的节点是已经存存在与文档上,那么结果就是该节点会从文档中原来的位置移动到新的位置。类似于剪切操作,并且被操作的节点的子节点会跟随父节点一起移动。
还有一些其他的操作方法:
Element.insertBefor()
:在特定子元素前插入一个新的子元素;Element.replaceChild()
:替换特定子元素;Element.cloneNode()
:创建调用节点的副本,通过一个布尔参数来决定是否执行深复制,浅复制则不包括其子节点。所有类型节点都有该方法;normalize()
:唯一的左右就是处理文档树中的文本节点。
Document 类型
JavaScript 通过 Document 类型来表示文档,也就是经常使用的document
对象。document
对象是 HTMLDocument 的一个实例。而 HTMLDocument 则继承自 Document 类型。
Document --> HTMLDocument.prototype --> document
document 对象是 window 的一个属性,所以它可以全局访问。它有一下特点:
- nodeType 为 9;
- nodeName 为
#document
; - 其他值均为
null
;
它有四种可能的子节点:DocumentType、Element、ProcessingInstruction 或 Comment。
其中 DocumentType 和 Element 都最多只能有一个,他们就是 HTML 文档最常见的开头:
<!DOCTYPE html>
<html lang="en">
早期 IE 无法访问 Document 类型的构造函数和原型。
文档子节点
document.documentElement
始终指向<HTML>
元素;document.body
指向<body>
元素;document.doctype
指向<!DOCTYPE html>
。
不同浏览器之间对于<html>
标签之外的注释有着不同的见解,这也导致了在<html>
元素外的注释没有什么用处。
文档信息
document.title
:可以直接获取文档标题。
使用document.title
配合两个定时器就可以设置一个经典的 Title 切换的方法:
setInterval(() => {
document.title = '小肥羊'
setTimeout(() => {
document.title = '无敌螺旋小肥羊'
}, 500);
}, 1000);
document.URL
:获取当前文档的完整URL;document.domain
:获取当前域名;document.referrer
:获取来源页面URL。
使用document.referrer
就可以轻松判断跳转自页面,可以配合 Live2d 做个简单的识别。
document.referrer
// "https://www.baidu.com/link?url=Lx3l1h452xnMy39DdUnl4y2Dl84m7Rb22M2CCwvH6n033Jr7EmLO_LxUYYmg3VMx&wd=&eqid=ba12bd2a000c1485000000055ffdadd3"
查找元素
Document 类型有两个可以取得 DOM 元素的方法:
getElementById()
getElementByTagName()
getElementById()
在早期的 IE7 中调用可能会获取到带有name="test"
的 Attribute 其他元素的 Bug。
getElementByTagName()
返回的是一个 HTMLCollection 对象,它是一个动态的集合。该对象与 NodeList 非常类似,他们都可以使用方括号语法或item()
方法来访问其中的项目。
div[0];
div.item(0);
// 使用名称
div['myDiv'];
HTMLDocument 类型也有一个独有的查询 DOM 的方法:
getElementByName()
:返回带有特定 name Attribute 的元素。
同样的,它返回的也是一个 NodeList 集合。
特殊集合
除了一些常见的属性和方法之外,document 对象还提供了一些特殊的集合。这些集合都是 HTMLDocument 对象为访问文档常用的部分提供了快捷方式。
document.anchors
:文档中所有带 name 特性的<a>
元素;document.applets
:文档中所有<applets>
元素,已经弃用;document.forms
:文档中所有<form>
元素;document.images
:文档中所有<img>
元素,与document.getElementByTagName('img')
结果相同;document.links
:文档中所有带herf
特性的<a>
元素。
这些特殊的集合也都是动态的。
DOM 一致性检测
DOM 分为多个级别,也包含多个部分,因此检测浏览器实现了 DOM 的哪些部分就十分必要了。document.implementation
属性就是提供检测方法的对象。
文档写入
将输出流写入到网页中的能力已经存在很多年了,这个能力体现在四个方法中:write()
、writeIn()
、open()
和close()
。
写入文本:
write()
:接受一个参数,原样写入;writeIn()
:接受一个参数,添加换行符\n
写入。
<body>
<p>The current date and time is:</p>
</body>
<script>
document.write(`<strong>${(new Date()).toString()}</strong>`)
</script>
在页面呈现期间直接使用document.write()
向页面添加内容是正常的,但是如果等页面渲染完毕了再使用document.write()
添加内容就会重写覆盖整个页面。
Element 类型
除了 Document 类型之外,Element 类型就是 Web 编程中最常用的类型了。它用于表现 XML 或 HTML 元素。
- nodeType 为 1;
- nodeName 为元素的标签名;
- nodeValue 为 null;
- parentNode 可能是 Document 或 Element;
要取得元素的标签名,除了使用nodeName
外,还可以使用tagName
。二者返回的值相等。
ele.tagName == ele.nodeName // true
HTML 元素
HTMLElement 类型继承自 Element 并添加了一些属性,所有的 HTML 元素都由他们和其子类型表示。
可以直接访问/修改 HTML 元素的属性:
<div id="the_div" class="active" title="balabala">
let div = document.querySelector('.active');
div.id;
div.className;
div.title;
取得特性
element.getAttribute()
可以获取非元素属性的 Attribute 值,通常用来设置一些自定义的值。
<div id="the_div" class="active" title="balabala" data-v-xfy="balabala">
let div = document.querySelector('#the_div');
div.getAttribute('data-v-xfy');
行内样式 style 和 onclick 这两个属性通过element.getAttribute()
返回的值并不相同。
style 属性返回的是以编程方式访问的元素样式,onclick 返回的是相应的 JavaScript 代码字符串。
设置特性
element.setAttribute()
可以设置 Attribute 值,主要作用与element.getAttribute()
相同。
attributes
attributes 返回一个 NamedNodeMap 与 NodeList 类似,也是一个“动态”的集合。
它也拥有类似的方法:
getNamedItem(name)
setNamedItem(name)
removeNamedItem(name)
item(pos)
当然它还是一个类数组,可以直接使用方括号语法来访问对应的属性:
div.attributes['id']
// id="the_div"
div.attributes[0]
// data-v-xfy="balabala"
div.attributes[0].nodeValue
// "balabala"
attributes
方法虽然不常用,但是它可以用于遍历元素的所有 Attributes:
function outputAttributes(node) {
let pairs = [];
for (let i of node.attributes) {
pairs.push(`${i.nodeName}=${i.nodeValue}`)
}
return pairs.join(' ');
}
创建元素
使用document.createElement()
方法可以创建新元素,同时还可以设置元素的特性,添加更多的子节点,以及执行其他操作。
let div = document.createElement('div');
div.id = 'root';
div.className = 'active';
IE 支持传入整个完整的元素标签,也可以包含属性。
元素的子节点
element.childNodes
属性能够遍历所有的子节点,不仅仅只是元素节点。
for (let i of div.childNodes) {
if (i.nodeType == 1) {
// do something...
}
}
此方法只会遍历直接子元素,如果要取得包含的所有子元素,可以使用getElementsByTagName('')
:
let ul = document.querySelector('ul');
let li = ul.getElementsByTagName('li');
Text 类型
文本节点由 Text 类型表示。纯文本中可以包含转义后的 HTML 字符,但是不能包含 HTML 代码。
- nodeType 为 3;
- nodeName 为
#text
; - nodeValue 的值为节点所包含的文本;
- parentNode 是一个 Element;
在默认情况下,每个可以包含内容的元素最多只能有一个文本节点,而且必须确实有内容存在。
创建文本节点
使用document.createTextNode()
可以创建新文本节点。
var element = document.createElement("div");
element.className = "message";
var textNode = document.createTextNode("Hello world!");
element.appendChild(textNode);
document.body.appendChild(element);
在某些情况下,一个元素内也可能包含多个子文本节点。
var element = document.createElement("div");
element.className = "message";
var textNode = document.createTextNode("Hello world!");
element.appendChild(textNode);
var anotherTextNode = document.createTextNode("Yippee!");
element.appendChild(anotherTextNode);
document.body.appendChild(element);
如果两个文本节点是相邻的同胞节点,那么这两个节点中的文本就会连起来显示,中间不会有空格。
规范化文本节点
当一个元素中存在多个文本节点时,就难以分清哪个节点对应哪些文本。在父元素下使用normalize()
方法可以所有文本节点合并成一个节点。这个方法是由 Node 定义的,因而在所有节点类型中都存在。
var element = document.createElement("div");
element.className = "message";
var textNode = document.createTextNode("Hello world!");
element.appendChild(textNode);
var anotherTextNode = document.createTextNode("Yippee!");
element.appendChild(anotherTextNode);
document.body.appendChild(element);
console.log(element.childNodes.length) // 2
element.normalize()
console.log(element.childNodes.length) // 1
分割节点
与 normalize()
相反,splitText()
用于按照指定位置分割文本节点。
element.childNodes.splitText(5)
Comment 类型
注释是通过 Comment 类型来表示的。
- nodeType 为 8;
- nodeName 为
#comment
; - nodeValue 为注释的内容;
- parentNode 可能是 Document 或 Element;
- 不支持(没有)子节点;
浏览器不会识别位于<html>
标签外的注释。
DocumentType 类型
- nodeType 为 10;
- nodeName 为 doctype 名;
- nodeValue 为 null
- parentNode 为 Document;
- 不支持(没有)子节点;
在 DOM1 级中,DocumentType 对象不能动态创建。
DocumentFragment 类型
DocumentFragment 是一种文档片段,它可以在插入文档前操作 DOM,不会像完整文档那样占用资源。
- nodeType 为 11;
- nodeName 为
#document-fragment
; - nodeValue 为 null;
- parentNode 为 null;
- 子节点可以是大部分节点类型;
let frag = document.createDocumentFragment();
let ul = document.createElement('ul');
for (let i = 0; i < 5; i++) {
let li = document.createElement('li');
li.innerText = `Item ${i + 1}`
ul.append(li);
}
frag.append(ul);