DOM(文档对象模型)

Posted 劳埃德·福杰

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了DOM(文档对象模型)相关的知识,希望对你有一定的参考价值。

目录

1.简介

DOM(文档对象模型)提供了访问和操作网页内容的方法和接口。

DOM1级规范: 由DOM Core和DOM html两个模块组成。DOM Core规定如何映射XML的文档结构,以便简化对文档中任意部分的操作。DOM HTML在DOM Core上加以扩展,添加了针对HTML的对象和方法。

2.DOM1

①节点关系

DOM将任何HTML或XML文档描绘成一个由多层节点构成的结构。

JS中的所有节点类型都继承自Node类型,所以所有节点都有一些共同的属性和方法
每个节点都有一个nodeType属性,用于表明节点的类型,共有12种。
我们也可以使用nodeName和nodeValue属性查看节点信息。

(1)每个节点都有一个childNodes属性,其中保存一个NodeList对象,NodeList是一种类数组对象,保存了一组有序的节点,可通过位置下标来访问,也有length属性,但它不是Array的实例,可使用中括号或使用 item() 方法访问 NodeList 中的元素。
查询一个节点是否有子节点:利用hasChildNodes()方法或length属性

let firstChild = someNode.childNodes[0];
let secondChild = someNode.childNodes.item(1);

(2)每个节点都有一个 parentNode 属性,指向其 DOM 树中的父元素。 
childNodes 列表中的每个节点都是同一列表中其他节点的同胞节点,使用 previousSiblingnextSibling 属性可以访问同一列表中的其它节点。 
父节点的第一个及最后一个子节点有专门属性: firstChild 和 lastChild

(3)共有属性ownerDocument是一个指向代表整个文档的文档节点的指针。 

②操作节点

(1)appendChild() :用于在 childNodes 列表末尾添加节点。如果添加的是文档中已经存在的节点,那么之前位置的那个节点会被移除。
(2)insertBefore() :把节点放到 childNodes 中的特定位置,该方法接收两个参数:要插入的节点和参照节点。
(3)replaceChild() :替换节点。该方法接收两个参数:要插入的节点和要替换的节点。(4)removeChild() :移除节点。该这个方法接收一个参数,即要移除的节点。
(5)cloneNode() :返回与调用它的节点一模一样的节点。 该方法接收一个布尔值参数,表示是否深复制。传入true参数,会进行深复制,即复制节点及其整个子 DOM树。传入false ,则只复制调用该方法的节点。
(6)normalize():处理文档子树中的文本节点。检测调用节点的所有后代,如果发现空文本节点,将其删除;如果两个同胞文本节点是相邻的,则将其合并为一个文本节点。

③Document 类型

浏览器中,文档对象 document 是HTMLDocument 的实例,表示整个 HTML页面。 document 是 window对象的一个属性,因此是一个全局对象

document.documentElement: 指向<html> 元素,也可以通过 childNodes 列表访问
document.body:指向 <body> 元素
document.doctype 属性: 指向一个DocumentType 对象,包含文档的文档类型信息, 就是<!DOCTYPE>标签里面的一些东西,也可以通过 childNodes 列表访问
理论上,出现在<html>标签外部的注释应该算文档的子节点,然而不同浏览器在是否解析这些注释方面存在很大差异。

document 作为 HTMLDocument 的实例,还有一些标准 Document 对象上所没有的属性:

document.title:读取文档标题,可以修改 title 属性但并不会改变 <title> 元素
document.URL:取得完整的 URL
document.domain:取得域名
document.referrer:取得来源,没有来源就是空字符串
所有这些信息都可以在请求的 HTTP 头部信息中获取

④查找元素

document.getElementById()、document.getElementsById()
document.getElementsByTagName() :
返回一个HTMLCollection对象,可通过中括号或 item() 方法从中取得特定的元素;该对象还有一个方法namedItem() ,可通过标签的 name 属性取得集合中的项;HTMLCollection对象本身也支持通过["name属性名"]的方式获取列表项。
获取页面中的所有元素:document.getElementsByTagName("*")
getElementsByName():返回具有给定 name 属性的所有元素,也返回一个HTMLCollection对象,最常用于单选框所有单选框id不同,但是name相同,从而保证了只将一个值发送给服务器(name:xxx)

