文档对象模型(DOM, Document Object Model)是HTML和XML文档的编程接口。DOM表示由多层节点构成的文档,通过它,开发者可以添加、删除和修改页面的各个部分。是真正的跨平台、语言无关的表示和操作网页的方式。
14.1 节点层级
DOM 树结构
DOM将HTML文档表示为树形结构,由不同类型的节点组成:
DOM示例
标题
段落文本
对应的DOM树:
文档节点 (Document)
├── 文档类型节点 (DocumentType)
├── 元素节点 (Element)
├── 元素节点 (Element)
│ └── 元素节点 (Element)
│ └── 文本节点 (Text) "DOM示例"
└── 元素节点 (Element) <body>
├── 元素节点 (Element) <h1>
│ └── 文本节点 (Text) "标题"
└── 元素节点 (Element) <p>
└── 文本节点 (Text) "段落文本"</code></pre>
<p id="u539b9984">文档元素时文档的最外层元素,每个文档只有一个文档元素。HTML页面中,文档元素始终是<html>元素,在XML中任何元素都有可能。</p><h4 id="KNOO9">14.1.1 Node类型</h4><p id="u0500fb82">所有DOM节点类型都继承自Node类型,因此所有类型都共享相同的基本属性和方法。</p><p id="u110ff697">每个节点都有nodeType属性,表示节点的类型,共有12种类型以12个数值表示:</p><ul><li id="u905f0043">Node.ELEMENT_NODE(1)</li><li id="u37d3d1de">Node.ATTRIBUTE_NODE(2)</li><li id="u071f7744">Node.TEXT_NODE(3)</li><li id="u0109a307">Node.CDATA_SECTION_NODE(4)</li><li id="ua27f7e16">Node.ENTITY_REFERENCE_NODE(5)</li><li id="u9af6f7bd">Node.ENTITY_NODE(6)</li><li id="u4fbdf531">Node.PROCESSING_INSTRUCTION_NODE(7)</li><li id="u151f2b4f">Node.COMMENT_NODE(8)</li><li id="u3e6b203b">Node.DOCUMENT_NODE(9)</li><li id="u3f6f359d">Node.DOCUMENT_TYPE_NODE(10)</li><li id="ub7019305">Node.DOCUMENT_FRAGMENT_NODE(11)</li><li id="uc78d0c03">Node.NOTARION_NODE(12)</li></ul><h5 id="Saykn">节点关系</h5><ol><li id="u611caa9e">parentNode 属性:获取目标节点的父元素节点</li><li id="u9f7524f5">childNodes属性:获取目标节点的子元素节点的类数组对象NodeList,其中包含的子节点元素都拥有共同的父节点,各子节点直接互为同胞节点。</li><li id="u749164d0">nextSibling属性:获取下一个同胞节点元素,不存在时为null。</li><li id="uf0f57dd9">previousSibling属性:获取上一个同胞节点元素,不存在时为null。</li><li id="ua37b1860">firstChild属性:获取第一个子节点</li><li id="uc7065070">lastChild属性:获取最后一个子节点</li></ol><h5 id="REEx0">操纵节点</h5><ol><li id="u2cc9242d">appendChild():在childNodes列表末尾添加节点,并返回新添加的节点</li><li id="u961533e0">insertBefore():接收两个参数:要插入的节点和参照节点,将新节点变成参照节点前的一个同胞节点并返回。</li><li id="u05949aad">replaceChild():接收两个参数:要插入的节点和要替换的节点,将旧节点替换。</li><li id="u6b72ba5b">removeChild():接收一个参数:要移除的节点,返回被移除的节点。</li><li id="u41c9c147">cloneNode():接收一个布尔值参数,true表示深复制目标节点,包含整个子DOM树;false则只复制调用该方法的节点。</li><li id="u31b0095f">normalize():处理文档子树中的文本节点。调用后会检查调用节点的所有后代,发现空文本节点则删除,发现相邻同胞节点则合并。</li></ol><h4 id="fkzUM">14.1.2 Document类型</h4><p id="u92c35225">Document类型是JavaScript中表示文档节点的类型。在浏览器中文档对象document是HTMLDocument的实例,表示整个HTML页面。document是window对象的属性,因此是一个全局对象。</p><h5 id="EVyq0">文档属性与信息</h5><ul><li id="u8e7ce678">document.documentElement:文档只有一个子节点,即<html>元素</li><li id="ub858730f">document.body:直接指向<body>元素</li><li id="u291b66d7">document.head:指向<head>元素</li><li id="u38af1e1e">document.title:获取文档标题,赋值修改无效</li><li id="uffca09cc">document.URL:获取包含当前页的完整URL,只读</li><li id="ue0ca9478">document.domain:获取域名,可修改。</li><li id="ue940ab53">document.referrer:获取链接到当前页面的上一个页面的完整URL,无来源则返回空串,只读</li></ul><p id="ubefcf312">document.domain详细示例如下:</p><p id="ud66eaee8">不能给该属性设置URL中不包含的值:</p>
<pre id="tfJAv" style="white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important;"><code class="language-javascript">//当页面来自于p2p.wrox.com时
document.domain = "wrox.com"; //成功
document.domain = "nczsdd.net"; //失败</code></pre>
<p id="u1f8ebff3">浏览器限制:该属性值一旦放松后就不能再收紧了:</p>
<pre id="l5cm9" style="white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important;"><code class="language-javascript">//当页面来自于p2p.wrox.com时
document.domain = "wrox.com"; //放松,成功
document.domain = "p2p.wrox.com"; //收紧,错误</code></pre>
<p id="u9b1024a5">可用于不同子域的跨域通信:当页面中包含来自某个不同子域的窗格(<frame>)或内嵌窗格(<iframe>)时,可以通过将每个页面的document.domain设置为同一个值,这些页面就可以访问对方的JavaScript对象了。</p><h5 id="prkbC">定位元素</h5><p id="u371410fc">传统方法:</p><ul><li id="u69e389c4">document.getElementById():接收一个参数,即元素的ID,如:'myId',获取id值匹配的第一个元素,否则返回null;</li><li id="u34251337">getElementsByClassName():接收一个参数,即元素的类名,如'myClass',获取类名匹配的元素返回一个HTMLCollecyion对象与NodeList类似,里面包含多个或零个元素。</li><li id="ud9adbb3a">getElementsByTagName():接收一个参数,即标签名,如'img',获取标签名匹配的元素集合HTMLCollecyion对象。</li><li id="uc231ed9a">getElementsByName():接收一个参数,即元素上定义的name属性值,返回一个HTMLCollecyion对象,包含具有该name属性值的所有元素。</li></ul><p id="uf11e58bd">现代方法:</p><ul><li id="uf276a18b">document.querySelector():接收一个参数,可以是类名也可以是id,如'#myId'或'.myClass',返回匹配的第一个元素。</li><li id="u8ff08c19">document.querySelectorAll():接收一个字符串参数,参数内可以包含多个匹配条件,以逗号隔开,如:'.myClass, .otherClass',返回一个NodeList对象,包含匹配的所有元素。</li></ul><p id="u08e09340">性能对比:querySelectorAll返回静态NodeList,getElementsByClassName返回动态HTMLCollection,推荐使用现代方法。</p><h4 id="m7bc2">14.1.3 Element类型</h4><p id="u7c2113fb">Element表示XML和HTML元素,对外暴露出访问元素标签名、子节点和属性的能力。可以通过nodeName或tagName属性来获取元素的标签名。但tagName属性返回标签名的始终是全大写表示,如“DIV”</p><h5 id="G1FyZ">HTML元素</h5><p id="u1728ce76">所有的HTML元素都通过HTMLElement类型表示,包括其直接实例或间接实例。且HTMLElement直接继承Element并添加了一些属性如:id、title、lang、dir、className。以上这几个属性是所有HTML元素的标准属性,且通过属性进行读写,修改标签的属性。</p><p id="ued04fa6c">常用的HTML元素如下:</p><p id="u78c16998">H1~H6、A、EM、FROM、FRAME、HEAD、LI、P、TABLE......</p><p id="u26dbeb2c"></p><h5 id="pL9Dm">获取属性</h5><p id="u4a168f7c">getAttribute()方法,接收一个属性名,获取属性值,属性名不区分大小写。支持自定义属性的获取,根据HTML5规范要求,自定义属性名应该加上前缀data-方便验证。但是如果访问事件处理程序(事件属性)如onclick则返回字符串形式的源代码。</p>
<pre id="yq1kS" style="white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important;"><code class="language-javascript">const div = document.querySelector('div');
const value = div.getAttribute('data-custom');//获取自定义属性data-custom的值</code></pre>
<h5 id="D2rhW">设置属性</h5><p id="u9fd40fad">setAttribute()方法接收两个参数:要设置的属性名和属性值。若存在同名属性则值更新否则创建该属性。注意,该方法接收的属性名用小写形式;</p>
<pre id="S8Zrl" style="white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important;"><code class="language-javascript">div.setAttribute('data-custom', 'value');</code></pre>
<h5 id="R9evf">移除属性</h5><p id="u58b06c4d">removeAttribute()方法接收一个属性名,将该属性从调用的元素上移除。</p>
<pre id="MZsWE" style="white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important;"><code class="language-javascript">div.removeAttribute('data-custom');</code></pre>
<h5 id="LAoJZ">attributes属性</h5><p id="u08735b9d">Element类型是唯一使用attributes属性的DOM节点类型。属性包含下列方法:</p><ul><li id="ueed6b526">getNamedItem(name),返回nodeName属性等于name的节点</li><li id="u906ef7c3">removeNamedItem(name),删除nodeName属性等于name的节点</li><li id="uc2a8e549">setNamedItem(node),向属性列表中添加node节点,以其nodeName为索引</li><li id="u6cd0ff2c">item(pos),返回索引位置pos处的节点</li></ul><p id="u7da2fefe">attributes属性中每个节点的nodeName是对应属性的名字,nodeValue是属性的值。</p>
<pre id="ZoOks" style="white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important;"><code class="language-javascript">//获取元素的id属性值
let id = element.attributes.getNamedItem("id").nodeValue;
//设置id值
element.attributes["id"].nodeValue = "someOtherId";
//添加新属性
element.attributes.setNamedItem(newAttr);
//删除属性并返回被删除的节点
let oldAttr = element.attributes.removeNamedItem("id");</code></pre>
<h5 id="dAi30">创建元素</h5><p id="uf8031eb4">使用document.createElement()方法创建新元素。该方法接收一个参数,即要创建的元素标签名。</p>
<pre id="ZvYQT" style="white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important;"><code class="language-javascript">//创建一个div元素
const newElement = document.createElement('div');
//为该元素添加属性
newElement.className = 'container';
//将元素添加到<body>文档中
document.body.appendChild(newElement);</code></pre>
<h4 id="zrrYB">14.1.4 Text类型</h4><p id="u5f425a18">Text节点由Text类型表示,包含按字面解释的纯文本,也可能包含转义后的HTML字符,但不含HTML代码。Text节点中包含的文本可以通过nodeValue属性和datd属性访问。</p><h5 id="cHQlj">操作方法与属性</h5><ul><li id="u82d137db">appendDate(text),向节点末尾添加文本text</li><li id="u009f9840">deleteData(offset, count),从位置offset处开始删除count个字符</li><li id="uef0179ea">insertData(offset, text),从位置offset处插入text</li><li id="uc874a2ef">replaceData(offset, count, text),用text替换从位置offset到offset+count的文本。</li><li id="u58028e74">splitText(offset),在位置offset将当前文本拆成两个文本节点;</li><li id="u507c66c9">substringData(offset, count),提取位置从offset到offset+count的文本</li><li id="ua4940d75">length属性,获取文本节点中包含的字符数量。</li></ul><p id="u20c4f71a"></p><h5 id="OYnvN">创建文本节点</h5><p id="u4007b4b6">document.createTextNode()用于创建文本节点,接收一个参数,即要插入的文本。一般一个标签元素只包含一个文本节点,当包含多个文本节点时,两个文本节点的文本之间不会有空格。</p>
<pre id="jXWjB" style="white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important;"><code class="language-javascript">// 创建和操作文本节点
const textNode = document.createTextNode('Hello World');
div.appendChild(textNode);</code></pre>
<p id="u0f716417"></p><h4 id="T0Qfk">14.1.5 DocumentFragment类型</h4><p id="u7756ad1b">DocumentFragment(文档片段)类型是唯一一个在标记中没有对应表示的类型。文档片段定义为“轻量级”文档,能够包含和操作节点,却没有完整文档那样额外的消耗。可以作为其他要被添加到文档的节点的仓库。文档片段本身永远不会被添加到文档树中。</p>
<pre id="Xnq2h" style="white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important;"><code class="language-javascript">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>元素可以避免多次渲染,实现一次性渲染。</code></pre>
<h3>总结</h3><p>现代DOM操作已经从传统的直接操作发展为更加高效、安全的方式:</p><ol><li><p><strong>查询选择</strong>:优先使用<code>querySelector</code>和<code>querySelectorAll</code></p></li><li><p><strong>元素创建</strong>:结合模板字符串和<code>insertAdjacentHTML</code></p></li><li><p><strong>属性操作</strong>:使用<code>classList</code>和<code>dataset</code>等现代API</p></li><li><p><strong>性能优化</strong>:利用文档片段、事件委托和观察者模式</p></li><li><p><strong>现代API</strong>:拥抱<code>MutationObserver</code>、<code>IntersectionObserver</code>等新特性</p></li></ol></div>
浙公网安备 33010602011771号