Files
DefectingCat.github.io/source/_md/JavaScript-DOM.md
DefectingCat 2385c24169 挖坑&测试
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
2021-11-16 20:26:44 +08:00

21 KiB
Raw Blame History

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 一个 元素 节点,例如 pdiv
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`)
}

要详细的查看节点的信息,还有nodeNamenodeValue属性。这两个属性的值完全取决于节点的类型。对于 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

所有的子节点相互之间都是兄弟/同胞节点,可以通过使用previousSiblingnestSibling属性来访问相邻的同胞节点。

父节点的firstChildlastChild分别指向 childNodes 列表中的第一个和最后一个节点。也就是说,lastChild等于childNodes.length - 1

除了确定childNodes.length之外,还可以使用hasChildNodes()方法来确定是否拥有子节点。

所有节点的最后一个属性是ownerDocument,该属性指向整个文档的文档节点,也就是document。这种关系表示的任何节点都属于它所在的文档,任何节点都不能同时存在与两个或以上的文档中。有了该属性,就不必层层回溯到顶端,而是可以直接访问文档节点。

所有节点类型都继承自 Node但并不是每种节点都有子节点。

节点关系的小结:

  • 父节点是唯一的;
  • childNodes返回所有类型的子节点,children返回子元素节点;
  • childNodes返回的是类数组;
  • parentNodes返回父节点,parentElement返回父元素节点;
  • previousSiblingnestSibling属性来访问相邻的同胞节点;
  • firstChildlastChild属性来访问第一个和最后一个子节点;
  • 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);