文章目录
DOM
DOM(Document Object Model)文档对象模型
1.节点层次
1.1 Node类型
JavaScript中的所有节点类型都继承自Node类型。
节点类型由Node类型中定义的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 | Document对象 |
Node.DOCUMENT_TYPE_NODE | 10 | |
Node.DOCUMENT_FRAGMENT_NODE | 11 | |
Node.NOTATION_NODE | 12 |
每个节点类型都有一个nodeType
属性,保存以上常量值
console.log(Node.ELEMENT_NODE); //1
console.log(Node.ATTRIBUTE_NODE); //2
console.log(Node.ELEMENT_NODE == 1); //true
var htmlNode = document.documentElement; //获取html节点
//每个节点都有nodeType属性
console.log(htmlNode.nodeType == Node.ELEMENT_NODE); //true IE无效
console.log(htmlNode.nodeType == 1); //true 所有浏览器都可以
nodeName和nodeValue属性
nodeName
和nodeValue
的值完全取决于节点的类型。
节点类型 | nodeName | nodeValue |
---|---|---|
Node.ELEMENT_NODE | 标签名 | null |
Node.DOCUMENT_NODE | #document | null |
var htmlNode = document.documentElement; //获取html节点
//htmlNode的节点类型是Node.ELEMENT_NODE
console.log(htmlNode.nodeName); //HTML
console.log(htmlNode.nodeValue); //null
节点关系
每个节点都有childNodes
属性,实质是一个NodeList
对象(动态数组),可以通过方括号[]
或者item(index)
访问,该NodeList
对象也有length
属性。
var htmlNode = document.documentElement; //获取html节点
console.log(htmlNode.childNodes.length); //3 子节点个数,如果</head>与<body>没有换行和空格,子节点数为2
var firstChild = htmlNode.childNodes[0]; //获取第一个节点
var secondChild = htmlNode.childNodes.item(1); //获取第二个节点
console.log(firstChild.nodeType); //1 ELEMENT_NODE
console.log(secondChild.nodeType); //3 TEXT_NODE
console.log(htmlNode.childNodes.item(htmlNode.childNodes.length - 1).nodeType); //1 ELEMENT_NODE
其他常用属性:
parentNode
父节点, 根节点的父节点为nullpreviousSibling
同级中的前一个节点, 第一个节点的previousSibling为nullnextSibling
同级中的下一个节点, 最后一个节点的nextSibling为nullfistChild
,someNode.firstChild
与someNode.childNodes[0]
等价lastChild
,someNode.lastChild
与someNode.childNodes[htmlNode.childNodes.length-1]
等价ownerDocument
, 表示整个文档的文档节点
console.log(document.nodeType);//9 document为文档节点类型
console.log(htmlNode.ownerDocument == document); //true
一个常用方法:
hasChildNodes()
console.log(htmlNode.hasChildNodes()); //true
操作节点
常用的操作节点的方法有:
appendChild()
返回新增的节点insertBefore()
返回插入的节点replaceChild()
替换节点removeChild()
移除节点, 返回被移除的节点
//appendChild()
var returnedNode = someNode.appendChild(newNode);
alert(returnedNode == newNode); //true
alert(someNode.lastChild == newNode); //true
//如果上面的newNode是文档的一部分,该节点会从原位置转移到新位置,原位置的节点消失
//someNode有多个子节点,appendChild将添加到最后一个节点
var returnedNode = someNode.appendChild(someNode.firstChild);
alert(returnedNode == someNode.firstChild); //false
alert(returnedNode == someNode.lastChild); //true
//insertBefore()
//插入后成为最后一个子节点
returnedNode = someNode.insertBefore(newNode, null);
alert(newNode == someNode.lastChild); //true
//插入后成为第一个子节点
returnedNode = someNode.insertBefore(newNode, someNode.firstChild);
alert(returnedNode == newNode); //true
alert(newNode == someNode.firstChild); //true
//插入到最后一个子节点前面
returnedNode = someNode.insertBefore(newNode, someNode.lastChild);
alert(newNode == someNode.childNodes[someNode.childNodes.length-2]);
//replaceChild()
//替换第一个子节点
returnedNode = someNode.replaceChild(newNode, someNode.firstChild);
//替换最后一个子节点
returnedNode = someNode.replaceChild(newNode, someNode.lastChild);
//removeChild()
//移除第一个子节点
var returnedNode = someNode.removeChild(someNode.firstChild);
//移除最后一个子节点
var returnedNode = someNode.removeChild(someNode.lastChild);
其他节点操作方法:
cloneNode(boolean)
创建节点的副本。如果boolean为true,则执行深复制,复制节点和子节点树;若为false,则浅复制,只复制节点本身。normalize()
处理文档树中的文本节点
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>CloneNode</title>
</head>
<body>
<ul id="max">
<li>item 1</li>
<li>item 1</li>
<li>item 1</li>
</ul>
<ul id="min"><li>item 1</li><li>item 1</li> <li>item 1</li>
</ul>
<script>
var maxList = document.getElementById("max");
var deepMaxList = maxList.cloneNode(true); //深复制
console.log(deepMaxList.childNodes.length); //7,其中三个元素节点,四个文本节点,文本节点是换行符导致的。
var minList = document.getElementById("min");
var deepMinList = minList.cloneNode(false); //浅复制
console.log(deepMinList.childNodes.length); //0
</script>
</body>
</html>
1.2 Document类型
- JavaScript通过Document类型表示文档
- document对象是HTMLDocument的实例,HTMLDocument继承自Document类型
- document对象是window对象的一个属性,可以全局访问。
文档的子节点
(1)<html>
获取<html>
元素节点有两种方式:
- document对象的documentElement属性,
document.documentElement
- 通过对document对象的childNodes访问,
document.childNodes[]
对于如下html文件
<html>
<body></body>
</html>
获取<html>
的方式:
var html = document.documentElement;
alert(html === document.childNode[0]); //true
alert(html === document.firstChild); //true
(2)<body>
//直接利用属性body
var body = document.body;
//也可以通过childNodes遍历访问
(3)<!DOCTYPE>
该节点类型为DocumentType, 有doctype属性访问,不过很少用,因为各个浏览器的支持不同。
var doctype = document.doctype;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document 类型</title>
</head>
<body>
<script>
var html = document.documentElement; //documentElement属性专指<html>
console.log(document.childNodes.length); //2
console.log(html === document.childNodes[1]); // true 第二个元素
console.log(html === document.childNodes.item(1)); //true
console.log(html === document.firstNode); //false
var body = document.body; //专指<body>
console.log(body);
console.log(body === html.childNodes[2]); //true
console.log(html.firstChild);//<head>
console.log(html.childNodes[1]);// #text
console.log(html.childNodes[2]);//<body>
var doctype = document.doctype;
console.log(doctype); // <!DOCTYPE html>
console.log(document.firstNode); //undefined
</script>
</body>
</html>
文档信息
//获取文档标题
var title = document.title;
//设置文档标题
document.title = "New Title";
//获取地址栏显示的URL
var url = document.URL;
//获取域名
var domain = document.domain;
//取得来源页面的URL
var referrer = document.referrer;
查找元素
- getElementById(), 根据id属性值获取元素
- getElementsByTagName(),根据标签名获取多个元素,保存在一个NodeList中。在HTML文档中,这个方法返回HTMLCollection对象,与NodeList很类似。
- getElementsByName(), 根据元素的name属性获得元素集合,方法返回HTMLCollection对象
//getElementsByTagName()
var images = document.getElementsByTagName('img'); //返回所有的图片
alert(images.length); //图片的数量
alert(images[0].src); //第一个图片的src属性
alert(images.item(0).src) //第一个图片的src属性
// HTMLCollection的namedItem()方法,可以通过元素的name属性获得images集合中的项
<img src="myimage.gif" name='myImage'>
var myImage = images.namedItem('myImages');
//也可以采用[]访问
var myImage = images['myImages'];
//获取文档中的所有元素,传入'*'
var allElement = document.getElementByTagName('*');
特殊集合
document对象有一些特殊的集合,都是HTMLCollection对象。
- document.anchors, 包含所有带name特性的元素
- document.forms, 包含所有的
<form>
元素,与document.getElementByTagName("form")
结果相同 - document.images, 包含所有
<img>
元素,与document.getElementByTagName('img')
结果相同
文档写入
- write(),原样写入
- writeln(),原样写入,结尾加换行符
- open(),打开输出流
- close(),关闭输出流
<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<title>document.write() demo</title>
</head>
<body>
<p>The current date is:
<script>
document.write('<strong>' + (new Date()).toString() + '</strong>');
</script>
</p>
</body>
</html>
在动态写入<script type="text/javascript"></script>
需要注意为标签</script>
需要写为<\/script>
document.write("<script type=\"text/javascript\" src=\"file.js\">" + "<\/script>")";
注意: 如果在文档加载结束后再调用document.write()
,那么输出的内容将会重写整个页面。
1.3 Element类型
Element类型用于表现XML和HTML元素,提供标签名,子节点, 属性的访问。
访问元素的标签名,可以使用nodeName属性和tagName属性。
var mydiv = document.getElementById("myDiv");
console.log(mydiv.tagName); //标签名 DIV
console.log(mydiv.nodeName); //也是标签名 DIV
//默认获取到的标签名都为大写
console.log(mydiv.tagName == "div"); //false
console.log(mydiv.tagName.toLowerCase() == "div"); //true
HTML元素
每个HTML元素存在的5种标准特性(注意是特性):
- id, 唯一标识符
- title, 附加说明信息,一般通过工具提示条显示出来
- lang, 元素内容的语言代码,很少使用
- dir, 语言的方向,有’ltr’和’rtl’,很少使用
- className,与元素的class特性对应,为元素指定的CSS类
//下面的div
<div id="myDiv" class="bd" title="Body Text" lang="en" dir="ltr"></div>
var div = document.getElementById("myDiv");
//获取五大特性
console.log(div.id); //myDiv
console.log(div.className); //bd
console.log(div.title); //Body Text
console.log(div.lang); //en
console.log(div.dir); //ltr
//设置五大特性
div.id = "youDiv";
console.log(div.id); //youDiv
操作特性
下面三个操作,可以针对任何特性,包括自定义的特性
getAttribute('attr')
setAttribute("attr", "attr-value")
removeAttribute("attr")
注意:一般情况下获取元素特性采用对象的属性,比如div.id
,而不用div.getAttribute('id')
attributes 属性
attributes 属性的类型是 Node.ATTRIBUTE_NODE,包含一个NamedNodeMap,这是个动态集合。NamedNodeMap对象有下列方法:
- getNamedItem(name); 返回nodeName属性等于name的节点
- removeNamedItem(name); 移除nodeName属性等于Name的节点
- setNamedItem(node); 向列表中添加节点
- item(pos); 返回数字pos位置处的节点
//获取元素的id特性
var id = element.attributes.getNamedItem("id").nodeValue;
var id = element.attributes['id'].nodeValue;
//给id设置新值
element.attributes['id'].nodeValue = 'someOtherId';
//删除属性,与removeAttribute()的效果形同
var oldAttr = element.attributes.removeNamedItem('id');
var oldAttr = element.removeAttribute('id');
//添加新特性
element.attributes.setNamedItem(newAttr);
//上述attributes方法不够方便,一般还是使用getAttribute()、setAttribute()、removeAttribute()
//不过,遍历属性可以使用attributes属性
function outputAttribute(element){
var pairs = new Array(),
attrName,
attrValue,
i,
len;
for(i = 0, len=element.attributes.length; i<len, i++){
attrName = element.attributes[i].nodeName;
attrValue = element.attributes[i].nodeValue;
pairs.push(attrName + '=\'' + attrValue +'\'');
}
return pairs.join(" ");
}
创建元素
var div = document.createElement('div'); //创建元素
div.id = 'myDiv';
div.className = 'box';
document.body.appendChild(div);// 添加到<body>中
元素的子节点
元素也支持getElementsByTagName()方法。
//html代码
<ul id='myList'>
<li> 1 </li>
<li> 2 </li>
<li> 3 </li>
</ul>
//获取ul下的所有li
var ul = document.getElementById('myList');
var li = ul.getElementByTagName('li');
1.4 Text类型
Text节点有如下特征:
- nodeType: 3,也就是Node.TEXT_NODE
- nodeName: #text
- nodeValue: 所包含的文本
- parentNode: 一个Element
- 没有子节点
通过nodeValue
和data
属性访问Text节点中包含的文本。通过下面几个方法可以操作节点中的文本:
- appendData(text):将text添加到节点的末尾
- deleteData(offset, count):从offset指定的位置开始删除count个字符
- insertData(offset, text):从offset指定的位置插入text
- replaceData(offset, count, text),从offset指定的位置用text替换count个字符
- splitText(offset),从offset指定的位置将当前文本节点分成两个文本节点
- substringData(offset, count):提取从offset指定的位置开始到offset+count为止的字符串
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Text类型</title>
</head>
<body>
<div id="myDiv">Hello World!</div>
<script>
//通过nodeValue或者data访问Text节点中的内容
var text = document.getElementById("myDiv").firstChild;
console.log(text.nodeValue);//Hello World!
console.log(text.data); //Hello World!
//操作节点内容
text.appendData("Mango");
console.log(text.data); //Hello World!Mango
text.deleteData(12, 5);
console.log(text.data); //Hello World!
text.insertData(6, "the ");
console.log(text.data); //Hello the World!
text.replaceData(6, 3, "BigBig");
console.log(text.data); //Hello BigBig World!
</script>
</body>
</html>
创建文本节点
通过document.createTextNode()创建新文本节点。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>创建文本节点</title>
<style>
.message {
width: 100px;
height: 100px;
background-color: #333;
color: white;
}
</style>
</head>
<body>
<script>
var myDiv = document.createElement("div");
myDiv.className = "message";
var textNode = document.createTextNode("Hello World");
myDiv.appendChild(textNode);
document.body.appendChild(myDiv);
</script>
</body>
</html>
规范化文本节点
规划化文本节点的目的:使相邻的同胞文本节点合并为一个文本节点。使用normalize()方法。
<body>
<script>
var myDiv = document.createElement("div");
myDiv.className = "message";
var textNode = document.createTextNode("Hello World");
myDiv.appendChild(textNode);
var anotherTextNode = document.createTextNode("Mango");
myDiv.appendChild(anotherTextNode);
document.body.appendChild(myDiv);
console.log(myDiv.childNodes.length); //2
//规范化文本节点
myDiv.normalize();
console.log(myDiv.childNodes.length); //1
console.log(myDiv.firstChild.nodeValue); //Hello World!Mango
</script>
</body>
分割文本节点
使用splitText()方法。
<body>
<script>
var myDiv = document.createElement("div");
myDiv.className = "message";
var textNode = document.createTextNode("Hello World");
myDiv.appendChild(textNode);
document.body.appendChild(myDiv);
console.log(myDiv.childNodes.length); //1
var newNode = myDiv.firstChild.splitText(5);
console.log(myDiv.firstChild.nodeValue); //"Hello"
console.log(newNode.nodeValue); //" world"
console.log(myDiv.childNodes.length); //2
</script>
</body>
1.5 Comment类型
注释。先过
1.6 CDATASection类型
只针对基于XML的文档。暂时还未用到,先过。
1.7 DocumentType类型
部分浏览器支持该类型,一般不用。
DocumentType类型的对象保存在1document.doctype中。
console.log(document.doctype.name); //html
1.8 DocumentFragment类型
文档片段(document fragment)是一种“轻量级”的文档,可以包含和控制节点,不再文档树中。可以将它作为一个“仓库”使用。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>DocumentFragment类型</title>
</head>
<body>
<ul id="myList"></ul>
<script>
var fragment = document.createDocumentFragment();
var ul = document.getElementById("myList");
var li = null;
for (var i = 0; i < 3; i++) {
li = document.createElement("li");
li.appendChild(document.createTextNode("Item" + (i+1)));
fragment.appendChild(li);
}
//将fragment中的节点都转移到ul下
ul.appendChild(fragment);
</script>
</body>
</html>
2. DOM操作技术
2.1 动态脚本
动态脚本指的是:页面加载时不存在,但将来的某一时刻通过修改DOM动态添加的脚本。创建动态脚本的两种方式:
- 插入外部文件
- 直接插入JavaScript代码
比如加载如下代码:
<!-- 第一种方式:插入外部文件 -->
<script type="text/javascript" src="client.js"></script>
//动态加载
function loadScript(url){
var script = document.createElement("script");
script.type = "text/javascript";
script.src = url;
document.body.appendChild(script);
}
loadScript("client.js");
<!-- 第二种方式:行内方式 -->
<script type="text/javascript">
function sayHi(){
alert("hi");
}
</script>
//动态加载
function loadScriptString(code) {
var script = document.createElement("script");
script.type = "text/javascript";
try{
script.appendChild(document.createTextNode(code));//IE有可能抛出错误
} catch(ex) {
script.text = code;
}
document.body.appendChild(script);
}
loadScriptString("function sayHi(){alert('Hi');}");
2.2 动态样式
动态样式是指在页面刚加载时不存在的样式,在页面加载完成后动态添加到页面中。
以动态加载如下语句为例:
<link rel='stylesheet' type='text/css' href='styles.css'>
loadCSS(url) {
var link = document.createElement('link');
link.rel = 'stylesheet';
link.type = 'type';
link.href = url;
// 将 link 标签插入到 head 标签后
var head = document.getElementByTagName('head')[0];
head.appendChild(link);
}
loadCSS('styles.css');
动态加载非文件式样式,如下:
<style type='text/css'>
body {
background-color: red
}
</style>
var style = document.createElement("style");
style.type = "text/css";
try{
style.appendChild(document.createTextNode("body{background-color:red}"));
} catch (ex){
style.styleSheet.cssText = "body{background-color:red}";
}
var head = document.getElementsByTagName("head")[0];
head.appendChild(style);
2.3 操作表格
// 创建 table
var table = document.createElement('table');
table.border = 1;
table.width = '100%';
// 创建 tbody
var 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'));
//加入到body中
document.body.appendChild(table);
2.4 使用NodeList
理解NodeList
及其“近亲”NamedNodeMap
和HTMLCollection
,是从整体上透彻理解DOM的关键所在。这三个集合都是“动态的”;换句话说,每当文档结构发生变化时,它们都会得到更新。
一般来说,应该尽量减少访问NodeList 的次数。因为每次访问NodeList,都会运行一次基于文档的查询。所以,可以考虑将从NodeList 中取得的值缓存起来。