DOM
DOM主要讲解四大部分:获取元素,事件基础,操作元素,节点操作。上节已经学习了前三个,本节来学习节点操作。
1.节点操作
1.1 为什么需要节点操作
1.2 节点概述
新建.html文件,执行代码如下
<!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>
</head>
<body>
<!-- 1.节点的优点 -->
<div>我是div</div>
<span>我是span</span>
<!-- ul>li*4 -->
<ul>
<li>我是li</li>
<li>我是li</li>
<li>我是li</li>
<li>我是li</li>
</ul>
<script>
// 2.为什么需要节点
// 如果没有节点,我想要获取div和span标签,通过docment.querySelector('div')和docment.querySelector('span')
// 如果没有节点,我想要获取li标签,通过docment.querySelector('ul')先拿到ul标签,再querySelectorall('li')
// 如果有节点,想要得到div,先用以前的方法docment.querySelector('div')把div拿到,再用div的下一个兄弟,就能拿到span
// 如果有节点,想要得到li,先拿到父亲元素ul,再用父亲的孩子就可以拿到li
</script>
<div class="box">
<!-- span.erweima -->
<span class="erweima">x</span>
</div>
<script>
// 如果没有节点,比如关闭二维码案例,必须先获取span,再获取到div,这样才能进行关闭操作
// 如果有节点,比如关闭二维码案例,先拿到span,再用span的父亲就可以拿到div
</script>
<script>
// 3.需求:获取box的节点
var box = document.querySelector('.box');
// 打印元素对象
console.dir(box);
</script>
</body>
</html>
效果如下
1.3 节点层级
1.3.1 父级节点
node.parentNode
新建.html文件,执行代码如下
<!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>
</head>
<body>
<div>我是div</div>
<span>我是span</span>
<ul>
<li>我是li</li>
<li>我是li</li>
<li>我是li</li>
<li>我是li</li>
</ul>
<div class="demo">
<div class="box">
<span class="erweima">x</span>
</div>
</div>
<script>
// 需求:点击左上角的x号,关闭二维码图片
// 1.父节点 parentNode querySelecor的快捷键qs
var erweima = document.querySelector('.erweima');
// var box = document.querySelector('.box') 这是以前的写法
erweima.parentNode; // 获取了bo节点,利用节点关系,div和span是父子关系
// 得到的是离元素最近的父级节点 亲爸爸 如果找不到父节点,返回为空
console.log(erweima.parentNode);
console.log('---------');
// 获取demo的父节点
var demo = document.querySelector('.demo')
console.log(demo.parentNode);
console.log('---------');
// 获取html的父节点
var html = document.documentElement;
console.log(html.parentNode);
console.log('---------');
</script>
</body>
</html>
效果如下
1.3.2 子节点
1.parentNode.childNodes (标准)
2.parentNode.children (非标准)
新建.html文件,执行代码如下
<!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>
</head>
<body>
<div>我是div</div>
<span>我是span</span>
<!-- ul>li*4 -->
<ul>
<li>我是li</li>
<li>我是li</li>
<li>我是li</li>
<li>我是li</li>
</ul>
<!-- ol>li*4 -->
<ol>
<!-- 多行写快捷键 shift + alt + a -->
<li>我是li</li>
<li>我是li</li>
<li>我是li</li>
<li>我是li</li>
</ol>
<!-- .demo>.box>span.ervweima -->
<div class="demo">
<div class="box">
<span class="ervweima">x</span>
</div>
</div>
<script>
// 以前的做法:用DOM提供的方法(API)获取,先获取ul,再得到里面的li
/* var ul = document.querySelector('ul');
var lis = ul.querySelectorAll('li'); */
// 1.子节点
// 方法一:childNodes 所有的子节点 包含 元素节点 文本节点等
/* var ul = document.querySelector('ul');
ul.childNodes; // 不光会返回li,还有像换行这样的文本节点
console.log(ul.childNodes);
console.log(ul.childNodes[0]); // 拿到第一个文本节点
console.log(ul.childNodes[0].nodeType); // 3 拿到第一个文本节点的nodeType属性
console.log(ul.childNodes[1].nodeType); // 1 拿到第一个li的nodeType属性 */
// 如果只想要获得里面的元素节点,则需要专门处理,一般不提倡使用childNodes
/* var ul = document.querySelector('ul');
for (var i = 0; i < ul.childNodes.length; i++) {
if (ul.childNodes[i].nodeType == 1) {
// ul.childNodes[i]是元素节点
console.log(ul.childNodes[i]);
}
} */
// 方法二:children 非标准 获取所有的子元素节点
// parentNode.children 是一个只读属性,返回所有的子元素节点。它只返回子元素节点,其余节点不反悔
// 虽然chilren 是一个非标准,但是得到了各个浏览器的支持
var ul = document.querySelector('ul');
ul.children;
console.log(ul.children);
</script>
</body>
</html>
效果如下
效果如下
1.3.3 获取第一个子元素和最后一个子元素
1.parentNode.firstChild 返回第一个子节点,找不到则返回null。同样,也是包含所有的节点。
2.parentNode.lastChild 返回最后一个子节点,找不到则返回null。同样,也是包含所有的节点。
// 下面两个方法有兼容性问题,IE9以上才支持
3.parentNode.firstElementChild // 返回第一个子元素节点,找不到则返回null
4.parentNode.lastElementChild // 返回最后一个子元素节点,找不到则返回null
实际开发中,firstChild和lastChild包含其他节点,操作不方便,而firstElementChild和lastElement又有兼容性问题, 那么我们如何获取第一个节点或最后一个子元素节点呢?
解决方案:
1.如果想要第一个子元素节点,可以使用parentNode.children[0]
新建.html文件,执行代码如下
<!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>
</head>
<body>
<!-- ol>li*4 -->
<ol>
<li>我是li1</li>
<li>我是li2</li>
<li>我是li3</li>
<li>我是li4</li>
</ol>
<script>
var ol = document.querySelector('ol');
// 1.firstchild 第一个子节点,不管是文本节点还是元素节点
ol.firstChild; // 获取ol里面子节点的第一个子节点
console.log(ol.firstChild); // #text
// 2.lastChild 返回最后一个子节点 不管是文本节点还是元素节点
ol.lastChild;
console.log(ol.lastChild); // #text
// 1.firstElementChild 返回第一个子元素节点 ie9以上支持
ol.firstElementChild;
console.log(ol.firstElementChild); // 获取了第一个子元素节点 li
// 2.last.ElementChild 返回最后一个子元素节点 ie9以上支持
ol.lastElementChild;
console.log(ol.lastElementChild); // 获取了最后一个子元素节点 li
// 3.实际开发中的写法,既没有兼容性问题又返回第一个子元素
ol.children; // children 返回的是伪数组
console.log(ol.children);
ol.children[0]; // 返回第一个
console.log(ol.children[0]);
ol.children[3]; // 返回第四个
console.log(ol.children[3]);
// 实际开发过程中,往往最后一个子元素不确定,可以写成
ol.children[ol.children.length - 1];
console.log(ol.children.length);
console.log(ol.children[ol.children.length - 1]);
</script>
</body>
</html>
效果如下
1.3.4 案例:下拉菜单
新建.html文件,执行代码如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>新浪下拉菜单</title>
<style>
* {
margin: 0;
padding: 0;
}
li {
list-style: none;
}
a {
text-decoration: none;
font-size: 14px;
}
.nav {
margin: 100px;
border-top: 2px solid #ff8500;
}
.nav>li {
position: relative;
float: left;
width: 100px;
height: 41px;
text-align: center;
}
.nav li a {
display: block;
width: 100%;
height: 100%;
line-height: 41px;
color: #333;
}
.nav>li:hover {
background-color: #eee;
}
.nav>li>a:hover {
background-color: #eee;
color: #ff8400;
}
.nav ul {
/* 隐藏 */
display: none;
position: absolute;
top: 41px;
left: 0;
width: 100%;
border-left: 1px solid #FECC5B;
border-right: 1px solid #FECC5B;
}
.nav ul li {
border-bottom: 1px solid #fecc5b;
}
.nav ul li a:hover {
background-color: #fff5da;
}
</style>
</head>
<body>
<ul class="nav">
<li>
<a href="#">微博</a>
<ul>
<li><a href="#">私信</a></li>
<li><a href="#">评论</a></li>
<li><a href="#">@我</a></li>
</ul>
</li>
<li>
<a href="#">博客</a>
<ul>
<li><a href="#">博客评论</a></li>
<li><a href="#">未读提醒</a></li>
</ul>
</li>
<li>
<a href="#">邮箱</a>
<ul>
<li><a href="#">免费邮箱</a></li>
<li><a href="#">VIP邮箱</a></li>
<li><a href="#">企业邮箱</a></li>
<li><a href="#">新浪邮箱客户端</a></li>
</ul>
</li>
<li>
<a href="#">网站导航</a>
<ul>
<li><a href="#">关于</a></li>
<li><a href="#">联系我们</a></li>
</ul>
</li>
</ul>
<script>
// 案例分析:
// 1.导航栏里面的li都要有鼠标经过效果,所以需要循环注册鼠标事件
// 2.核心原理:当鼠标经过li里面的第二个孩子 ul显示,当鼠标离开,则ul隐藏
// 1.获取元素
var nav = document.querySelector('.nav');
var lis = nav.children; // 得到4个小li
// 2.循环注册事件
for (var i = 0; i < lis.length; i++) {
lis[i].onmouseover = function () {
this.children[1].style.display = 'block';
}
lis[i].onmouseout = function () {
this.children[1].style.display = 'none';
}
}
</script>
</body>
</html>
效果如下
1.3.5 兄弟节点
1.node.nextSibling // 返回当前元素的下一个兄弟节点,找不到则返回null。同样,也是包含所有的节点
2.node.previousSibling // 返回当前元素上一个兄弟节点,找不到则返回null。同样,也是包含所有的节点
// 下面两个方法有兼容性问题,IE9以上才支持
3.node.nextElementSibling // 返回当前元素下一个兄弟节点,找不到则返回null
4.node.previousElementSibling // 返回当前元上一个兄弟节点,找不到则返回null
如何解决兼容性问题? —— 自己封装一个函数
新建.html文件,执行代码如下
<!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>
</head>
<body>
<div>我是div</div>
<span>我是span</span>
<script>
var div = document.querySelector('div');
console.log(div.nextSibling); // #text
console.log(div.previousSibling); // #text
// 下面两行代码需要ie9才能用
console.log(div.nextElementSibling); // span元素
console.log(div.previousElementSibling); // null 因为div的上面没有兄弟
// 如何解决兼容性问题
// 自己封装一个函数
function getNextElementSibling(element) {
var el = element;
while (el = el.nextSibling) {
if (el.nodeType == 1) {
return el;
}
}
return null;
}
</script>
</body>
</html>
效果如下
1.3.6 创建节点和添加节点
【提示】想要页面添加一个新的元素:1.创建元素;2.添加元素
document.createElement('tagName')
document.createElement()方法创建由 tagName指定的 HTML 元素。因为这些元素原先不存在,是根据我们的需求动态生成的,所以我们也称为动态创建元素节点。
node.appendChild(child)
node.appendChild() 方法将一个节点添加到指定父节点的子节点列表末尾。类似于css里面after伪元素。
node.insertBefore(child,指定元素)
node.insertBefore() 方法将一个节点添加到父节点前面。类似于css里面的before伪元素
新建.html文件,执行代码如下
<!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>
</head>
<body>
<ul>
<li>123</li>
</ul>
<!-- 需求:在上行ul里面动态创建一个li -->
<script>
// 1.创建元素节点
var li = document.createElement('li');
// 2.添加节点 node.appendChild(child) 其中node是父级,child是子级
// 如果有相同元素节点,它是在后面追加元素,类似于数组中的push
var ul = document.querySelector('ul');
ul.appendChild(li);
// 3.添加节点 node.insertBefore(child,指定元素);
var lili = document.createElement('li');
ul.insertBefore(lili,ul.children[0])
// 4.我们想要页面添加一个新的元素:1.创建元素;2.添加元素
</script>
</body>
</html>
效果如下
1.3.7 案例:发布留言
新建.html文件,执行代码如下
<!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>
</head>
<body>
<textarea name="" id="" cols="30" rows="10"></textarea>
<button>发布</button>
<ul></ul>
<script>
// 1.获取元素
var btn = document.querySelector('button');
var text = document.querySelector('textarea');
var ul = document.querySelector('ul');
// 2.注册事件
btn.onclick =function() {
if (text.value == '') {
alert('您没有输入内容');
return false;
} else {
// (1) 创建元素
var li = document.createElement('li');
// 先有li才能赋值
li.innerHTML = text.value;
// (2) 添加元素
// ul.appendChild(li);
ul.insertBefore(li,ul.children[0]) // 新创建的一个永远在第一个的前面
}
}
</script>
</body>
</html>
效果如下
1.3.8 删除节点
node.removeChild(child)
node.removeChild() 方法从DOM 中删除一个子节点,返回删除的节点。
新建.html文件,执行代码如下
<!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>
</head>
<body>
<button>删除</button>
<ul>
<li>熊大</li>
<li>熊二</li>
<li>光头强</li>
</ul>
<script>
// 需求1:删除熊大这一行
// 1.获取元素
var ul = document.querySelector('ul');
// 2.删除元素 node.removeChild(child)
// ul.removeChild(ul.children[0]);
// 需求2:通过点击删除按钮进行删除
// 3.点击按钮依次删除里面的孩子
var btn = document.querySelector('button');
btn.onclick = function() {
// 如果删除完了之后,按钮禁用
if (ul.children.length == 0) {
this.disabled = true;
} else {
ul.removeChild(ul.children[0])
}
}
</script>
</body>
</html>
效果如下
1.3.9 案例:删除留言
案例分析:
1.当我们把文本域里面的值赋值给li的时候,多添加一个删除的链接
2.需要把所有的链接获取过来,当我们点击当前的链接的时候,删除当前链接所在的li
3.阻止链接跳转需要添加javascript:void(0);或者 javascript:;
新建.html文件,执行代码如下
<!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>
ul {
margin-top: 50px;
}
li {
width: 300px;
padding: 5px;
background-color: rgb(189, 196, 193);
color: red;
font-size: 14px;
margin: 15px 0;
}
li a {
float: right;
}
</style>
</head>
<body>
<textarea name="" id="" cols="30" rows="10"></textarea>
<button>发布</button>
<ul></ul>
<script>
// 1.获取元素
var btn = document.querySelector('button');
var text = document.querySelector('textarea');
var ul = document.querySelector('ul');
// 2.注册事件
btn.onclick =function() {
if (text.value == '') {
alert('您没有输入内容');
return false;
} else {
// (1) 创建元素
var li = document.createElement('li');
// 先有li才能赋值
li.innerHTML = text.value + "<a href='javascript:;'>删除</a>";
// (2) 添加元素
// ul.appendChild(li);
ul.insertBefore(li,ul.children[0]) // 新创建的一个永远在第一个的前面
// (3) 删除元素 删除的是当前链接的li 它的父亲
// 首先要获取所有的链接
var as = document.querySelectorAll('a');
// 利用遍历,给所有的a添加事件,一点击就删除
for (var i = 0; i < as.length; i++) {
as[i].onclick = function() {
// node.removeChild(child); 删除的是li 是当前a所在的li this.parentNode;
ul.removeChild(this.parentNode);
}
}
}
}
</script>
</body>
</html>
效果如下
1.3.10 复制节点(克隆节点)
node.cloneNode()
node.cloneNode() 方法返回调用该方法的节点的一个副本。也称为克隆节点/拷贝节点
注意:(节点包括元素节点,文本节点,属性节点等)
1.如果括号里面为空或者为false,则是浅拷贝,即只克隆复制节点本身,不克隆里面的子节点。
2.如果括号参数为true,则是深度拷贝,会复制节点本身以及里面所有的子节点。
新建.html文件,执行代码如下
<!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>
</head>
<body>
<!-- ul>li*3 -->
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<script>
// 需求1:把第一个li复制一份
var ul = document.querySelector('ul');
// 1.node.cloneNode(); 括号为空或者里面是false,浅拷贝 只复制标签不复制里面的内容
// 2.node.cloneNode(true); 括号为true,深拷贝 只复制标签而且复制里面的内容
var lili = ul.children[0].cloneNode();
// 添加 浅拷贝
ul.appendChild(lili);
var second = ul.children[1].cloneNode(true);
// 添加 深拷贝
ul.appendChild(second);
</script>
</body>
</html>
效果如下
1.4 案例:动态生成表格
表格根据数据的多少相应的发生变化
新建.html文件,执行代码如下
<!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>
table {
width: 500px;
margin: 100px auto;
border-collapse: collapse;
text-align: center;
}
td,th {
border: 1px solid #333;
}
thead tr {
height: 40px;
background-color: #ccc;
}
</style>
</head>
<body>
<table cellspacing="0">
<thead>
<tr>
<th>姓名</th>
<th>科目</th>
<th>成绩</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<!-- 需求:动态生成行和列,添加数据 -->
</tbody>
</table>
<script>
// 1.准备数据
// 数组里面可以存放多个对象
var datas = [{
name:'张三',
subject:'语文',
score:90
},{
name:'李四',
subject:'数学',
score:100
},{
name:'王五',
subject:'英语',
score:80
},{
name:'赵六',
subject:'地理',
score:88
}];
// 2.往tbody里面创建行 里面有几个人(通过数组的长度可以得到),我们就创建几行
var tbody = document.querySelector('tbody');
for (var i = 0; i < datas.length; i++) { // 外面的for循环管行 tr
// 1.创建 tr 行
var tr = document.createElement('tr');
tbody.appendChild(tr);
// 2.行里面创建单元格(跟数据有关系的三个单元格) td 单元格的数量取决于每个对象里面的属性个数 可以用for循环遍历对象得到data[i]
// for ...in 语句用于对数组或者对象的属性进行循环操作。 详情见对象专栏
for (var k in datas[i]) { // 里面的for循环管列 td
// 创建单元格
var td = document.createElement('td');
// 把对象里面的属性值 datas[i][k] 给 td
console.log(datas[i][k]);
td.innerHTML = datas[i][k];
// 添加到行里面
tr.appendChild(td);
}
// 3.创建有删除2个字的单元格
var td = document.createElement('td');
td.innerHTML = '<a href="javascript:;">删除</a>';
tr.appendChild(td);
}
// 4. 删除操作 开始
var as = document.querySelectorAll('a');
for (var i = 0; i < as.length; i++) {
as[i].onclick = function() {
// 点击a 删除当前a的所在的行(链接的爸爸的爸爸),因为链接的爸爸是单元格td,使用node.removeChild(child)
tbody.removeChild(this.parentNode.parentNode)
}
}
/* for (var k in obj) {
k 得到的是属性名
obj[k] 得到的是属性值
} */
</script>
</body>
</html>
效果如下
1.5 三种动态创建元素的区别
三种创建元素方式区别 | 作用 |
---|---|
1.document.write() | 创建元素 |
2.innerHTML | 创建元素 |
3.document.createElement() | 创建元素 |
举例代码:
<!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>
</head>
<body>
<button>点击</button>
<p>abc</p>
<div class="inner"></div>
<div class="create"></div>
<script>
// 三种创建元素方式区别
// 1.document.write() 创建元素 如果页面文档流加载完毕,再调用这句话会导致页面重绘
var btn = document.querySelector('button');
btn.onclick = function() {
document.write('<div>123</div>');
}
// 需求:点击button按钮后,添加新元素。结果用write()之后,一点击按钮,会出现页面重绘的现象,即之前的格式
// 都没有了,它会用<div>123</div>新建一个页面。
// 类似于
/* window.onload = function() {
document.write('<div>123</div>')
} 当整个页面加载完,再去写<div>123</div>这个标签 */
// 需求:想在<div class="inner"></div>和<div class="create"></div>里面创建a链接
// 2.innerHTML 创建元素
var inner = document.querySelector('.inner');
inner.innerHTML = '<a href="#">百度</a>';
// 创建多个时
for (var i = 0;i <= 100;i++) {
inner.innerHTML += '<a href="#">百度</a>'
}
// 优化
var arr = [];
for (var i = 0; i <= 100;i++) {
arr.push('<a href="#">百度</a>');
}
// 3.document.createElement() 创建元素
var create = document.querySelector('.create');
var a = document.createElement('a');
create.appendChild(a);
// 创建多个时
for (var i = 0; i <= 100; i++) {
var a = document.createElement('a');
create.appendChild(a);
}
</script>
</body>
</html>
总结: