mirror of
https://github.com/DefectingCat/DefectingCat.github.io
synced 2025-07-15 16:51:37 +00:00
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
496 lines
21 KiB
Markdown
496 lines
21 KiB
Markdown
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` | 一个 [`元素`](https://developer.mozilla.org/zh-CN/docs/Web/API/Element) 节点,例如 [`p`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/p) 和 [`div`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/div)。 |
|
||
| `Node.TEXT_NODE` | `3` | [`Element`](https://developer.mozilla.org/zh-CN/docs/Web/API/Element) 或者 [`Attr`](https://developer.mozilla.org/zh-CN/docs/Web/API/Attr) 中实际的 [`文字`](https://developer.mozilla.org/zh-CN/docs/Web/API/Text) |
|
||
| `Node.CDATA_SECTION_NODE` | `4` | 一个 [`CDATASection`](https://developer.mozilla.org/zh-CN/docs/Web/API/CDATASection),例如 `<!CDATA[[ … ]]>`。 |
|
||
| `Node.PROCESSING_INSTRUCTION_NODE` | `7` | 一个用于XML文档的 [`ProcessingInstruction`](https://developer.mozilla.org/zh-CN/docs/Web/API/ProcessingInstruction) ,例如 `<?xml-stylesheet ... ?>` 声明。 |
|
||
| `Node.COMMENT_NODE` | `8` | 一个 [`Comment`](https://developer.mozilla.org/zh-CN/docs/Web/API/Comment) 节点。 |
|
||
| `Node.DOCUMENT_NODE` | `9` | 一个 [`Document`](https://developer.mozilla.org/zh-CN/docs/Web/API/Document) 节点。 |
|
||
| `Node.DOCUMENT_TYPE_NODE` | `10` | 描述文档类型的 [`DocumentType`](https://developer.mozilla.org/zh-CN/docs/Web/API/DocumentType) 节点。例如 `<!DOCTYPE html>` 就是用于 HTML5 的。 |
|
||
| `Node.DOCUMENT_FRAGMENT_NODE` | `11` | 一个 [`DocumentFragment`](https://developer.mozilla.org/zh-CN/docs/Web/API/DocumentFragment) 节点 |
|
||
|
||
**已经弃用的节点类型**
|
||
|
||
| 常量 | 值 | 描述 |
|
||
| ---------------------------- | --- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||
| `Node.ATTRIBUTE_NODE` | 2 | [`元素`](https://developer.mozilla.org/zh-CN/docs/Web/API/Element) 的耦合[`属性`](https://developer.mozilla.org/zh-CN/docs/Web/API/Attr) 。在 [DOM4](https://www.w3.org/TR/dom/) 规范里[`Node`](https://developer.mozilla.org/zh-CN/docs/Web/API/Node) 接口将不再实现这个元素属性。 |
|
||
| `Node.ENTITY_REFERENCE_NODE` | 5 | 一个 XML 实体引用节点。 在 [DOM4](https://www.w3.org/TR/dom/) 规范里被移除。 |
|
||
| `Node.ENTITY_NODE` | 6 | 一个 XML `<!ENTITY ...>` 节点。 在 [DOM4](https://www.w3.org/TR/dom/) 规范中被移除。 |
|
||
| `Node.NOTATION_NODE` | 12 | 一个 XML `<!NOTATION ...>` 节点。 在 [DOM4](https://www.w3.org/TR/dom/) 规范里被移除. |
|
||
|
||
通过比较`nodeType`属性中包含的上述常量,就可以确定节点的类型。由于早期的 IE 没有公开 Node 类型,为了兼容所有浏览器,可以使用`nodeType`属性的数值量进行对比:
|
||
|
||
```js
|
||
if (someNode.nodeType === 1) {
|
||
console.log(`this is an element`)
|
||
}
|
||
```
|
||
|
||
要详细的查看节点的信息,还有`nodeName`和`nodeValue`属性。这两个属性的值完全取决于节点的类型。对于 Element 节点,`nodeName`就是其标签名,而`nodeValue`始终为`null`。
|
||
|
||
```js
|
||
let c = document.body.appendChild(document.createComment('test'));
|
||
c.nodeName // "#comment"
|
||
c.nodeValue // "test"
|
||
```
|
||
|
||
#### 节点关系
|
||
|
||
每个节点都有一个`childNodes`属性,其中保存着一个 NodeList 对象,这个对象是一个类数组对象,用于保存一组有序的节点。NodeList 对象的独特之处在于,它实际上是基于 DOM 结构动态查询的结果,而不是静态的。
|
||
|
||
`childNodes`会返回所有子节点,不仅仅只是 Element 元素,可以使用`children`来访问子元素。
|
||
|
||
```js
|
||
div.childNodes
|
||
// NodeList(9) [text, span, text, div, text, div, text, img, text]
|
||
```
|
||
|
||
虽然 NodeList 对象是一个类数组,但是可以通过数组方法`slice()`将其直接转换为数组。
|
||
|
||
```js
|
||
let test = Array.prototype.slice.call(div.childNodes, 0);
|
||
Object.prototype.toString.call(test)
|
||
// "[object Array]"
|
||
```
|
||
|
||
但是 IE 早期无效,在早期 IE 中只能将其遍历转换为数组。因为其是类数组,所以 NodeList 是可迭代的(但是我的 IE 11 貌似不能用使用`for/of`)。
|
||
|
||
```js
|
||
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`返回父元素节点。
|
||
|
||
```js
|
||
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 文档最常见的开头:
|
||
|
||
```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 切换的方法:
|
||
|
||
```js
|
||
setInterval(() => {
|
||
document.title = '小肥羊'
|
||
setTimeout(() => {
|
||
document.title = '无敌螺旋小肥羊'
|
||
}, 500);
|
||
}, 1000);
|
||
```
|
||
|
||
* `document.URL`:获取当前文档的完整URL;
|
||
* `document.domain`:获取当前域名;
|
||
* `document.referrer`:获取来源页面URL。
|
||
|
||
使用`document.referrer`就可以轻松判断跳转自页面,可以配合 Live2d 做个简单的识别。
|
||
|
||
```js
|
||
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()`方法来访问其中的项目。
|
||
|
||
```js
|
||
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`写入。
|
||
|
||
```html
|
||
<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`。二者返回的值相等。
|
||
|
||
```js
|
||
ele.tagName == ele.nodeName // true
|
||
```
|
||
|
||
#### HTML 元素
|
||
|
||
HTMLElement 类型继承自 Element 并添加了一些属性,所有的 HTML 元素都由他们和其子类型表示。
|
||
|
||
可以直接访问/修改 HTML 元素的属性:
|
||
|
||
```html
|
||
<div id="the_div" class="active" title="balabala">
|
||
```
|
||
|
||
```js
|
||
let div = document.querySelector('.active');
|
||
div.id;
|
||
div.className;
|
||
div.title;
|
||
```
|
||
|
||
#### 取得特性
|
||
|
||
`element.getAttribute()`可以获取非元素属性的 Attribute 值,通常用来设置一些自定义的值。
|
||
|
||
```html
|
||
<div id="the_div" class="active" title="balabala" data-v-xfy="balabala">
|
||
```
|
||
|
||
```js
|
||
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)`
|
||
|
||
当然它还是一个类数组,可以直接使用方括号语法来访问对应的属性:
|
||
|
||
```js
|
||
div.attributes['id']
|
||
// id="the_div"
|
||
div.attributes[0]
|
||
// data-v-xfy="balabala"
|
||
|
||
div.attributes[0].nodeValue
|
||
// "balabala"
|
||
```
|
||
`attributes`方法虽然不常用,但是它可以用于遍历元素的所有 Attributes:
|
||
|
||
```js
|
||
function outputAttributes(node) {
|
||
let pairs = [];
|
||
for (let i of node.attributes) {
|
||
pairs.push(`${i.nodeName}=${i.nodeValue}`)
|
||
}
|
||
return pairs.join(' ');
|
||
}
|
||
```
|
||
|
||
#### 创建元素
|
||
|
||
使用`document.createElement()`方法可以创建新元素,同时还可以设置元素的特性,添加更多的子节点,以及执行其他操作。
|
||
|
||
```js
|
||
let div = document.createElement('div');
|
||
div.id = 'root';
|
||
div.className = 'active';
|
||
```
|
||
|
||
> IE 支持传入整个完整的元素标签,也可以包含属性。
|
||
|
||
#### 元素的子节点
|
||
|
||
`element.childNodes`属性能够遍历所有的子节点,不仅仅只是元素节点。
|
||
|
||
```js
|
||
for (let i of div.childNodes) {
|
||
if (i.nodeType == 1) {
|
||
// do something...
|
||
}
|
||
}
|
||
```
|
||
|
||
此方法只会遍历直接子元素,如果要取得包含的所有子元素,可以使用`getElementsByTagName('')`:
|
||
|
||
```js
|
||
let ul = document.querySelector('ul');
|
||
let li = ul.getElementsByTagName('li');
|
||
```
|
||
|
||
### Text 类型
|
||
|
||
文本节点由 Text 类型表示。纯文本中可以包含转义后的 HTML 字符,但是不能包含 HTML 代码。
|
||
|
||
* nodeType 为 3;
|
||
* nodeName 为 `#text`;
|
||
* nodeValue 的值为节点所包含的文本;
|
||
* parentNode 是一个 Element;
|
||
|
||
在默认情况下,每个可以包含内容的元素最多只能有一个文本节点,而且必须确实有内容存在。
|
||
|
||
#### 创建文本节点
|
||
|
||
使用`document.createTextNode()`可以创建新文本节点。
|
||
|
||
```js
|
||
var element = document.createElement("div");
|
||
element.className = "message";
|
||
|
||
var textNode = document.createTextNode("Hello world!");
|
||
element.appendChild(textNode);
|
||
|
||
document.body.appendChild(element);
|
||
```
|
||
|
||
在某些情况下,一个元素内也可能包含多个子文本节点。
|
||
|
||
```js
|
||
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 定义的,因而在所有节点类型中都存在。
|
||
|
||
```js
|
||
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()`用于按照指定位置分割文本节点。
|
||
|
||
```js
|
||
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;
|
||
* 子节点可以是大部分节点类型;
|
||
|
||
```js
|
||
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);
|
||
```
|
||
|