⑤创建元素

 document.createElement()

let div = document.createElement("div");
div.id = "myNewDiv";
div.className = "box";
document.body.appendChild(div);

⑥文档写入

document.write()、document.writeln():向网页输出流中写入内容,后者比前者多个换行符

# 在页面加载期间向页面中动态添加内容
document.write("<strong>" + (new Date()).toString() + "</strong>");
# 动态包含外部资源,"<\\/script>" 是为了防止输出的</script>匹配最外层的<script>标签
document.write("<script type=\\"text/javascript\\" src=\\"file.js\\">" + "<\\/script>");

document.open() 、document.close() :分别用于打开和关闭网页输出流

⑦Element 类型

nodeType 等于 1
获取元素的标签名:nodeName 或 tagName 属性
在 HTML 中,元素标签名始终以大写表示(比如 xxx.tagName返回的是“DIV”);在 XML(包括 XHTML)中,标签名始终与源代码中的大小写一致。(适用不同文档写法:element.tagName.toLowerCase() == "div")
HTML元素中的属性:idtitle (包含元素的额外信息,通常以提示条形式展示)、lang (元素内容的语言代码)、dir (语言的书写方向,"ltr" :从左到右, "rtl" :从右到左)、className

Text 类型

nodeType 等于 3
Text 节点中包含的文本可以通过 nodeValue 属性访问,也可以通过 data 属性访问,这两个属性
包含相同的值。
创建文本节点

let element = document.createElement("div");
element.className = "message";
let textNode = document.createTextNode("Hello world!");
element.appendChild(textNode);
document.body.appendChild(element);

默认情况下,包含文本内容的每个元素最多只能有一个文本节点,不过,也可以让元素包含多个文本子节点。就是想上面这样appendChild两个文本节点即可。

normalize():所有同胞文本节点会被合并为一个文本节点

let element = document.createElement("div");
element.className = "message";
let textNode = document.createTextNode("Hello world!");
element.appendChild(textNode);
let anotherTextNode = document.createTextNode("Yippee!");
element.appendChild(anotherTextNode);
document.body.appendChild(element);

alert(element.childNodes.length); // 2
element.normalize();
alert(element.childNodes.length); // 1
alert(element.firstChild.nodeValue); // "Hello world!Yippee!"

splitText():将一个文本节点拆分成两个文本节点

let element = document.createElement("div");
element.className = "message";
let textNode = document.createTextNode("Hello world!");
element.appendChild(textNode);
document.body.appendChild(element);
let newNode = element.firstChild.splitText(5); // 从位置 5 拆分成两个文本节点
alert(element.firstChild.nodeValue); // "Hello"
alert(newNode.nodeValue); // " world!"
alert(element.childNodes.length); // 2

⑨Comment 类型 

nodeType 等于 8

<div id="myDiv"><!-- A comment --></div> 

//注释节点可以通过父节点来访问
let div = document.getElementById("myDiv");
let comment = div.firstChild;
alert(comment.data); // "A comment"

// 可以使用 document.createComment() 方法创建注释节点,参数为注释文本
let comment = document.createComment("A comment");

⑩DocumentFragment 类型 

nodeType 等于 11
充当其他要被添加到文档的节点的仓库,文档片段本身永远不会被添加到文档树

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);

⑪Attr 类型

nodeType 等于 2
Attr 对象上有 3 个属性:
name 、 value 和 specified 。
其中, name 包含属性名, value 包含属性值,而 specified 是一个布尔值,表示属性使用的是默认值还是被指定的值。
创建新的 Attr:document.createAttribute() 

let attr = document.createAttribute("align");
attr.value = "left";
element.setAttributeNode(attr);
alert(element.attributes["align"].value); // "left"
alert(element.getAttributeNode("align").value); // "left"
alert(element.getAttribute("align")); // "left"

操作属性

getAttribute() :拿到属性值,比如xxx.getAttribute("class"),调用对象属性用className,这里用class。也能拿到自己定义的属性,但是除了IE,自定义属性是不会被添加到DOM对象中的。
setAttribute() :接收要设置的属性名和属性的值两个参数。有目标属性就覆盖其值,没有就创建。
removeAttribute() :用于从元素中删除属性

attributes属性

获取元素的id特性

