一、DOM概述
二、节点和节点树概述
三、Node接口
1. Node接口概述
2. Node接口属性
3. Node接口方法
四、动态集合:NodeList、HTMLCollection和NamedNodeMap
1. NodeList接口(节点的集合)
(1)NodeList接口概述
(2)NodeList接口属性和方法
2. HTMLCollection接口(元素节点的集合)
(1)HTMLCollection接口概述
(2)HTMLCollection接口属性和方法
3. NamedNodeMap接口(属性节点的集合)
(1)NamedNodeMap接口概述
(2)(2)NameNodeMap接口属性和方法
4. 为什么动态(live)NodeList 比 静态(static)NodeList 更快
五、ParentNode接口
(1)ParentNode接口概述
(2)ParentNode接口属性和方法
六、ChildNode接口
(1)ChildNode接口概述
(2)ChildNode接口属性和方法
七、document接口(节点类型)(对象)
(1)document接口概述
(2)document接口属性
(3)document接口方法
附录:程序
一、DOM概述
《JavaScript权威指南》第15章:脚本化文档
- 每个Window对象有一个document属性引用了Document对象。
- Document对象表示窗口的内容,其并非独立的,而是一个巨大的API(应用程序编程接口)中的核心对象,这个API叫做DOM(Document Object Model,文档对象模型)——Document对象是DOM API的核心对象
- DOM是表示和操作HTML和XML文档内容的基础API
《JavaScript高级程序设计》第10章:DOM
- DOM是针对HTML和XML文档的一个API(接口)
- DOM描绘了一个层次化的节点树,允许开发人员添加、移除和修改页面的某一部分
- DOM脱胎于Netscape及微软公司创始的DHTML(动态HTML),但现在已经成为表现和操作页面标记的真正的跨平台、语言中立的方式
- 1998年10月DOM1级规范成为W3C的推荐标准,为基本的文档结构及查询提供了接口
网道 https://wangdoc.com/javascript/dom/general.html
- DOM是JavaScript操作网页的接口,作用是将网页转为一个JavaScript对象,从而可以用脚本进行各种操作——脚本化Web页面内容是JavaScript的核心目标
- 浏览器会根据DOM,将结构化文档(比如HTML和XML)解析成一系列的节点,再由这些节点组成一个树状结构(DOM Tree)。所有的节点和最终的树状结构,都有规范的对外接口
- DOM只是一个接口规范,可以用各种语言实现。所以严格地说,DOM不是JavaScript语法的一部分。
但是一方面DOM操作是JavaScript最常见的任务,离开了DOM,JavaScript就无法控制网页。
另一方面,JavaScript也是最常用于DOM操作的语言。
二、节点和节点树概述
网道
- 节点(node)是DOM的最小组成单位
- DOM树(文档的树状结构)就是由一个文档的所有各种不同类型的节点按照所在的层级抽象成的一种树状结构。每个节点可以看作是文档树的一片叶子
- HTML文档(document文档节点)的第一层有两个子节点
①文档类型节点:<!doctype html>
②文档元素节点:<html>
《JavaScript权威指南》
- HTML文档的树状结构(即HTML的DOM树)包含
①表示HTML标签或元素的节点
②表示文本字符串的节点
③也可能包含表示HTML注释的节点
《JavaScript高级程序设计》
- DOM可以将任何HTML或XML文档描绘成一个由多层节点组成的结构
- 节点分为几种不同的类型
①每种类型分别表示文档中不同的信息及(或)标记
②每个节点都拥有各自的特点、数据和方法
③每个节点也与其他节点存在某种关系——节点之间的关系构成了层次
④所有页面标记表现为一个以特定节点为根节点的树形结构- 文档节点(Document)
①文档节点是每个文档的根节点
②文档元素(<html>)是文档的最外层元素,文档中的其他所有元素都包含在文档元素中
③在HTLM页面中,文档元素始终都是<html>元素
④在XML页面中,由于没有预定义的元素,因此任何元素都可能成为文档元素- 每一段标记都可以通过树中的一个节点来表示
①HTML元素通过元素节点表示
②特性(attribute)通过特性节点表示
③文档类型通过文档类型节点表示
④注释通过注释节点表示
——总共有12种节点类型,均继承自一个基类型(Node类型)
三、Node接口
1. Node接口概述
《JavaScript高级程序设计》
- DOM1级定义了一个Node接口,该接口在JavaScript中是作为Node类型实现的(除IE外,其他所有浏览器都可以访问到这个类型)
- JavaScript中的所有节点类型都继承自Node类型,因此所有节点类型都共享着相同的基本属性和方法
网道
- 所有DOM节点对象都继承了Node接口,拥有一些共同的属性和方法。这是DOM操作的基础
《JavaScript权威指南》
- 文档的一个节点表示一个Node对象
①Document节点(树形的根部):代表整个文档
②Element节点:代表HTML元素
③Text节点:代表文本
——Document、Element和Text都是Node的子类- Node及其子类
①Document:代表一个HTML或XML文档
HTMLDocument:只是针对于HTML文档
②Element:代表HTML或XML文档中的一个元素
HTMLElement::只是针对于HTML文档
③Attr:代表HTML或XML属性,但几乎从不使用
2. Node接口属性
(1)节点信息属性
属性 | 功能 |
nodeType | 返回一个整数值,表示节点的类型,属性值见表1:nodeType常量 |
nodeName | 返回节点的名称,属性值见表2:不同节点的nodeName属性值 |
nodeValue | 返回一个字符串,表示当前节点本身的文本值,可读写 ,属性值见下文 |
textContent | 返回当前节点和它的所有后代节点的文本内容(自动忽略当前节点内部的HTML标签),可读写 (自动对HTML标签转义,即不支持HTML标签),属性值见下文 |
- 注意:由于IE没有公开Node类型的构造函数,因此为了确保跨浏览器兼容,最好将nodeType属性与数字值进行比较
- nodeValue属性值:只有文本节点(text)、注释节点(comment)和属性节点(attr)有文本值,因此只有这三类节点的nodeValue可以返回结果,其他类型的节点一律返回
null
;也只有这三类节点可以设置nodeValue属性的值,其他类型的节点设置无效
- textContent属性值:文本节点(text)、注释节点(comment)和属性节点(attr)同nodeValue属性值;
对于其他类型的节点,会将每个子节点(不包括注释节点)的内容连接在一起返回
如果一个节点没有子节点,则返回空字符串
常量 | 对应节点及常量值 |
Node.ELEMENT_NODE | 元素节点(element):1 |
Node.ATTRBUTE_NODE | 属性节点(attr):2 |
Node.TEXT_NODE | 文本节点(text):3 |
Node.CDATA_SECTION_NODE | 4 |
Node.ENTITY_REFERENCE_NODE | 5 |
Node.ENTITY_NODE | 6 |
Node.PROCESSING_NODE | 7 |
Node.COMMENT_NODE | 注释节点(comment):8 |
Node.DOCUMENT_NODE | 文档节点(document):9 |
Node.DOCUMENT_TYPE_NODE | 文档类型节点(documentType):10 |
Node.DOCUMENT_FRAGMENT_NODE | 文档片段节点(DocumentFragment):11 |
Node.NOTATION_NODE | 12 |
节点类型 | 节点 | 属性值 |
1 | 元素节点(element) | 大写的标签名 |
2 | 属性节点(attr) | 属性的名称 |
3 | 文本节点(text) | #text |
8 | 注释节点(comment) | #comment |
9 | 文档节点(document) | #document |
10 | 文档类型节点(documentType) | 文档的类型 |
11 | 文档片段节点(documentFragment) | #document-fragment |
(2)节点关系属性(基于节点树,除了parentElement基于元素节点树)
属性 | 功能 | 说明 |
childNodes | 返回一个类数组对象(NodeList集合),成员包括当前节点的所有子节点 | 1. NodeList 见下文2. 除了元素节点,childNodes属性的返回值还包括文本节点(Text)和注释节点(Comment) 3. 文档节点(document)只有两个子节点:文档类型节点(docType)和HTML根元素节点 4. 虽然所有节点类型都继承自Node,但并不是每种节点都有子节点 |
parentNode | 返回当前节点的父元素节点 | 1. 一个节点的父节点只可能是三种类型:元素节点(element)、文档节点(document)和文档片段节点(documentFragment) 2. 文档节点(document)和文档片段节点(documentFragment)的父节点都是null 3. 生成后还没有插入DOM树的节点的父节点也是null |
parentElement | 返回当前节点的父元素节点;如果当前节点没有父节点,或者父节点类型不是元素节点(Element),则返回null | 由于父节点只可能是三种类型:元素节点(Element)、文档节点(document)和文档片段节点(DocumentFragment)。parentElement属性相当于把后两种父节点都排除了 |
previousSibling | 返回当前节点前面的第一个同级节点;如果没有,则返回null | 该属性还包括文本节点(Text)和注释节点(Comment)。因此如果当前节点前面还有空格,该属性会返回一个文本节点,内容为空格 |
nextSibling | 返回当前节点后面的第一个同级节点;如果没有,则返回null | 同previousSibling属性,还包括文本节点和注释节点 |
firstChild | 返回当前节点的第一个子节点;如果没有,则返回null | 包含文本节点和注释节点 |
lastChild | 返回当前节点的最后一个子节点;如果没有,则返回null | 包含文本节点和注释节点 |
(3)其他属性
ownerDocument | 该属性指向表示整个文档的文档节点,即当前节点所在的顶层文档对象(document对象) | 1. 存在意义:表示任何节点都属于它所在的文档,任何节点都不能同时存在于两个或更多个文档中 2. 通过这个属性,可以不必在节点层次中通过层层回溯到达顶端,而是可以直接访问文档节点 |
isConnected | 返回一个布尔值,表示当前节点是否在文档之中 | 对于脚本生成的节点,在没有插入文档之前,inConnected属性返回false;插入之后,返回true |
baseURI | 只读,返回一个节点的绝对基址URL。浏览器会根据这个属性,解析网页上的相对路径的URL。如果无法获取,可能返回null | 1. baseURI而不是baseURL 2. 可以通过document.baseURI(默认为window.location.href)获取文档的基URL 3. 注意:检查文档的基URL可能会每次请求返回不同的结果,因为<base>标签或文档的location可能被改变了 |
3. Node接口方法
(1)操作节点方法
方法 | 功能 | 说明 |
appendChild() | 用于向指定父节点的childNodes列表的末尾添加一个节点,返回插入文档的节点 | 1. 语法:var appendNode = parentNode.appendChild(node); 2. 参数节点是DOM已经存在的节点,则将该节点从原来的位置移动到新位置(即父节点的最后一个子节点)——这意味着,一个节点不可能同时出现在文档的不同位置 3. 参数节点是DocumentFragment节点,则插入的是其所有子节点,而不是DocumentFragment节点本身。此时,返回值是一个空的DocumentFragment节点(#document-fragment) 4. 此方法只能将某个子节点插入到同一个文档的其他位置,而不能跨文档插入 |
insertBefore() | 用于将某个节点插入到指定父节点内部的某个参考节点之前,返回被插入的子节点 | 1. 语法:var insertNode = parentNode.insertBefore(newNode, referenceNode); 2. 如果referenceNode为null,则newNode将被插入到子节点的末尾(执行和appendChild()相同的操作) 3. 如果newNode是当前DOM已经存在的节点,则将该节点移动到新位置(referenceNode之前) 4. 如果newNode是DocumentFragment节点,则同appendChild() 5. 可以使用该方法结合nextSibling属性模拟"insertAfter"方法: parent.insetBefore(newNode, referenceNode.nextSibling); |
replaceChild() | 用于将一个新的节点替换当前节点的一个子节点,并返回被替换掉的节点 | 1. 语法:var replaceNode = parentNode.replaceChild(newChild, oldChild); 2. 如果newNode是当前DOM已经存在的节点,则同样将该节点替换掉oldNode 3. 如果newNode是DocumentFragment节点,则同appendChild() 4. 在使用replaceChild()插入一个节点时,该节点的所有关系指针都会从被它替换的节点复制过来。尽管从技术上讲,被替换的节点仍然还在文档中,但它在文档中已经没有了自己的位置 |
removeChild() | 用于移除一个指定的子节点,返回被移除的节点 | 1. 被移除的节点仍然存在于内存中,只是没有添加到当前文档的DOM树中。因此,一个节点移除以后,依然可以使用它,比如将这个节点重新添加回文档中。 2. 如果没有使用变量来保存对这个节点的引用,则认为被移除的节点已经是无用的,在短时间内将会被内存管理回收 |
注意:这四个方法操作的都是某个节点的子节点,也就是说,要使用这几个方法必须先取得父节点。另外,因为并不是所有类型的节点都是子节点,所以如果在不支持子节点的节点上调用了这些方法,将会导致错误发生:Uncaught TypeError: Cannot read property ‘appendChild’ of null
cloneNode() | 用于创建调用这个方法的节点的一个完全相同的副本并返回该副本 | 1. 语法:var dupNode = node.cloneNode(deep); deep(可选):是否采用深度克隆 ①true:深克隆,会复制整棵DOM子树(包括那些可能存在的Text子节点) ②false:浅克隆,只复制节点本身,该节点所包含的所有文本都不会被克隆 (因为文本本身也是一个或多个Text节点) 2. 克隆一个元素节点会拷贝该节点的所有属性以及属性值,当然也就包括了属性上绑定的事件(比如οnclick="alert(1)"),但不会拷贝那些使用addEventListener()方法或node.onclick = fn这种JavaScript动态绑定的事件 3. 该方法返回的节点副本不在文档之中,即没有父节点,必须使用诸如appendChild()这样的方法添加到文档之中 4. 为了防止一个文档中出现两个id重复的元素,使用cloneNode()方法克隆的节点在需要时应该指定另外一个与原id值不同的id。如果原节点有name属性,可能也需要修改(取决于是否希望有相同名称的节点存在于文档中) |
normalize() | 用于清理当前节点内部的所有文本节点(text) | 1.去除空的文本节点,并且将毗邻的文本节点合并成一个,也就是说不存在空的文本节点,以及毗邻的文本节点 2. 空的文本节点并不包括空白字符(空格、换行等)构成的文本节点 3. 两个以上相邻文本节点的产生原因包括 ①通过脚本调用有关的DOM接口进行了文本节点的插入和分割等 ②HTML中超长的文本节点会被浏览器自动分割为多个相邻文本节点 4. 当在某个节点上调用这个方法时,就会在该节点的后代节点中查找 ①如果找到了空文本节点,则删除它; ②如果找到相邻的文本节点,则将它们合并为一个文本节点 |
(2)检测节点方法
方法 | 功能 | 说明 |
hasChildNodes() | 返回一个布尔值,用于检测当前节点是否有子节点 | 1. 此方法检测的子节点包括所有类型的节点,而不仅仅是元素节点(Element)。即使检测检节点只包含一个空格,hasChildNodes()方法也会返回true 2. 判断当前节点是否有子节点的三种方法 ①node.firstChild !== null ②node.childNodes.length > 0 ③node.hasChildNodes() |
contains() | 返回一个布尔值,用于检测传入的节点是否为该节点的后代节点 | 如果传入节点本身,返回true |
isEqualNode() | 返回一个布尔值,用于检测两个节点是否相等 | 节点相等:类型相同、属性相同、子节点相同 |
isSameNode() | 返回一个布尔值,用于检测两个节点是否为同一个节点,即指向同一个对象 | 该方法已经在DOM 4被废弃 |
四、动态集合:NodeList、HTMLCollection和NamedNodeMap
动态集合:DOM结构的变化能够自动反应到所保存的对象中
网道
节点都是单个对象,有时需要一种数据结构,能够容纳多个节点
《JavaScript高级程序设计》
理解NodeList及其“近亲”NamedNodeMap和HTMLCollection,是从整体上透彻理解DOM的关键所在。这三个集合都是“动态的”;换句话说,每当文档结构发生变化时,它们都会得到更新。因此,它们始终都会保存着最新、最准确的信息。
1. NodeList接口(节点的集合)
(1)NodeList接口概述
MDN
- NodeList对象是一个节点的集合,是由Node.childNodes属性和document.querySelectorAll()方法(
见下文
)返回的。- NodeList不是一个数组,而是一个类似数组的对象(Like Array Object)。
- 在一些情况下,NodeList是一个实时集合,也就是说,如果文档中的DOM树发生变化,NodeList也会随之变化。例如,Node.ChildNodes是实时的
- 在其他情况下,NodeList是一个静态集合,也就意味着随后对DOM的任何改动都不会影响集合的内容。比如,document.querySelectorAll()就会返回一个静态NodeList。
《JavaScript程序设计》
- 每个节点都有一个childNodes属性,其中保存着一个NodeList对象。NodeList是一种类数组对象,用于保存一组有序的节点,可以通过位置来访问这些节点。
- NodeList的独特之处在于,它实际上是基于DOM结构动态执行查询的结果,因此DOM结构的变化能够自动反应在NodeList对象中。我们常说,NodeList是有生命、有呼吸的对象,而不是在我们第一次访问它们的某个瞬间拍摄下来的一张快照
- 一般来说,应该尽量减少访问NodeList的次数。因为每次访问NodeList,都会运行一次基于文档的查询。所以,可以考虑将NodeList中取得的值缓存起来
- 理解DOM的关键,就是理解DOM对性能的影响。DOM操作往往是JavaScript程序中开销最大的部分,而因访问NodeList导致的问题为最多。NodeList对象都是“动态的”,这就意味着每次访问NodeList对象,都会运行一次查询。有鉴于此,最好的办法就是尽量减少DOM操作。
- querySelectorAll()方法返回的是一个NodeList的实例。具体来说,返回的值实际上是带有所有属性和方法的NodeList,而其底层实现则类似于一组元素的快照,而非不断对文档进行搜索的动态查询。这样实现可以避免使用NodeList对象通常会引起的大多数性能问题。(如果没有找到匹配的元素,返回的NodeList就是空的)
网道
- NodeList的成员是节点对象
- 目前,只有childNodes属性返回的是一个动态集合,其他的NodeList都是静态集合
(2)NodeList接口属性和方法
,NodeList实例很像数组,可以使用length属性和forEach()方法。但是,它不是数组,不能使用pop()或push()之类数组特有的方法
属性 / 方法 | 功能 | 说明 |
length | 返回NodeList中包含的节点个数 | 1. 对于不存在的HTML标签,length属性返回0 2. length属性表示的是访问NodeList的那一刻包含的节点数量 |
forEach() | 用于遍历NodeList的所有成员 | 用法与数组实例的forEach()方法完全一致 |
item() | 用于根据给定的索引,返回一个NodeList对象中包含的Node对象 | 1. 索引大于实际长度或者索引不合法(比如负数),item()方法返回null;省略索引,报错 2. 所有类数组对象,都可以使用方括号运算符取出成员。一般情况下,都是使用方括号运算符,而不是用item()方法。不过,在这种情况下,越界访问和非法访问将返回undefined |
keys() values() entries() |
均返回一个ES6的遍历器/迭代器对象(iterator) keys()返回键名的遍历器 values()返回键值的遍历器 entries()返回键名和键值的遍历器 |
entries()返回的每一项键名和键值保存在一个数组中,其中键值是一个Node对象 |
2. HTMLCollection接口(元素节点的集合)
(1)HTMLCollection接口概述
网道
- HTMLCollection是一个节点对象的集合,只能包含元素节点(Element)
- HTMLCollection实例都是动态集合,节点的变化会实时反映在集合中
- 如果元素节点有 id 或 name 属性,那么在HTMLCollection实例上可以使用 id 属性 或 name 属性引用该节点元素。如果没有对应的节点,则返回 null
《JavaScript高级程序设计》
HTMLCollection对象对于Web应用的性能而言是巨大的损害——任何时候要访问HTMLCollection(不管是一个属性还是一个方法)都是在文档上进行一个查询,这个查询开销很昂贵。最小化访问HTMLCollection的次数可以极大地改进脚本的性能
MDN
- getElementsByName():返回一个包括所有给定name属性值的元素的NodeList集合。
IE和Edge都返回一个HTMLCollection,而不是NodeList。
- getElementsByTagName():返回一个包括所有给定标签名称的元素的HTML集合HTMLCollection。
最新的W3C规范说明这些元素是HTMLCollection(HTML集合);然而,这个方法在Webkit内核的浏览器中返回一个NodeList。
读者注:chrome80 返回的依然是一个HTMLCollection- getElementsByClassName():返回一个包括所有给定类名的元素的HTMLCollection集合
- HTMLTableElement.tBodies
HTMLTableElement.rows
HTMLTableRowElement.cells- document.anchors
document.applets
document.forms
document.images
document.links
(2)HTMLCollection接口属性和方法
属性 / 方法 | 功能 | 说明 |
length | 返回HTMLCollection中包含的节点个数 | 同NodeList.length |
item() | 用于根据给定的索引,返回一个HTMLCollection对象中包含的Element对象 | 同NodeList.item() |
namedItem() | 接收一个表示 id 属性或 name 属性的值,返回对应的节点。如果没有对应的节点,则返回null | 根据 name 属性的值匹配只能作为最后的以来,并且只有当被引用的元素支持 name 属性时才能被匹配 |
3. NamedNodeMap接口(属性节点的集合)
(1)NamedNodeMap接口概述
《JavaScript高级程序设计》
- Element类型是使用attributes属性的唯一一个DOM节点类型。attributes属性中包含一个NamedNodeMap,与NodeList类似,也是一个“动态的”集合。元素的每一个特性都由一个Attr节点表示,每个Attr节点都保存在NamedNodeMap对象中
- NamedNodeMap集合中的节点拥有nodeName和nodeValue属性(见前文Node接口属性),分别表示属性的名称和属性的值。
MDN
- NameNodeMap接口表示属性节点Attr对象的集合。
- 尽管在NameNodeMap里面的对象可以像数组一样通过索引来访问,但和NodeList不同,NameNodeMap对象的顺序没有指定
(2)NameNodeMap接口属性和方法
属性 / 方法 | 功能 |
length | 返回NameNodeMap中包含的节点个数 |
item() | 返回指定索引处的属性节点。越界访问或非法访问返回null。 不能通过方括号运算符进行访问(返回undefined) |
getNamedItem() | 返回与给定属性名对应的属性节点,如果没有对应名称的属性则返回null |
setNamedItem() | 替换或添加一个属性节点到NameNodeMap中。如果指定的属性节点已存在,则将替换该节点,并返回被替换的节点;否则添加该属性节点,并返回null |
removeNamedItem() | 接收一个字符串类型的属性节点名,用于移除该属性节点。移除成功后,返回被移除的节点;如果传入的属性节点名不存在,则产生一个Uncaught DOMException异常。 |
getNamedItemNS()、setNamedItemNS()和removeNamedItemNS()暂不做探究。
4. 为什么动态(live)NodeList 比 静态(static)NodeList 更快
GitHub https://github.com/cncounter/translation/blob/master/tiemao_2014/NodeList/NodeList.md
- 动态NodeList对象在浏览器中可以更快地被创建并返回,因为它们不需要预先获取所有的信息;而静态NodeList对象从一开始就需要取得并封装所有相关数据。
- 在webkit的源码中对动态NodeList和静态NodeList分别有一个单独的源文件:DynamicNodeList.cpp 和 StaticNodeList.cpp。
动态NodeList对象通过在cache缓存中注册它的存在并创建。从本质上将,创建一个新的动态NodeList对象是非常轻量级的,因为不需要做任何前期工作。
相比之下,静态NodeList对象实例由另一个文件创建,然后循环填充所有的数据。在document中执行静态查询的前期成本比起动态NodeList要显著提高很多倍。如果真正地查看webkit的源码:querySelectorAll() 明确地创建一个返回对象,在其中又使用一个循环来获取每一个结果,并创建最终返回的一个静态NodeList。