1 概述
DOM (Document Object Model) 是文本对象模型,是针对 HTML/XML 的基于树的 API。
- D:文档 — html 文档或 xml 文档 。
- O:对象 — Document 对象的属性和方法 。
- M:模型
DOM 树:节点(node)的层次。
DOM 把一个文档表示为一颗家谱树(父、子、兄弟),其定义了 Node 的接口以及许多种节点类型来表示 XML 节点的多个方面。
Java中也有DOM的实现,且提供的方法和属性与js是一致的,这里主要介绍js对DOM的支持。
以一个html文档为例:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Shopping List</title>
</head>
<body>
<h1>Want To Buy</h1>
<p title="a gentle reminder">Don't forget to buy this stuff.</p>
<ul id = "purchases">
<li>AAA</li>
<li>BBB</li>
<li>CCC</li>
</ul>
</body>
</html>
上述html文档可以看做如下一颗树:
- 节点及其类型
定义:节点来源于网络理论,代表网络中的一个连接点。网络是由节点构成的集合。
以上述html文档中的p节点为例来对节点的分类进行说明:
上述三个节点中,属性节点就是元素的属性,而文本节点则是元素节点的子节点,当然,元素节点的子节点也可能是元素节点,并不一定是文本节点。
2. HTML中js的位置及加载
- 1. 嵌入组件中
<button id="button" onclick="alert('hello world');">Click Me!</button>
优点:简介,方便。
缺点:js 和 html 耦合性太强,不利用代码的维护。
结论:在 html 结构简单组件较少且 js 内容较少时可使用该种写法。
- 2. 嵌入 html 的 head 节点中
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Untitled Document</title>
<script type="text/javascript">
var cityNode = document.getElementById("city");
//打印结果为 null
alert(cityNode);
</script>
</head>
优点:js 和 html 耦合较低,便于维护
缺点:繁琐。
结论:在 html 结构复杂且 js 逻辑较多时适合采用该种方式。
打印结果为空的原因是 html 按照顺序加载时会先加载 js 之后加载后面的 html 元素,所以先加载的 js 无法获取还未加载的 html 组件。
- 3. 写在 html 文档的最后
为解决上述获取不到元素的问题,我们可以将 script 标签写到 html 文档的最后,也就是</html>
的后面。
...
</html>
<script type="text/javascript">
alert(2);
</script>
虽然 js 会在加载 html 组件后才会加载,避免了获取不到未加载组件的为题,但是这样不符合我们的习惯。
- 4. 写在 window.onload 事件中
我们可以使用window.onload
来完美的解决上面三种写法的问题。
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" >"http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Untitled Document</title>
<script type="text/javascript">
alert(1);
window.onload = function(){
alert(3);
}
</script>
</head>
<body>
<p>这是一个html</p>
</body>
</html>
<script type="text/javascript">
alert(2);
</script>
上述代码运行后弹窗顺序分别为1、2、3,也就是说 window.onload 中的内容在文档加载完成后才运行。因此,我们应当将 js 嵌入head标签中,并用 window.onload 指定在html文档完全加载之后执行。
- 正确的写法
我们应该将 js 代码块放到 head 标签中并使用 window.onload 进行包裹,实例如下:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Untitled Document</title>
<script type="text/javascript">
window.onload = function(){
......
}
</script>
</head>
<body>
......
</body>
</html>
3 获取元素节点
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Untitled Document</title>
<script type="text/javascript">
//获取指定的元素节点.
window.onload = function(){
/**
*1. 通过 ID 获取节点(该方法为 document 对象的方法)
* 需要注意的是要确保ID的唯一。
*/
var bjNode = document.getElementById("bj");
/**
*2. 使用标签名获取指定节点的集合
* 该方法为 Node 接口的方法, 即任何一个节点都有这个方法。
*/
var liNodes = document.getElementsByTagName("li");
/**
*3. 根据 HTML 文档元素的 name 属性名来获取指定的节点的集合
* 需要注意的是若 HTML 元素自身没有定义 name 属性,
* 则 getElementsByName()方法对于 IE 无效.
* 所以使用该方法时需谨慎。
*/
var genderNodes = document.getElementsByName("gender");
/**
*下方的代码无法获取到节点
*虽然有一个 name 为 BeiJing 的 li,但 li 并没有被定义 name 属性,
*也就是说这里的 name 属性是我们强加上的,虽然谷歌浏览器可以识别,但IE无法识别。
*/
var bjNode2 = document.getElementsByName("BeiJing");
}
</script>
</head>
<body>
<p>你喜欢哪个城市?</p>
<ul id="city">
<li id="bj" name="BeiJing">北京</li>
<li>上海</li>
<li>东京</li>
<li>首尔</li>
</ul>
<br><br>
<p>你喜欢哪款单机游戏?</p>
<ul id="game">
<li id="rl">红警</li>
<li>实况</li>
<li>极品飞车</li>
<li>魔兽</li>
</ul>
<br><br>
gender:
<input type="radio" name="gender" value="male"/>Male
<input type="radio" name="gender" value="female"/>Female
</body>
</html>
4 读写属性节点
//属性节点即为某一指定的元素节点的属性,这里以文本框为例。
//第一种方式:通过元素节点 . 的方式来获取属性值和设置属性值.
//1. 获取指定的那个元素节点
var nameNode = document.getElementById("city");
//2. 读取指定属性的值
alert(nameNode.value);
//3. 设置指定的属性的值.
nameNode.value = "北京";
//第二种方式:获取属性节点对象
//1. 获取元素节点的指定属性节点
var nameAttr = nameNode.getAttributeNode("value");
alert(nameAttr);
//2. 读取指定属性的值
alert(nameAttr.nodeValue);
//3. 设置指定的属性的值.
nameAttr.nodeValue = "BeiJing";
5 读写文本节点
文本节点是元素节点的子节点,想要读写文本节点就需要先获取元素节点,然后获取其文本子节点,实例代码如下:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Untitled Document</title>
<script type="text/javascript">
window.onload = function(){
//1. 获取 id 为 city 的元素节点。
var cityNode = document.getElementById("city");
//2. 利用元素节点的 childNodes 方法可以获取指定元素节点的所有子节点。
//结果为 8,因为被 li 子节点分割的四份空白部分为四个文本节点,故而该方法不实用。
alert(cityNode.childNodes.length);
//3. 获取标签类型为 city 的节点的所有 li 子节点.
var cityLiNodes = cityNode.getElementsByTagName("li");
//结果为 4
alert(cityLiNodes.length);
//4. 获取指定节点的第一个子节点和最后一个子节点.
//结果是第一个 li 节点对象
alert(cityNode.firstChild);
//结果是最后一个 li 节点后的空白部分,是一个文本节点
alert(cityNode.lastChild);
//文本节点一定是元素节点的子节点.
//1. 获取文本节点所在的元素节点
var bjNode = document.getElementById("bj");
//2. 通过 firstChild 定义为到文本节点(子节点只有一个文本节点)
var bjTextNode = bjNode.firstChild;
//3. 通过操作文本节点的 nodeValue 属性来读写文本节点的值.
alert(bjTextNode.nodeValue);
bjTextNode.nodeValue = "BeiJing";
}
</script>
</head>
<body>
<p>你喜欢哪个城市?</p>
<ul id="city"><li id="bj" name="BeiJing">北京</li>
<li>上海</li>
<li>东京</li>
<li>首尔</li>
</ul>
</body>
</html>
注意:只有元素节点才有子节点。
6 Js 中的 this 与方法引用
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Untitled Document</title>
<script type="text/javascript">
window.onload = function(){
var liNodes = document.getElementsByTagName("li");
//1. 使用 for 循环进行遍历. 得到每一个 li 节点
for(var i = 0; i < liNodes.length; i++){
//2. 为每一个 li 节点添加 onclick 响应函数.
liNodes[i].onclick = function(){
//this 为正在响应事件的那个节点.
alert(this.firstChild.nodeValue);
//如果这里不使用 this 而是使用 liNodes[8] 则不能达到预期效果
//因为该点击事件是在 for 循环执行完毕,用户点击 li 时才会执行,
//此时 i 已经是 8 了, 而 liNodes[8] 不指向任何节点.
}
/*
下方写法赋给 onclick 的是 hello 函数的引用,当用户触发点击事件才会执行 hello 函数。
liNodes[i].onclick = hello;
下方写法在给 onclick 赋值的同时也执行了 hello 函数
liNodes[i].onclick = hello();
function hello(){
alert("hello!");
}
*/
}
}
</script>
</head>
<body>
<p>你喜欢哪个城市?</p>
<ul id="city">
<li id="bj" name="BeiJing">北京</li>
<li>上海</li>
<li>东京</li>
<li>首尔</li>
</ul>
<br><br>
<p>你喜欢哪款单机游戏?</p>
<ul id="game">
<li id="rl">红警</li>
<li>实况</li>
<li>极品飞车</li>
<li>魔兽</li>
</ul>
<br><br>
name: <input type="text" name="username"
id="name" value="anruo"/>
</body>
</html>
注意点:
1. 不能在 for 循环的事件中使用循环变量 i ,因为 for 循环中的事件是在 for 循环执行完毕后用户触发事件才会执行相应的函数,此时循环变量 i 是循环结束时的值,没有实际意义。(可参考示例代码中的相关部分)
2. 在 Js 中使用函数的名称来引用函数,如果名称后加了()
则表示该函数在引用的同时执行。(参考示例代码添加点击事件部分)
7 三种节点的节点属性
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Untitled Document</title>
<script type="text/javascript">
//关于节点的属性: nodeType, nodeName, nodeValue
//在文档中, 任何一个节点都有这 3 个属性
//而 id, name, value 是具体节点的属性.
window.onload = function(){
//1. 元素节点的这 3 个属性
var bjNode = document.getElementById("bj");
alert(bjNode.nodeType); //元素节点: 1
alert(bjNode.nodeName); //节点名: li
alert(bjNode.nodeValue); //元素节点没有 nodeValue 属性值: null
//2. 属性节点
var nameAttr = document.getElementById("name")
.getAttributeNode("name");
alert(nameAttr.nodeType); //属性节点: 2
alert(nameAttr.nodeName); //属性节点的节点名: 属性名
alert(nameAttr.nodeValue); //属性节点的 nodeValue 属性值: 属性值
//3. 文本节点:
var textNode = bjNode.firstChild;
alert(textNode.nodeType); //文本节点: 0
alert(textNode.nodeName); //节点名: #text
alert(textNode.nodeValue); //文本节点的 nodeValue 属性值: 文本值本身.
//nodeType、nodeName 是只读的.
//而 nodeValue 是可以被改变的。
//以上三个属性, 只有在文本节点中使用 nodeValue 读写文本值时使用最多.
}
</script>
</head>
<body>
<p>你喜欢哪个城市?</p>
<ul id="city">
<li id="bj" name="BeiJing">北京</li>
<li>上海</li>
<li>东京</li>
<li>首尔</li>
</ul>
<br><br>
<p>你喜欢哪款单机游戏?</p>
<ul id="game">
<li id="rl">红警</li>
<li>实况</li>
<li>极品飞车</li>
<li>魔兽</li>
</ul>
<br><br>
name: <input type="text" name="username"
id="name" value="anruo"/>
</body>
</html>
判断元素节点是否存在指定属性:我们可以通过
if(元素节点.属性名)
来判断某一元素节点有没有特定属性。如使用if(redioNode.checked)
来判断单选按钮元素节点是否有 checked 属性,如果有也就是单选按钮被选中则 if 中的判断条件结果为 true。
8 创建并加入节点
1. 创建元素节点
我们可以使用createElement(nodeName)
来创建一个节点,方法描述如下:
所属对象 | document | |
---|---|---|
方法 | createElement(nodeName) | |
描述 | 根据指定的标签名 (li、p、lable等)创建一个新的元素节点,这个新创建的节点的 nodeType 属性值为1。 var liNode = document.createElement("li"); |
需要注意的是:使用
createElement()
方法创建的新元素节点只是一个存在于 JavaScript 上下文的对象,并不会添加到文档里,即不会对 HTML 文件内容做修改。
2. 创建文本节点
我们可以使用createTextNode(textValue)
方法为元素节点创建一个文本节点,方法描述如下:
方法 | 描述 | |
---|---|---|
createTextNode(textValue) | 作用 | 创建一个包含着给定文本的新文本节点,该节点的 nodeType 属性值为 3。 |
入参 | 字符串类型,作为被创建的文本节点所包含的文本字符串。 | |
返回值 | 一个指向新建文本节点引用指针。 | |
示例 | var xmText = document.createTextNode("厦门"); |
同样,使用
createTextNode()
方法创建的新文本节点也只是一个存在于 JavaScript 上下文的对象,并不会添加到文档里,即不会对 HTML 文件内容做修改。
3. 为元素节点添加子节点
我们可以使用appendChild(ele)
方法为元素节点添加一个子节点,方法描述如下:
方法 | 描述 | |
---|---|---|
appendChild(ele) | 作用 | 将给定节点添加为目标节点的最后一个子节点。 |
入参 | 节点对象或叫节点引用,作为子节点被添加到目标节点。 | |
返回值 | 一个指向新增子节点的引用指针。 | |
示例 | 我们将上述两个示例中新建文本节点添加为新建元素节点的子节点: liNode.appendChild(xmText); |
该操作依旧不会对 HTML 文件的内容产生影响。
9 替换节点
我们可以使用replaceChild(newChild,oldChild)
方法把一个给定父元素里的一个子节点替换为另外一个子节点,方法描述如下:
方法 | 描述 | |
---|---|---|
replaceChild(newChild,oldChild) | 作用 | 把一个给定父元素里的一个子节点替换为另外一个子节点。 |
入参 | 需要两个入参,第一个为新节点引用指针,后一个为要被替换的旧节点引用指针。 | |
返回值 | 一个指向已被替换的那个子节点的引用指针。 | |
示例 | var reference = bParent.replaceChild(newChild,oldChild); |
replaceChild()
方法除了替换功能以外还有移动的功能,即如果页面上存在节点 A 和节点 B 两个节点,我们使用方法replaceChild(A,B)
将 B 节点替换为 A 节点,效果是将 A 节点移动的 B 节点的位置,页面上将不再有B节点。简而言之就是将新节点移动到待替换的旧节点位置进行覆盖。
只能完成单向替换, 若需要使用双向替换, 需要自定义函数。
下面是一个鼠标点击交换 li 的案例:
效果:
代码:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
<script type="text/javascript">
//需求: 为所有的 li 节点添加 onclick 响应函数
//实现 city 子节点和 game 子节点对应位置的元素的互换
window.onload = function(){
//自定义互换两个节点的函数
function replaceEach(aNode, bNode){
//1. 获取 aNode 和 bNode 的父节点. 使用 parentNode 属性
var aParent = aNode.parentNode;
var bParent = bNode.parentNode;
if(aParent && bParent){//判断是否存在属性
//2. 克隆 aNode 或 bNode,当入参为 true 时可以克隆子节点
var aNode2 = aNode.cloneNode(true);
//克隆 aNode 的时不能克隆其属性,因此我们要手动处理
aNode2.onclick = aNode.onclick;
aNode2.index = aNode.index;
//3. 分别调用 aNode 的父节点和 bNode 的父节点的 replaceChild()
//方法实现节点的互换
bParent.replaceChild(aNode2, bNode);
aParent.replaceChild(bNode, aNode);
}
}
//1. 获取所有的 li 节点
var liNodes = document.getElementsByTagName("li");
//2. 为每一个 li 节点添加 Onclick 响应函数
for(var i = 0; i < liNodes.length; i++){
//手动为每个 li 节点添加一个 index 属性
liNodes[i].index = i;
liNodes[i].onclick = function(){
//3. 找到和当前节点对应的那个 li 节点
var targetIndex = 0;
if(this.index < 4){
targetIndex = 4 + this.index;
}else{
targetIndex = this.index - 4;
}
//交换 index 属性.
var tempIndex = this.index;
this.index = liNodes[targetIndex].index;
liNodes[targetIndex].index = tempIndex;
//4. 互换.
replaceEach(this, liNodes[targetIndex]);
}
}
}
</script>
</head>
<body>
<p>你喜欢哪个城市?</p>
<ul id="city">
<li id="bj">北京</li>
<li>上海</li>
<li>东京</li>
<li>首尔</li>
</ul>
<br><br>
<p>你喜欢哪款单机游戏?</p>
<ul id="game">
<li id="rl">红警</li>
<li>实况</li>
<li>极品飞车</li>
<li>魔兽</li>
</ul>
<br><br>
</body>
</html>
上述案例中需要注意两点:
1. 第 17 行使用的cloneNode(true)
方法不能克隆节点的属性,因此在后面克隆时需要给克隆节点设置属性(添加点击事件),否则克隆后的节点将不再拥有原有点击事件。
2. 第 33 行定义index
属性是因为for
循环的循环变量i
在点击事件中没有实际意义,我们只能个节点定义一个index
属性来作为其索引,方便交换。
所属对象 | Node | |
---|---|---|
方法 | Node cloneNode(boolean deep) | |
描述 | 克隆节点,当入参 deep 为 true 时将会克隆目标节点的子节点。 需要注意的是,该方法不会克隆原节点的属性值,如需要则应在克隆后为克隆节点手动赋予。 var aNode2 = aNode.cloneNode(true); aNode 是一个节点对象,也是克隆目标。aNode2 为对 aNode 克隆后产生的克隆节点的引用指针。 |
10 删除节点
所属对象 | Node 元素节点 | |
---|---|---|
方法 | Node removeChild(Node node) | |
描述 | 父元素调用该方法,删除指定子元素节点。方法返回值为被删除的子节点的引用指针。 想要删除一个节点必须要得到该节点的父节点,然后使用父节点调用该删除节点的方法。 节点被删除时,该节点下的所有子节点也将被删除。 |
如果我们不知道要删除节点的父节点,可以使用 parentNode 来获取,示例如下.
//获取待删除节点 var deleteNode = document.getElementById("BeiJing"); var parentEle = deleteNode.parentNode; parentEle.removeChild(this);
我们可以使用 confirm 来使用浏览器默认给出的确认弹框,示例代码如下:
var flag = confirm("确认弹框显示信息。");
如果我们选择了确认弹框的确认按钮,返回值 flag 为 true ,否则为 false。弹窗样式如下:
11 插入节点
我们可以使用 insertBefore 方法来实现节点的插入。如parentNode.insertBefore(newNode, nextNode);
语句的作用就是将 newNode 节点插入到 parentNode 节点的子节点 nextNode 的前面。也就是说我们只有在知道父节点的情况下才能使用该方法向该父节点一某个子节点前插入一个节点。
insertBefore 方法除了插入节点的功能还具有移动节点的功能。如果 newNode 是一个已存在的节点,那么通过 parentNode.insertBefore(newNode, nextNode);
语句我们可以将其移动到 nextNode 节点的前方。
虽然有 insertBefore 方法,但并不存在往某节点后插入节点的方法。如果我们需要该功能则需要手动编写该方法,下方将给出一个示例。
//把 newNode 插入到 refNode 的后面
function insertAfter(newNode, refNode){
//1. 测试 refNode 是否为其父节点的最后一个子节点
var parentNode = refNode.parentNode;
if(parentNode){
var lastNode = parentNode.lastChild;
//2. 若是: 直接把 newNode 插入为 refNode 父节点的最后一个子节点.
if(refNode == lastNode){
parentNode.appendChild(newNode);
}
//3. 若不是: 获取 refNode 的下一个兄弟节点, 然后插入到其下一个兄弟
//节点的前面.
else{
var nextNode = refNode.nextSibling;
parentNode.insertBefore(newNode, nextNode);
}
}
}
12 innerHTML 属性
innerHTML 属性能够设置或返回某节点开始和结束标签之间的 HTML 内容。
var cityNode = document.getElementById("city");
//1.获取节点标签间的HTML内容
var nodeHtml = cityNode.innerHTML;
//2.设置节点标签间的HTML内容
cityNode.innerHTML = "<p>跳转</p>";