DOM:Document Object Model 文档对象模型
节点层级
<html>
<head>
<title>Sample Page</title>
</head>
<body>
<p>Hellow World!</p>
</body>
</html>
对应的层级结构:
Document
- Element html
- Element head
- Element title
- Text Sample Page
- Element body
- Element p
- Text Hello worlds!
其中,document结点表示每个文档的跟节点。根节点的唯一子节点是<html>元素,我们称之为文档元素。文档元素是文档最外层的元素,所有其他元素都存在于这个元素之内。每个文档只能有一个文档元素。
HTML中的每段标记都可以表示为这个树形结构中的一个节点。元素节点表示HTML元素,属性节点表示属性,文档节点表示文档类型,注释节点表示注释。DOM中一共有12种节点类型,这些类型都继承一种基本类型。
节点关系
每个节点都有一个childNodes属性,其中包含一个NodeList的实例。NodeList是一个类数组对象,用于存储可以按位置存取的有序节点。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.box {
display: flex;
flex-direction: column;
justify-content: space-between;
width: 300px;
height: 300px;
background-color: red;
}
.box>div {
flex: 1;
margin: 20px 0;
border: 1px solid blue;
background-color: #fff;
}
</style>
</head>
<body>
<div class="box">
<div>1</div>
<div>2</div>
<div>3</div>
</div>
<script>
let box = document.querySelector('.box');
let nodes = box.childNodes;
console.log(nodes);
</script>
</body>
</html>
访问NodeList中的元素
-
一种是使用中括号[]
let firstChild = someNode.childNodes[0] -
一种是使用
item()方法let secondChild = someNode.childNodes.item(1) -
使用
length可以返回节点数量let count = someNode.childNodes.length;
每个节点都有一个parentNode属性,指向DOM树(层级结构)中的父元素。childNodes中的所有结点都有同一个父元素,因此他们的parentNode属性都指向同一个节点。此外,childNodes列表中的每个节点都是同一列表中其他节点的同胞节点。而使用previousSibling和nextSibling可以在这个列表的节点间导航。这个列表中的第一个节点的previousSibling属性是null, 最后一个节点的nextSibling属性也是null。
// html接的上面的
let box = document.querySelector('.box');
let nodes = box.childNodes;
let firstNode = nodes[0];
console.log(firstNode.previousSibling); // null
let lastNode = nodes[nodes.length - 1];
console.log(lastNode.nextSibling); //null
注:如果childNodes中只有一个节点,则它的previousSibling和nextSibling属性都是null。
此外,第一个节点和最后一个节点有专门的属性。
// html接的上面的
let box = document.querySelector('.box');
let firstNode = box.firstChild;
console.log('firstNode:', firstNode);
let lastNode = box.lastChild;
console.log('lastNode:', lastNode);
childNodes与children
但是我们发现这个childNodes有一些不好的地方,他只要在子元素中有任何换行、空格就会产生Text Element。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.box {
display: flex;
flex-direction: column;
justify-content: space-between;
width: 300px;
height: 300px;
background-color: red;
}
.box>div {
flex: 1;
margin: 20px 0;
border: 1px solid blue;
background-color: #fff;
}
</style>
</head>
<body>
<div class="box">
<div>1</div>
<div>2</div>
<div>3</div>
</div>
<script>
// html接的上面的
let box = document.querySelector('.box');
console.log(box.childNodes);
</script>
</body>
</html>
-
0:text的data是'\n' ,指的是
-
1:div是
<div>1</div> -
2:text的data是'\n',指的是
.......
如果我们吧这个换行去掉:
<body>
<div class="box"><div>1</div><div>2</div><div>3</div></div>
<script>
// html接的上面的
let box = document.querySelector('.box');
console.log(box.childNodes);
</script>
</body>
我们发现它的子节点没有text了,length也为3了。
为了更高的获得标签Element,而不是text,我们一般不使用someNode.childNodes,而是使用someNode.children.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.box {
display: flex;
flex-direction: column;
justify-content: space-between;
width: 300px;
height: 300px;
background-color: red;
}
.box>div {
flex: 1;
margin: 20px 0;
border: 1px solid blue;
background-color: #fff;
}
</style>
</head>
<body>
<div class="box">
<div>1</div>
<div>2</div>
<div>3</div>
</div>
<script>
// html接的上面的
let box = document.querySelector('.box');
console.log(box.children);
</script>
</body>
</html>
获取节点
老方法就不写了,基本没咋用过:
document.getElementById()
document.getElementByTagName()
document.get....
...
现在都是使用querySelector()来获取节点元素了:(返回的是一个标签对象)
let box1 = document.querySelector('#box'); //获取id为box的元素
let box2 = document.querySelector('.box'); //获取类名为box的第一个元素
let body = document.querySelector('body'); //获取<body>
let div1 = document.querySelector('#box div'); //获取类名为box下面的第一个div元素
let div2 = box2.querySelector('div'); // 返回的是box2下面的第一个div标签
let img = document.querySelector('img.button'); //取得类名为button的图片
要获取多个元素使用querySelectorAll():(返回的是一个伪数组)
document.querySelectorAll('div'); //获取所有的div标签
document.querySelectorAll('.box'); //获取所有类名包含box的标签
获取body可以使用:
let body = document.body;
获取html可以使用:
let html = document.documentElement;
获取head可以使用:
let head = document.head;
操纵节点
createElement()
用于创建元素节点。并返回一个Element对象:
//语法 document.createElement(tagName);
// tagName 字符串值,指明创建元素的类型
let div = document.createElement('div');
div.innerHTML = '4';
console.log(div); // <div>4</div>
appendChild()
用于在childNodes或者children列表末尾添加结点。appendChild()方法返回新添加的节点。
// 接上面的代码
let box = document.querySelector('.box');
let div = document.createElement('div');
div.innerHTML = '4';
let newNode = box.appendChild(div);
console.log(newNode); // <div>4</div>
如果把文档树中已存在的节点传给appendChild(),则这个节点会从之前的位置被转移到新位置。
<body>
<div class="box">
<div>1</div>
<div>2</div>
<div>3</div>
</div>
<script>
// html接的上面的
let box = document.querySelector('.box');
let firstNode = box.firstElementChild;
let newNode = box.appendChild(firstNode);
console.log(newNode);
</script>
</body>
注意:someNode.firstElementChild,得到的是子节点中的第一个标签节点,会忽略其他节点。同理,还有someNode.lastElementChild,指向最后一个Element类型的子元素;previousElementSibling,指向前一个Element类型的同胞元素,nextElementSibling,指向后一个Element类型的同胞元素;childElementCount返回子元素中Element类型的元素数量。
insertBefore()
如果想要吧节点放到childNodes或者children列表中的特定位置而不是末尾,则可以调用insertBefore()方法。这个方法接收两个参数:要插入的节点和参照节点。调用这个方法后,要插入的节点会变成参照节点的前一个同胞节点,并被返回。如果参照节点是null,则insertBefore()和appendChild()效果相同。
<body>
<div class="box">
<div>1</div>
<div>2</div>
<div>3</div>
</div>
<script>
// html接的上面的
let box = document.querySelector('.box');
let nodes = box.children;
let div = document.createElement('div');
div.innerHTML = '4';
let newNode = box.insertBefore(div,nodes[1]); //nodes[1] 在这里就是 <div>2</div>
console.log(newNode);
</script>
</body>
replaceChild()
replaceChild()方法接收两个参数:要插入的节点和要替换的节点。要替换的节点会被返回并从文档树中完全移除,要插入的节点取而代之。
<body>
<div class="box">
<div>1</div>
<div>2</div>
<div>3</div>
</div>
<script>
// html接的上面的
let box = document.querySelector('.box');
let nodes = box.children;
let div = document.createElement('div');
div.innerHTML = '4';
let newNode = box.replaceChild(div,nodes[1]); //nodes[1] 在这里就是 <div>2</div>
console.log(newNode);
</script>
</body>
removeChild()
该方法接收一个参数,即将要移除的节点。
<body>
<div class="box">
<div>1</div>
<div>2</div>
<div>3</div>
</div>
<script>
// html接的上面的
let box = document.querySelector('.box');
let nodes = box.children;
let node = box.removeChild(nodes[1]); //nodes[1] 在这里就是 <div>2</div>
console.log(node);
</script>
</body>
cloneNode()
克隆节点。会返回与调用它的节点一模一样的节点,此外,该方法接收一个参数:布尔值,表示是否深层复制。传入true时,会进行深层复制,即复制结点以及其整个子DOM树。如果传入false,则只会复制调用该方法的节点。
<body>
<div class="box">
<div>1</div>
<div>2</div>
<div>3</div>
</div>
<script>
// html接的上面的
let box = document.querySelector('.box');
let nodes = box.children;
let newNode = nodes[1].cloneNode(true);
box.insertBefore(newNode,nodes[0]);
console.log(newNode);
</script>
</body>
如果我们传入false:
<body>
<div class="box">
<div>1</div>
<div>2</div>
<div>3</div>
</div>
<script>
// html接的上面的
let box = document.querySelector('.box');
let nodes = box.children;
let newNode = nodes[1].cloneNode(false);
box.insertBefore(newNode,nodes[0]);
console.log(newNode);
</script>
</body>
则只会复制
,而不会复制它里面的Text Node: 2.注:这个方法只会复制其HTML属性,不会复制其JavaScript属性,比如事件处理程序。
parentNode()
返回元素的父节点。
normalize()
现在没啥用了吧,有了Children代替childNodes后。
标签属性
我们每一个标签上面就有很多属性,有些是自带的,有些是我们自定义的:
<img src="" alt="" index=“”>
它上面有自带的src和alt属性,也有我们自定义的index属性。
DOM上提供了3种方法对标签属性进行操作:
getAttribute()
获取标签的属性:
<body>
<img src="https://img1.baidu.com/it/u=1149359842,3831784241&fm=253&fmt=auto&app=138&f=JPEG?w=400&h=400" alt="头像" index="1">
<script>
let img = document.querySelector('img');
let src = img.getAttribute('src');
console.log(src); // 1.html:34 https://img1.baidu.com/it/u=1149359842,3831784241&fm=253&fmt=auto&app=138&f=JPEG?w=400&h=400
</script>
</body>
removeAttribute()
去除标签的属性:
<body>
<img src="https://img1.baidu.com/it/u=1149359842,3831784241&fm=253&fmt=auto&app=138&f=JPEG?w=400&h=400" alt="头像" index="1">
<script>
let img = document.querySelector('img');
img.removeAttribute('index');
console.log(img);
// <img src="https://img1.baidu.com/it/u=1149359842,3831784241&fm=253&fmt=auto&app=138&f=JPEG?w=400&h=400" alt="头像">
</script>
</body>
setAttribute()
设置标签的属性:
<body>
<img src="https://img1.baidu.com/it/u=1149359842,3831784241&fm=253&fmt=auto&app=138&f=JPEG?w=400&h=400" alt="头像" index="1">
<script>
let img = document.querySelector('img');
img.setAttribute('from','百度');
console.log(img);
// <img src="https://img1.baidu.com/it/u=1149359842,3831784241&fm=253&fmt=auto&app=138&f=JPEG?w=400&h=400" alt="头像" from="百度">
</script>
</body>
操作CSS类
如果标签的类名只有一个,那么可以很好的操作:
<body>
<div class="box">
1
</div>
<script>
let box = document.querySelector('.box');
// 获取类名
let name = box.className;
console.log(name); // box
box.className = 'box2';
console.log(box); // <div class="box2">1</div>
</script>
</body>
如果有多个类名,那么使用className就很恶心了。
classList
HTML5新增的,是一个新的集合类型DOMTokenList的实例。属于DOM。
DOMTokenList增加了以下方法:
- add(value),向类名列表中添加指定的字符串值value。如果已存在这个值,则什么也不做。
- contains(value),返回布尔值,表示给定的value是否存在。
- remove(value),从类名列表中删除指定的字符串值value。
- toggle(value),如果类名列表中已经存在指定的value,则删除,如果不存在,则添加。
<body>
<div class="box user disabled">
1
</div>
<script>
let box = document.querySelector('.box');
// 删除user类
box.classList.remove('user');
// 添加current类
box.classList.add('current');
console.log(box); // <div class="box disabled current">1</div>
</script>
</body>
H5的其他扩展
readyState属性
有两个值:
- loading,表示文档正在加载
- complete,表示文档加载完成
自定义数据类型
H5允许给元素指定非标准的属性,但要使用前缀data-以便告诉浏览器,这些属性既不包含与渲染有关的信息,也不包含元素的语义信息。
<div id="myDiv" data-appId="12345" data-myname="Nicholas">
1
</div>
定义了自定义数据属性后,可以通过元素的dataset属性来访问。dataset属性是一个DOMStringMap的实例,包含一组键值对映射。元素的每一个data-name属性在dataset中都可以通过data-后面的字符串作为健来访问(例如,属性data-myname、data-myName可以通过myname访问,但要注意data-my-name、data-My-Name要通过myName来访问)。
<body>
<div id="myDiv" data-appId="12345" data-myname="Nicholas">
1
</div>
<script>
let div = document.querySelector('#myDiv');
let appId = div.dataset.appid;
let myName = div.dataset.myname;
//设置自定义数据属性的值
div.dataset.appid = 23456;
div.dataset.myname = 'Michael';
console.log(appId,myName); // 12345 Nicholas
console.log(div); // <div id="myDiv" data-appId="23456" data-myname="Michael">1</div>
</script>
</body>
innerHTML、outerHTML、innerText、outerText
innerHTML
这个包括元素所有后代的HTML字符串,包括元素、注释、文本节点。而在写入innerHTML时,则会根据提供的字符串值以新的DOM子树替代元素中原来包含的所有结点。
这个搭配ES6新增的模板字符串(``) 很好用哦!
outerHTML
会返回调用它的元素(及所有后代元素)的HTML字符串。其实就是比innerHTML多一个它本身!
<body>
<div id="myDiv" data-appId="12345" data-myname="Nicholas">
<h2>标题</h2>
<p>啦啦啦啦啦</p>
</div>
<script>
let div = document.querySelector('#myDiv');
console.log(div.outerHTML);
/***
* <div id="myDiv" data-appid="12345" data-myname="Nicholas">
* <h2>标题</h2>
* <p>啦啦啦啦啦</p>
* </div>
*/
console.log(div.innerHTML);
/**
* <h2>标题</h2>
* <p>啦啦啦啦啦</p>
*/
</script>
</body>
注意,如果使用outerHTML设置HTML:
div.outerHTML = '<p>this is a paragraph</p>';
则等于:
let p = document.createElement('p');
p.appendChild(document.createTextNode('this is a paragraph'));
div.parentNode.replaceChild(p,div);
新的<p>元素会替换掉DOM树原先的<div>元素。
innerText
对应元素中包含的所有文本内容。无论文本在子树中的哪一个层级。
读值时,innerText会按照深度优先的顺序将子树中的所有文本节点拼接起来。
写值时,innerText会移除元素的所有后代并插入一个包含该值的文本节点。(不会解析html标签)
<body>
<div id="myDiv" data-appId="12345" data-myname="Nicholas">
<h2>标题</h2>
<p>啦啦啦啦啦</p>
</div>
<script>
let div = document.querySelector('#myDiv');
console.log(div.innerText);
/**
* 标题
* 啦啦啦啦啦
*/
div.innerText = '<p>this is a paragraph</p>';
console.log(div);
// <div id="myDiv" data-appid="12345" data-myname="Nicholas"><p>this is a paragraph<p></div>
</script>
</body>
outerText
与innerText属性类似,只不过作用范围包含调用它的节点。
读取时,与innerText返回相同的内容
写值时,outerText不止会移除后代节点,而是会替换整个元素。
div.outerText = '<p>this is a paragraph</p>'
就等同于:
let text = document.createTextNode('<p>this is a paragraph</p>');
div.parentNode.replaceChild(text,div);