// nodeName获取特性的名称,nodeValue获取特性的值
var id = element.attributes.getNamedItem("id").nodeValue;
var id = element.attributes["id"].nodeValue;

3.动态脚本

// 引入外部文件
// DOM编程创建<script src="foo.js"></script>这个节点
function loadScript(url) 
    let script = document.createElement("script");
    script.src = url;
    document.body.appendChild(script);


loadScript("client.js");

//直接插入源代码
//<script>
//function sayHi() 
//    alert("hi");
//</script>
// 使用 DOM 实现
let script = document.createElement("script");
script.appendChild(document.createTextNode("function sayHi()alert('hi');"));
document.body.appendChild(script);

4.动态样式

// 用<link>元素来引入CSS外部文件
// DOM 编程创建 <link rel="stylesheet" type="text/css" href="styles.css">
function loadStyles(url)
    let link = document.createElement("link");
    link.rel = "stylesheet";
    link.type = "text/css";
    link.href = url;
    let head = document.getElementsByTagName("head")[0];
    head.appendChild(link);

loadStyles("styles.css");

// 利用<style>元素添加嵌入样式,DOM编程实现
let style = document.createElement("style");
style.type = "text/css";
style.appendChild(document.createTextNode("bodybackground-color:red"));
let head = document.getElementsByTagName("head")[0];
head.appendChild(style);

5.操作表格

<table border="1" width="100%">
    <tbody>
        <tr>
            <td>Cell 1,1</td>
            <td>Cell 2,1</td>
        </tr>
        <tr>
            <td>Cell 1,2</td>
            <td>Cell 2,2</td>
        </tr>
    </tbody>
</table>

通过 DOM 来创建上面的表格

// 创建表格
let table = document.createElement("table");
table.border = 1;
table.width = "100%";
// 创建表体
let tbody = document.createElement("tbody");
table.appendChild(tbody);
// 创建第一行
let row1 = document.createElement("tr");
tbody.appendChild(row1);
let cell1_1 = document.createElement("td");
cell1_1.appendChild(document.createTextNode("Cell 1,1"));
row1.appendChild(cell1_1);
let cell2_1 = document.createElement("td");
cell2_1.appendChild(document.createTextNode("Cell 2,1"));
row1.appendChild(cell2_1);
// 创建第二行
let row2 = document.createElement("tr");
tbody.appendChild(row2);
let cell1_2 = document.createElement("td");
cell1_2.appendChild(document.createTextNode("Cell 1,2"));
row2.appendChild(cell1_2);
let cell2_2= document.createElement("td");
cell2_2.appendChild(document.createTextNode("Cell 2,2"));
row2.appendChild(cell2_2);
// 把表格添加到文档主体
document.body.appendChild(table);

利用<tbody>和<tr>的属性和方法

// 创建表格
let table = document.createElement("table");
table.border = 1;
table.width = "100%";
// 创建表体
let tbody = document.createElement("tbody");
table.appendChild(tbody);
// 创建第一行
tbody.insertRow(0);
tbody.rows[0].insertCell(0);
tbody.rows[0].cells[0].appendChild(document.createTextNode("Cell 1,1"));
tbody.rows[0].insertCell(1);
tbody.rows[0].cells[1].appendChild(document.createTextNode("Cell 2,1"));
// 创建第二行
tbody.insertRow(1);
tbody.rows[1].insertCell(0);
tbody.rows[1].cells[0].appendChild(document.createTextNode("Cell 1,2"));
tbody.rows[1].insertCell(1);
tbody.rows[1].cells[1].appendChild(document.createTextNode("Cell 2,2"));
// 把表格添加到文档主体
document.body.appendChild(table);

6.DOM2和DOM3

DOM2主要是针对操作元素的样式信息而开发的

①访问元素的样式

DOM1(DOM Level 1)主要定义了 HTML 和 XML 文档的底层结构。DOM2(DOM Level 2)和
DOM3(DOM Level 3)在这些结构之上加入更多交互能力,提供了更高级的 XML 特性。

访问元素的样式

let myDiv = document.getElementById("myDiv");
// 设置背景颜色
myDiv.style.backgroundColor = "red";
// 修改大小
myDiv.style.width = "100px";
myDiv.style.height = "200px";
// 设置边框
myDiv.style.border = "1px solid black";

