精读《JavaScript 高级程序设计 第4版》第14章 DOM(一)
文档对象模型(DOM, Document Object Model)是HTML和XML文档的编程接口。DOM表示由多层节点构成的文档,通过它,开发者可以添加、删除和修改页面的各个部分。是真正的跨平台、语言无关的表示和操作网页的方式。
14.1 节点层级
DOM 树结构
DOM将HTML文档表示为树形结构,由不同类型的节点组成:
<!DOCTYPE html>
<html><head><title>DOM示例</title></head><body><h1>标题</h1><p>段落文本</p></body>
</html>
对应的DOM树:
文档节点 (Document)
├── 文档类型节点 (DocumentType) <!DOCTYPE html>
├── 元素节点 (Element) <html>├── 元素节点 (Element) <head>│ └── 元素节点 (Element) <title>│ └── 文本节点 (Text) "DOM示例"└── 元素节点 (Element) <body>├── 元素节点 (Element) <h1>│ └── 文本节点 (Text) "标题"└── 元素节点 (Element) <p>└── 文本节点 (Text) "段落文本"
文档元素时文档的最外层元素,每个文档只有一个文档元素。HTML页面中,文档元素始终是<html>元素,在XML中任何元素都有可能。
14.1.1 Node类型
所有DOM节点类型都继承自Node类型,因此所有类型都共享相同的基本属性和方法。
每个节点都有nodeType属性,表示节点的类型,共有12种类型以12个数值表示:
- Node.ELEMENT_NODE(1)
- Node.ATTRIBUTE_NODE(2)
- Node.TEXT_NODE(3)
- Node.CDATA_SECTION_NODE(4)
- Node.ENTITY_REFERENCE_NODE(5)
- Node.ENTITY_NODE(6)
- Node.PROCESSING_INSTRUCTION_NODE(7)
- Node.COMMENT_NODE(8)
- Node.DOCUMENT_NODE(9)
- Node.DOCUMENT_TYPE_NODE(10)
- Node.DOCUMENT_FRAGMENT_NODE(11)
- Node.NOTARION_NODE(12)
节点关系
- parentNode 属性:获取目标节点的父元素节点
- childNodes属性:获取目标节点的子元素节点的类数组对象NodeList,其中包含的子节点元素都拥有共同的父节点,各子节点直接互为同胞节点。
- nextSibling属性:获取下一个同胞节点元素,不存在时为null。
- previousSibling属性:获取上一个同胞节点元素,不存在时为null。
- firstChild属性:获取第一个子节点
- lastChild属性:获取最后一个子节点
操纵节点
- appendChild():在childNodes列表末尾添加节点,并返回新添加的节点
- insertBefore():接收两个参数:要插入的节点和参照节点,将新节点变成参照节点前的一个同胞节点并返回。
- replaceChild():接收两个参数:要插入的节点和要替换的节点,将旧节点替换。
- removeChild():接收一个参数:要移除的节点,返回被移除的节点。
- cloneNode():接收一个布尔值参数,true表示深复制目标节点,包含整个子DOM树;false则只复制调用该方法的节点。
- normalize():处理文档子树中的文本节点。调用后会检查调用节点的所有后代,发现空文本节点则删除,发现相邻同胞节点则合并。
14.1.2 Document类型
Document类型是JavaScript中表示文档节点的类型。在浏览器中文档对象document是HTMLDocument的实例,表示整个HTML页面。document是window对象的属性,因此是一个全局对象。
文档属性与信息
- document.documentElement:文档只有一个子节点,即<html>元素
- document.body:直接指向<body>元素
- document.head:指向<head>元素
- document.title:获取文档标题,赋值修改无效
- document.URL:获取包含当前页的完整URL,只读
- document.domain:获取域名,可修改。
- document.referrer:获取链接到当前页面的上一个页面的完整URL,无来源则返回空串,只读
document.domain详细示例如下:
不能给该属性设置URL中不包含的值:
//当页面来自于p2p.wrox.com时
document.domain = "wrox.com"; //成功
document.domain = "nczsdd.net"; //失败
浏览器限制:该属性值一旦放松后就不能再收紧了:
//当页面来自于p2p.wrox.com时
document.domain = "wrox.com"; //放松,成功
document.domain = "p2p.wrox.com"; //收紧,错误
可用于不同子域的跨域通信:当页面中包含来自某个不同子域的窗格(<frame>)或内嵌窗格(<iframe>)时,可以通过将每个页面的document.domain设置为同一个值,这些页面就可以访问对方的JavaScript对象了。
定位元素
传统方法:
- document.getElementById():接收一个参数,即元素的ID,如:'myId',获取id值匹配的第一个元素,否则返回null;
- getElementsByClassName():接收一个参数,即元素的类名,如'myClass',获取类名匹配的元素返回一个HTMLCollecyion对象与NodeList类似,里面包含多个或零个元素。
- getElementsByTagName():接收一个参数,即标签名,如'img',获取标签名匹配的元素集合HTMLCollecyion对象。
- getElementsByName():接收一个参数,即元素上定义的name属性值,返回一个HTMLCollecyion对象,包含具有该name属性值的所有元素。
现代方法:
- document.querySelector():接收一个参数,可以是类名也可以是id,如'#myId'或'.myClass',返回匹配的第一个元素。
- document.querySelectorAll():接收一个字符串参数,参数内可以包含多个匹配条件,以逗号隔开,如:'.myClass, .otherClass',返回一个NodeList对象,包含匹配的所有元素。
性能对比:querySelectorAll返回静态NodeList,getElementsByClassName返回动态HTMLCollection,推荐使用现代方法。
14.1.3 Element类型
Element表示XML和HTML元素,对外暴露出访问元素标签名、子节点和属性的能力。可以通过nodeName或tagName属性来获取元素的标签名。但tagName属性返回标签名的始终是全大写表示,如“DIV”
HTML元素
所有的HTML元素都通过HTMLElement类型表示,包括其直接实例或间接实例。且HTMLElement直接继承Element并添加了一些属性如:id、title、lang、dir、className。以上这几个属性是所有HTML元素的标准属性,且通过属性进行读写,修改标签的属性。
常用的HTML元素如下:
H1~H6、A、EM、FROM、FRAME、HEAD、LI、P、TABLE......
获取属性
getAttribute()方法,接收一个属性名,获取属性值,属性名不区分大小写。支持自定义属性的获取,根据HTML5规范要求,自定义属性名应该加上前缀data-方便验证。但是如果访问事件处理程序(事件属性)如onclick则返回字符串形式的源代码。
const div = document.querySelector('div');
const value = div.getAttribute('data-custom');//获取自定义属性data-custom的值
设置属性
setAttribute()方法接收两个参数:要设置的属性名和属性值。若存在同名属性则值更新否则创建该属性。注意,该方法接收的属性名用小写形式;
div.setAttribute('data-custom', 'value');
移除属性
removeAttribute()方法接收一个属性名,将该属性从调用的元素上移除。
div.removeAttribute('data-custom');
attributes属性
Element类型是唯一使用attributes属性的DOM节点类型。属性包含下列方法:
- getNamedItem(name),返回nodeName属性等于name的节点
- removeNamedItem(name),删除nodeName属性等于name的节点
- setNamedItem(node),向属性列表中添加node节点,以其nodeName为索引
- item(pos),返回索引位置pos处的节点
attributes属性中每个节点的nodeName是对应属性的名字,nodeValue是属性的值。
//获取元素的id属性值
let id = element.attributes.getNamedItem("id").nodeValue;
//设置id值element.attributes["id"].nodeValue = "someOtherId";
//添加新属性
element.attributes.setNamedItem(newAttr);
//删除属性并返回被删除的节点
let oldAttr = element.attributes.removeNamedItem("id");
创建元素
使用document.createElement()方法创建新元素。该方法接收一个参数,即要创建的元素标签名。
//创建一个div元素
const newElement = document.createElement('div');
//为该元素添加属性
newElement.className = 'container';
//将元素添加到<body>文档中
document.body.appendChild(newElement);
14.1.4 Text类型
Text节点由Text类型表示,包含按字面解释的纯文本,也可能包含转义后的HTML字符,但不含HTML代码。Text节点中包含的文本可以通过nodeValue属性和datd属性访问。
操作方法与属性
- appendDate(text),向节点末尾添加文本text
- deleteData(offset, count),从位置offset处开始删除count个字符
- insertData(offset, text),从位置offset处插入text
- replaceData(offset, count, text),用text替换从位置offset到offset+count的文本。
- splitText(offset),在位置offset将当前文本拆成两个文本节点;
- substringData(offset, count),提取位置从offset到offset+count的文本
- length属性,获取文本节点中包含的字符数量。
创建文本节点
document.createTextNode()用于创建文本节点,接收一个参数,即要插入的文本。一般一个标签元素只包含一个文本节点,当包含多个文本节点时,两个文本节点的文本之间不会有空格。
// 创建和操作文本节点
const textNode = document.createTextNode('Hello World');
div.appendChild(textNode);
14.1.5 DocumentFragment类型
DocumentFragment(文档片段)类型是唯一一个在标记中没有对应表示的类型。文档片段定义为“轻量级”文档,能够包含和操作节点,却没有完整文档那样额外的消耗。可以作为其他要被添加到文档的节点的仓库。文档片段本身永远不会被添加到文档树中。
let fragment = document.createDocumentFragment();
let ul = document.getElementById("myList");
for(let i = 0; i<3; i++){let li = document.createElement("li");li.appendChild(document.createTextNode(`Item ${ i + 1 }`));fragment.appendChild(li);
}
ul.appendChild(fragment);
//通过将3个列表项添加到文档片段,最终再将文档片段添加到<ul>元素可以避免多次渲染,实现一次性渲染。
总结
现代DOM操作已经从传统的直接操作发展为更加高效、安全的方式:
-
查询选择:优先使用
querySelector和querySelectorAll -
元素创建:结合模板字符串和
insertAdjacentHTML -
属性操作:使用
classList和dataset等现代API -
性能优化:利用文档片段、事件委托和观察者模式
-
现代API:拥抱
MutationObserver、IntersectionObserver等新特性
