# 节点层级
## 父子节点
从DOM文档树中可以看出,DOM的节点之间存在着关联关系,我们把从上而下的关联关系叫做 "父子节点 ",把同一层级(平级)的关系叫做 "兄弟节点"。在实际开发中,我们经常通过操作父子节点来完成相应的页面效果。节点层级关系通过节点属性体现,如 parentNode(),childNodes(所有节点,包括换行),children(HTML元素节点)等,以下介绍几种常用的节点属性
html和css代码
~~~
<div id="main">
<span>我是span标签文本</span>
<p>我是p标签文本</p>
<!--我是注释-->
</div>
~~~
JavaScript代码
~~~
var main = document.getElementById('main');
console.dir(main);
// 1、节点中这么多属性,我么要学习哪些属性呢?
// parentNode 父节点
// childNodes 子节点
//获取父节点
console.log(main.parentNode);
//节点类型 (nodeType) ,如下
// 元素节点 1 属性节点 2 文本节点 3 注释节点 8
// 2、获取所有子节点
console.log(main.childNodes);
// 获取所有子元素节点
var nodes = main.childNodes;
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i];
//获取所有元素节点
if (node.nodeType === 1) {
console.log(node);
}
}
// 3、有没有其他更方便的方法获取到所有子元素节点?
console.log(main.children);//返回 一个Node的所有子元素节点,是一个动态的集(HTMLCollection)
~~~
**需求(熟悉)**
隔行变色
目的: 练习children, hasChildNodes()的使用
html和css代码
~~~
<div>
<ul id="list">
<li>西施</li>
<li>昭君</li>
<li>貂蝉</li>
<li>玉环</li>
<li>芙蓉姐姐</li>
</ul>
</div>
~~~
JavaScript代码
~~~
// 1、以前的方式
// var list = document.getElementsByTagName('li');
// 2、现在的方式
//通过子节点完成隔行变色,获取元素的子节点:
// 方式一:childNodes 动态集合
// 方式二:children 动态集合
var list = document.getElementById('list');
// 判断是否有子节点(hasChildNodes()),遍历子元素节点,隔行变色
if (list.hasChildNodes()) {
for (var i = 0; i < list.children.length; i++) {
var li = list.children[i];
if (i % 2 === 0) {//奇数行
li.style.backgroundColor='lightblue';
}else { //偶数行
li.style.backgroundColor='lightyellow'
}
}
}
~~~
## **第一个和最后一个子节点(掌握)**
获取第一个和最后一个子节点:
**firstChild 第一个子节点**
**lastChild 最后一个子节点**
获取第一个和最后一个子元素节点: firstElementChild:有浏览器兼容性问题,从IE9开始支持 lastElementChild:有浏览器兼容性问题,从IE9开始支持
html和css代码
~~~
<div id="main">
<div>这是一个广告图片</div>
<ul>
<li>这是一个列表</li>
</ul>
<span>说明性文字</span>
</div>
~~~
JavaScript代码
~~~
//获取第一个和最后一个子节点
//firstChild 第一个子节点
//lastChild 最后一个子节点
var main = document.getElementById('main');
console.log(main.firstChild);
//获取第一个和最后一个子元素节点
//firstElementChild:有浏览器兼容性问题,从IE9开始支持
//lastElementChild:有浏览器兼容性问题,从IE9开始支持
console.log(main.firstElementChild);
console.log(main.lastElementChild);
var firstEle = getFirstElementChild(main);
console.log(firstEle);
~~~
**菜单**
目的: 练习parentNode,children,firstElementchild的使用,要理解"先清除后设置"的思想;
需求描述:
点击菜单时,先清除所有菜单的高亮效果,再设置当前点击的菜单为高亮显示。
需求分析:
(1)遍历li, 给li中的第一个a标签注册点击事件;
(2)先清除所有菜单的高亮效果, 再设置当前点击的菜单为高亮显示;
html和css代码
~~~
<style>
#menu ul li {
list-style-type: none;
width: 80px;
height: 30px;
line-height: 30px;
background-color: #595b5d;
text-align: center;
float: left;
margin-left: 5px;
}
#menu ul li.current {
background-color: thistle;
}
#menu ul li a {
text-decoration: none;
color: aliceblue;
}
#menu ul li a:hover {
color: red;
}
/*#menu ul li:hover {*/
/*background-color: aliceblue;*/
/*}*/
</style>
<script src="common.js"></script>
<div id="menu">
<ul>
<li class="current"><a href="javascript:void(0)">首页</a></li>
<li><a href="javascript: undefined">叩丁狼</a></li>
<li><a href="javascript:void(0)">博客</a></li>
<li><a href="javascript:void(0)">相册</a></li>
<li><a href="javascript:void(0)">关于</a></li>
<li><a href="javascript:void(0)">帮助</a></li>
</ul>
</div>
~~~
JavaScript代码
~~~
var menu = my$('menu');
//firstChild:获取第一个节点
//firstElementChild:获取第一个元素节点
var ul = getFirstElementChild(menu);
//遍历li中的a标签,并注册点击事件
for (var i = 0; i < ul.children.length; i++) {
var li = ul.children[i];
//获取a元素节点
var link = getFirstElementChild(li);
link.onclick = linkClick;
}
function linkClick() {
//取消所有li的高亮显示
for (var i = 0; i < ul.children.length; i++) {
var li = ul.children[i];
li.className='';
}
//设置当前点击的li标签为高亮显示
this.parentNode.className = 'current';
}
~~~
**小结**
>[info] 1、认识 javascript:void(0)
> https: 协议
> javascript: 伪协议
> void(0): 运算符,对给定的表达式进行求值,始终返回 undefined。当返回undefined时,a标签不会做任何事情
> 2、获取子元素节点、父元素节点、第一个元素节点(解决浏览器兼容性问题)
<br>
## 兄弟节点(掌握)
通过DOM文档树结构中上下节点是父子关系,水平(同级)节点是兄弟关系,那怎么获取一个节点的兄弟节点呢? nextSibling 获取下一个兄弟节点 nextElementSibling 获取下一个兄弟元素节点 previousSibling 获取上一个兄弟节点 previousElementSibling 获取上一个兄弟元素节点 通过以下代码了解获取兄弟节点的属性,以及解决该属性的浏览器兼容性问题。
html和css代码
~~~
<div id="main">
<div>这是一个区域1</div>
<div>这是一个区域2</div>
<div id="c3">这是一个区域3</div>
<div>这是一个区域4</div>
<div>这是一个区域5</div>
</div>
~~~
JavaScript代码
~~~
var c3 = document.getElementById('c3');
//获取兄弟节点
console.log(c3.nextSibling);//获取上一个兄弟节点
console.log(c3.nextElementSibling);//获取上一个兄弟元素节点
console.log(c3.previousSibling);//获取下一个兄弟节点
console.log(c3.previousElementSibling);//获取下一个兄弟元素节点
var nextEle = getNextElementSibling(c3);
console.log(nextEle);
//解决浏览器对nextElementSibling的兼容性问题
function getNextElementSibling(element) {
var el = element;
while (el = el.nextSibling) {
if (el.nodeType === 1) {
return el;
}
}
return null;
}
//同理,用同样的方式解决浏览器对previousElementSibling的兼容性问题
~~~
**小结**
>[info] 1. childNodes和children的区别,childNodes获取的是所有的子节点,children获取的是子元素节点
> 2. nextSibling和previousSibling获取的是兄弟节点,nextElementSibling和previousElementSibling获取的是兄弟元素节点
> 3. nextElementSibling和previousElementSibling有兼容性问题,IE9以后才支持
# **创建元素的三种方式**
**document.write()**:将一个文本字符串写入到由 document.open() 打开的一个文档流中;
**innerHTML**:设置或获取HTML语法表示的元素的后代;
**document.createElement( tagName )**:创建由 tagName 指定的HTML元素;
### document.write()(熟悉)
默认情况之下,页面由上而下地加载,形成一个文档流,当执行完毕时,文档流就会关闭。当使用documen.write()创建元素时,实际是开启一了个新的文档流,而将之前文档流冲刷掉,所以不推荐使用这种方法。
html和css代码
~~~
<input type="button" id="btn" value="document.write()"/><br><br>
<a id="ibangkf" href="http://www.ibangkf.com">网站客服</a>
<div>
<span>我是span标签</span>
<b>我是b标签</b>
</div>
~~~
JavaScript代码
~~~
my$('btnSet').onclick=function () {
document.write('<p>我是p标签</p>');
}
~~~
### innerHTML(掌握)
标签中会插入一个p标签,并在在页面上输出"新标签",当需要添加的标签比较多的时候使用这种方式。
html和css代码
~~~
<input type="button" id="btn" value="innerHTML"/>
<input type="button" id="btnSet" value="生成四大美女"/>
<div id="main">
<span>我是span标签</span>
</div>
~~~
JavaScript代码
~~~
//点击按钮时,将li标签加入到ul标签中,生成美女列表
var array = ['西施', '昭君', '貂蝉', '玉环'];
var main = my$('main');
my$('btnSet').onclick = function () {
main.innerHTML = '<ul>';
for (var i = 0; i < array.length; i++) {
main.innerHTML += '<li>' + array[i] + '</li>';
}
main.innerHTML += '</ul>';
}
//问题:每次给innerHTML赋值时,浏览器就会重新绘制DOM树的结构,导致性能降低。
~~~
<br>
## innerHTML性能问题(熟悉)
innerHTML方法由于会对字符串进行解析,需要避免在循环内多次使用。可以借助字符串或数组的方式进行替换,再设置给innerHTML,优化后与document.createElement性能相近。
优化一:使用字符串方式
~~~
var array = ['西施', '昭君', '貂蝉', '玉环'];
var main = my$('main');
//优化一:使用字符串方式
my$('btnSet').onclick = function () {
var str += '<ul>';
for (var i = 0; i < array.length; i++) {
str += '<li>' + array[i] + '</li>';
}
main.innerHTML = str + '</ul>';
}
~~~
优化二:使用数组方式
~~~
//优化二:使用数组方式
var array = ['西施', '昭君', '貂蝉', '玉环'];
var main = my$('main');
my$('btnSet').onclick = function () {
var strArray = [];
strArray.push('<ul>');
for (var i = 0; i < array.length; i++) {
strArray.push('<li>' + array[i] + '</li>');
}
strArray.push('<ul>');
main.innerHTML = strArray.join('');
}
~~~
## document.createElement()(掌握)
在内存中创建一个标签所对应的DOM对象,当需要动态创建结构比较复杂的标签时, 可使用这种方式。
html和css代码
~~~
<input type="button" id="btn1" value="生成四大美女"/><br><br>
<div id="main">
<span>我是span标签</span>
</div>
~~~
JavaScript代码
~~~
// 方式三:document.createElement()
//在内存中创建一个p标签的DOM对象
my$('btnSet').onclick=function () {
var p = document.createElement('p');
p.innerText='我是p标签';
p.style.backgroundColor='red';
//将p标签设置到div中,appendChild方法:将子元素设置到当前元素(父元素)的子元素列表中的最后一个位置
my$('main').appendChild(p);
}
~~~
# 节点操作方法介绍
类似于对象属性的增删改查,节点中也经常对节点属性进行增删改查,这需要用到节点方法,以下介绍常用的节点方法。
createElement(tagName) 创建指定名称tagName的HTML元素; appendChild(childNode) 将指定的 childNode 参数作为最后一个子节点添加到当前节点,返回 childNode。如果参数引用了 DOM 树上的现有节点,则节点将从当前位置分离,并附加到新位置;
parentNode.insertBefore(newNode, oldSubNode) 在当前节点之前插入子节点。如果给定的子节点已存在当前文档中,则insertBefore()会将其从当前位置移动到新位置;
removeChild(child) 从当前节点中删除指定的子节点child,并返回被删除的子节点; replaceChild(newChild, oldChild) 在当前节点中,用 newChild 替换 oldChild 并返回被替换掉的 oldChild;
以上节点操作相关的方法,通过下面的需求来练习、熟悉。
# 需求
## 动态创建列表(熟悉)
需求:当点击按钮时, 创建美女列表,当鼠标经过列表时,高亮显示 ; 目的: 练习createElement(), appendChild()方法的使用; 需求分析: (1) 当点击按钮时, 使用createElement(),动态生成li ; (2) 动态生成li时, 给每个li注册鼠标移入移出事件,在事件处理函数中设置li的样式 ;
html和css代码
~~~
<input type="button" id="btnSet" value="生成美女列表"/>
<div id="box">
</div>
~~~
JavaScript代码
~~~
//动态创建列表,当鼠标经过列表时,高亮显示
var array = ['西施', '昭君', '貂蝉', '玉环'];
my$('btnSet').onclick = function () {
var box = my$('box');
var ul = document.createElement('ul');
for (var i = 0; i < array.length; i++) {
var li = document.createElement('li');
li.innerHTML = array[i];
//注册鼠标经过事件
li.onmouseover = fn1;
li.onmouseout = fn2;
//将li添加到ul中
ul.appendChild(li);
}
//将ul添加到div中
box.appendChild(ul);
}
//获取页面中所有的li元素
var list = document.getElementsByTagName('li');
function fn1() {
//设置当前li的背景高亮样式
this.style.backgroundColor = 'lightblue';
}
function fn2() {
//清空当前li的背景高亮样式
this.style.backgroundColor = '';
}
~~~
### 动态创建表格(作业)
需求:根据表格数据动态创建表格
1、创建表格 table 设置表格样式:边框,边框间距, 宽高等; 2、创建表头 thead 将表头加入到表格中,设置表头的高,背景颜色;
3、创建表体 tbody (1)创建datas数组对象,用于模拟表格数据; (2)创建行,创建行中的列,并给行中的每列设置值;将每列加入到行中,将每行加入到tbody中;
4、在创建行时, 要在每行的最后一列设置a标签, 用于删除操作, 当点击a标签时, 删除当前行;
注意:此案例代码虽然多,但逻辑简单,大家只要清楚表格的结构,就很容易写出相应的代码。
html和css代码
~~~
<style>
#box{
width: 500px;
margin: 0 auto;
}
</style>
<div id="box"></div>
~~~
JavaScript代码
~~~
// 表体数据
var datas = [
{name: '刘备', subject: '语文', score: 80},
{name: '关羽', subject: '语文', score: 90},
{name: '张飞', subject: '语文', score: 50},
{name: '曹操', subject: '语文', score: 100},
{name: '诸葛亮', subject: '语文', score: 100},
]
// 表头数据
var array = ['姓名', '学科', '成绩','操作'];
// 获取box盒子
var oDiv = document.getElementById('box');
// 创建table并注入到oDiv中
var tableTag = document.createElement('table');
tableTag.style.width = '100%';
tableTag.style.borderCollapse = 'collapse';
oDiv.appendChild(tableTag);
/*------------------------为table中写入(thead>tr>th*3)------------------------*/
// 创建thead
var theadTag = document.createElement('thead');
tableTag.appendChild(theadTag);
// 创建thead下的tr
var theadTrTag = document.createElement('tr');
theadTag.appendChild(theadTrTag);
// 根据array的个数,创建th
for(var i=0;i<array.length;i++){
var thTag = document.createElement('th');
thTag.innerHTML = array[i];
thTag.style.border = '1px solid #000';
theadTrTag.appendChild(thTag);
}
/*------------------------为table中写入(tbody>tr>td*3)------------------------*/
// 创建tbody
var tbodyTag = document.createElement('tbody');
tableTag.appendChild(tbodyTag);
// 根据datas的个数,创建tbody下的tr
for(var i=0;i<datas.length;i++){
var myData = datas[i]; // 保存数组中每个项
// 创建tbody下的tr
var tbodyTrTag = document.createElement('tr');
tbodyTag.appendChild(tbodyTrTag);
for(key in myData){
var tdTag = document.createElement('td');
tdTag.innerHTML = myData[key];
tdTag.style.textAlign = 'center';
tdTag.style.border = '1px solid #000';
tbodyTrTag.appendChild(tdTag);
}
// 添加最后一个删除按钮
var tdTag1 = document.createElement('td');
var aTag = document.createElement('a');
aTag.href = 'javascript:void(0);';
aTag.innerHTML = '删除';
tdTag1.appendChild(aTag);
tdTag1.style.textAlign = 'center';
tdTag1.style.border = '1px solid #000';
tbodyTrTag.appendChild(tdTag1);
// 点击了删除按钮
aTag.onclick = deleteFn;
}
// 删除行
function deleteFn(){
var trTag = this.parentNode.parentNode;
tbodyTag.removeChild(trTag);
}
~~~
## 常用DOM方法小结
>[info] createElement()、appendChild()、removeChild()、insertBefore()、replaceChild(),见**节点操作**一节。
>
> 1、var insertedNode = parentNode.insertBefore(newNode, referenceNode);
> 在当前节点中将newNode插入referenceNode之前。如果给定的子节点已存在当前文档中,则insertBefore()会将其从当前位置移动到新位置;
>
> 2、var replacedNode = parentNode.replaceChild(newChild, oldChild);
> 在当前节点中,用 newChild 替换 oldChild 并返回被替换掉的 oldChild;
>
> 3、var childNode = parentNode.appendChild(childNode);
> 将指定的 childNode 参数作为最后一个子节点添加到当前节点,返回 childNode。如果参数引用了 DOM 树上的现有节点,则节点将从当前位置分离,并附加到新位置;
>
## 选择水果(掌握)
需求:向左移动全部水果,向左移动选中的水果;向右移动全部水果,向右移动选中的水果;
全部向右移动:获取左边所有选项,并加入到右边的select中。
html和css代码
~~~
<style>
select {
width: 200px;
height: 200px;
background-color: lightblue;
font-size: 20px;
}
</style>
<select id="leftSelect" multiple="multiple">
<option>苹果</option>
<option>橘子</option>
<option>梨</option>
<option>西瓜</option>
<option>水蜜桃</option>
</select>
<input type="button" value=">>" id="btn1">
<input type="button" value="<<" id="btn2">
<input type="button" value=">" id="btn3">
<input type="button" value="<" id="btn4">
<select id="rightSelect" multiple="multiple">
</select>
<script src="common.js"></script>
~~~
JavaScript代码
需求分析:
(1)全部向右移动,全部向左移动;
(2)向右移动选中项,向左移动选中项;
~~~
var leftSelect = my$('leftSelect');
var rightSelect = my$('rightSelect');
// ==========================全部向右移动============================
my$('btn1').onclick = function () {
//获取左边所有选项,并加入到右边的select中
for (var i = 0; i < leftSelect.children.length; i++) {
var option = leftSelect[i];
rightSelect.appendChild(option);
}
}
~~~
以上代码,正向遍历,存在问题:每次移除左侧选项之后,左侧数据索引都要重新排序
~~~
//逆向遍历
my$('btn1').onclick = function () {
for (var i = leftSelect.children.length-1; i >=0; i--) {
var option = leftSelect[i];
rightSelect.appendChild(option);
}
}
~~~
以上代码,逆向遍历,存在问题:选项顺序颠倒
~~~
my$('btn1').onclick = function () {
for (var i = 0; i < leftSelect.children.length; i++) {
var option = leftSelect[0];
rightSelect.appendChild(option);
}
}
~~~
以上代码,只选择左侧下标为0的项,存在问题:i 的值和元素长度是动态变化的,导致i的值不满足循环条件。
(1)全部向右移动,全部向左移动。 由于select标签中的option的个数是动态变化的, 最后导致不满足循环条件了, 针对这个为题, 我们可以先将select中option的个数存储起来, 将该值当做循环中 i 的范围, 即var len =leftSelect.children.length, for循环中 i< len ;
参考代码如下
~~~
// ==========================全部向右移动============================
my$('btn1').onclick = function () {
var len = leftSelect.children.length;
for (var i = 0; i < len; i++) {
var option = leftSelect.children[0];
// 将左边的option追加到右边select中
rightSelect.appendChild(option);
option.selected = false;
}
}
// ==========================全部向左移动============================
my$('btn2').onclick = function () {
var len = rightSelect.children.length;
for (var i = 0; i < len; i++) {
var option = rightSelect.children[0];
leftSelect.appendChild(option);
option.selected = false;
}
}
~~~
(2)向右移动选中项,向左移动选中项。参考代码如下
需求分析:
a、获取所有选中的option,存储到数组中 b、遍历数组,将数组中的option加入到rightSelect中
~~~
// ==========================向右移动选中项============================
//将已选中的option存储到数组中
my$('btn3').onclick = function () {
var array = [];
var len = leftSelect.children.length;
for (var i = 0; i < len; i++) {
var option = leftSelect[i];
if (option.selected) {
//将已选中的option加入到数组中
array.push(option);
//取消option的选中状态
option.selected = false;
}
}
//将数组中(即已选中的option)的option移动到rightSelect中
for (var i = 0; i < array.length; i++) {
var option = array[i];
rightSelect.appendChild(option);
}
}
// ==========================向左移动选中项============================
my$('btn4').onclick = function () {
var array = [];
var len = rightSelect.children.length;
for (var i = 0; i < len; i++) {
var option = rightSelect[i];
if (option.selected) {
//将已选中的option加入到数组中
array.push(option);
//取消option的选中状态
option.selected = false;
}
}
//将数组中(即已选中的option)的option移动到rightSelect中
for (var i = 0; i < array.length; i++) {
var option = array[i];
leftSelect.appendChild(option);
}
}
~~~
## innerHTML实现选择水果(了解)
使用innerHTML实现选择水果的功能,存在以下的问题。
~~~
//使用innerHTML实现
rightSelect.innerHTML = leftSelect.innerHTML;
//清空做左边的选项
leftSelect.innerHTML = '';
//存在的问题
// 1、如果子元素中注册了事件,则在移动子元素之后,子元素中的事件丢失。
// 2、使用leftSelect.innerHTML = '' 清空子元素之后,如果子元素中注册了事件,则事件仍然存在内存中,即此时发生内存泄漏。
~~~
## DOM对象总结
~~~
================================= 知识点总结 =================================
~~~