通过 cssText 属性可以存取样式的 CSS 代码

myDiv.style.cssText = "width: 25px; height: 100px; background-color: green";
console.log(myDiv.style.cssText);

length 属性是跟 item() 方法一起配套迭代 CSS 属性用的

let prop, value, i, len;
for (i = 0, len = myDiv.style.length; i < len; i++) 
    prop = myDiv.style[i]; // 或者用 myDiv.style.item(i)
    value = myDiv.style.getPropertyValue(prop); // getPropertyValue() 可以取得属性的值
    console.log(`prop: $value`);

removeProperty() :用于从元素样式中删除指定的 CSS 属性

myDiv.style.removeProperty("border");

②操作样式表

document.styleSheets :文档中可用的样式表集合

let sheet = null;
for (let i = 0, len = document.styleSheets.length; i < len; i++) 
    sheet = document.styleSheets[i];
    console.log(sheet.href);

CSSStyleRule对象表示样式表中的一条规则

假设有下面这个 CSS 规则:

div.box 
    background-color: blue;
    width: 100px;
    height: 200px;
let sheet = document.styleSheets[0];
let rules = sheet.cssRules || sheet.rules; // 取得规则集合
let rule = rules[0]; // 取得第一条规则

console.log(rule.selectorText); // "div.box"
console.log(rule.style.cssText); // 完整的 CSS 代码
console.log(rule.style.backgroundColor); // "blue"
console.log(rule.style.width); // "100px"
console.log(rule.style.height); // "200px"

// 修改规则中的样式
rule.style.backgroundColor = "red"

③创建规则

使用 insertRule() 方法向样式表中添加新规则。这个方法接收两个参数:规则的文本和表示插入位置的索引值。

sheet.insertRule("body  background-color: silver ", 0); // 使用 DOM 方法

④删除规则

 deleteRule(),它接收一个参数:要删除规则的索引

sheet.deleteRule(0); // 删除样式表中的第一条规则

⑤遍历 DOM 结构

NodeIterator
接收以下 4 个参数。
 root ,作为遍历根节点的节点。
 whatToShow ,数值代码,表示应该访问哪些节点。
 filter , NodeFilter 对象或函数,表示是否接收或跳过特定节点。
 entityReferenceExpansion ,布尔值,表示是否扩展实体引用。这个参数在 HTML 文档中没
有效果,因为实体引用永远不扩展。

遍历所有节点的 NodeIterator

let iterator = document.createNodeIterator(document, NodeFilter.SHOW_ALL, null, false);

两个主要方法: nextNode() 和 previousNode() ,前者以深度优先方式进前一步,而后者是在遍历中后退一步

<div id="div1">
    <p><b>Hello</b> world!</p>
    <ul>
        <li>List item 1</li>
        <li>List item 2</li>
        <li>List item 3</li>
    </ul>
</div>
// 想要遍历 <div> 元素内部的所有元素
let div = document.getElementById("div1");
let iterator = document.createNodeIterator(div, NodeFilter.SHOW_ELEMENT, null, false);
let node = iterator.nextNode(); // 第一次调用 nextNode() 返回的是根节点,也就是div元素
while (node !== null) 
    console.log(node.tagName); // 输出标签名
    node = iterator.nextNode();


//如果只想遍历 <li> 元素,可以传入一个过滤器
let div = document.getElementById("div1");
let filter = function(node) 
    return node.tagName.toLowerCase() == "li" ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
;
let iterator = document.createNodeIterator(div, NodeFilter.SHOW_ELEMENT, filter, false);
let node = iterator.nextNode();
while (node !== null) 
    console.log(node.tagName); // 输出标签名
    node = iterator.nextNode();

TreeWalker

TreeWalker 是 NodeIterator 的高级版。除了包含同样的 nextNode() 、 previousNode() 方法,
TreeWalker 还添加了如下方法:
 parentNode() ,遍历到当前节点的父节点
 firstChild() ,遍历到当前节点的第一个子节点
 lastChild() ,遍历到当前节点的最后一个子节点
 nextSibling() ,遍历到当前节点的下一个同胞节点
 previousSibling() ,遍历到当前节点的上一个同胞节点

let div = document.getElementById("div1");
let filter = function(node) 
    return node.tagName.toLowerCase() == "li" ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
;
let walker = document.createTreeWalker(div, NodeFilter.SHOW_ELEMENT, filter, false);
let node = iterator.nextNode();
while (node !== null) 
    console.log(node.tagName); // 输出标签名
    node = iterator.nextNode();

不同的是,节点过滤器( filter )除了可以返回 NodeFilter.FILTER_ACCEPT 和 NodeFilter.
FILTER_SKIP ,还可以返回 NodeFilter.FILTER_REJECT 。
NodeFilter.FILTER_SKIP 表示跳过节点,访问子树中的下一个节点,而NodeFilter.FILTER_REJECT 则表示跳过该节点以及该节点的整个子树。 

 TreeWalker 真正的威力是可以在 DOM 结构中四处游走 

let div = document.getElementById("div1");
let walker = document.createTreeWalker(div, NodeFilter.SHOW_ELEMENT, null, false);
walker.firstChild(); // 前往<p>
walker.nextSibling(); // 前往<ul>
let node = walker.firstChild(); // 前往第一个<li>
while (node !== null) 
    console.log(node.tagName);
    node = walker.nextSibling();

currentNode属性:表示遍历过程中上一次返回的节点。可以通过修改这个属性来影响接下来遍历的起点

let node = walker.nextNode();
console.log(node === walker.currentNode); // true
walker.currentNode = document.body; // 修改起点 

⑥DOM范围

为了支持对页面更细致的控制,DOM2 Traversal and Range 模块定义了范围接口。
范围可用于在文档中选择内容,而不用考虑节点之间的界限。

创建:let range = document.createRange();

<!DOCTYPE html>
<html>
    <body>
        <p id="p1"><b>Hello</b> world!</p>
    </body>
</html>

简单选择: selectNode() 或 selectNodeContents()
前者选择整个节点,包括其后代节点,后者只选择节点的后代

let range1 = document.createRange(), range2 = document.createRange(),
p1 = document.getElementById("p1");
range1.selectNode(p1);
range2.selectNodeContents(p1);

 

复杂选择: setStart() 和 setEnd(),接收两个参数:参照节点和偏移量

// 选择从 "Hello" 中的 "llo" 到 " world!" 中的 "o" 的部分
let p1 = document.getElementById("p1"), helloNode = p1.firstChild.firstChild, worldNode = p1.lastChild;
let range = document.createRange();
range.setStart(helloNode, 2);
range.setEnd(worldNode, 3);

range.deleteContents(); // 从文档中删除范围包含的节点

// extractContents() 跟 deleteContents() 类似,也会从文档中移除范围选区。但不同的是, extractContents() 方法返回范围对应的文档片段
let fragment = range.extractContents();
p1.parentNode.appendChild(fragment);

// cloneContents() 创建一个副本
let fragment = range.cloneContents();
p1.parentNode.appendChild(fragment);

 向范围中插入内容

// 在上面的例子中
let p1 = document.getElementById("p1"), helloNode = p1.firstChild.firstChild, worldNode = p1.lastChild, range = document.createRange();
range.setStart(helloNode, 2);
range.setEnd(worldNode, 3);
let span = document.createElement("span");
span.style.color = "red";
span.appendChild(document.createTextNode("Inserted text"));
range.insertNode(span);

效果

<p id="p1"><b>He<span style="color: red">Inserted text</span>llo</b> world</p>

插入包含范围的内容: surroundContents()

let p1 = document.getElementById("p1"), helloNode = p1.firstChild.firstChild, worldNode = p1.lastChild, range = document.createRange();
range.selectNode(helloNode);
let span = document.createElement("span");
span.style.backgroundColor = "yellow";
range.surroundContents(span);

效果

<p><b><span style="background-color:yellow">Hello</span></b> world!</p>

复制范围: cloneRange() 

let newRange = range.cloneRange();

清理范围:detach() 

range.detach(); // 从文档中剥离范围
range = null; // 解除引用

以上是关于DOM(文档对象模型)的主要内容,如果未能解决你的问题,请参考以下文章

使用jQuery对象:DOM属性CSS尺寸遍历DOM操作事件特效

DOM 文档对象模型

XML的解析方式有哪几种?有什么区别?

JavaScript之DOM文档对象模型

JavaScript(DOM文档对象模型)

JS第二部分--DOM文档对象模型