javascript学习笔记:DOM节点关系和操作

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了javascript学习笔记:DOM节点关系和操作相关的知识,希望对你有一定的参考价值。

0x01:前面的话

DOM可以将任何html描绘成一个由多层节点构成的结构。节点分为12种不同类型,每种类型分别表示文档中不同的信息及标记。每个节点都拥有各自的特点、数据和方法,也与其他节点存在某种关系。节点之间的关系构成了层次,而所有页面标记则表现为一个以特定节点为根节点的树形结构。本文将详细描述DOM间的节点关系和基础的DOM操作。
技术分享图片
节点中的各种关系可以用传统的家族关系来描述,相当于把文档树比喻成家谱。接下来,将把DOM节点关系分为属性和方法两部分进行详细说明。

0x02:属性

父级属性
parentNode

  每个节点都有一个parentNode属性,该属性指向文档树中的父节点。对于一个节点来说,它的父节点只可能是三种类型:element节点、document节点和documentfragment节点。如果不存在,则返回null。

    <div id="myDiv"></div>
<script>
console.log(myDiv.parentNode);//body
console.log(document.body.parentNode);//html
console.log(document.documentElement.parentNode);//document
console.log(document.parentNode);//null
</script>
<div id="myDiv"></div>
<script>
var myDiv = document.getElementById(‘myDiv‘);
console.log(myDiv.parentNode);//body
var fragment = document.createDocumentFragment();
fragment.appendChild(myDiv);
console.log(myDiv.parentNode);//document-fragment
</script>

parentElement

  与parentNode属性不同的是,parentElement返回的是父元素节点
<div id="myDiv"></div>
<script>
console.log(myDiv.parentElement);//body
console.log(document.body.parentElement);//html
console.log(document.documentElement.parentElement);//null
console.log(document.parentElement);//null
</script>
[注意]在IE浏览器中,只有Element元素节点才有该属性,其他浏览器则是所有类型的节点都有该属性

<div id="test">123</div>
<script>
//IE浏览器返回undefined,其他浏览器返回<div id="test">123</div>
console.log(test.firstChild.parentElement);
//所有浏览器都返回<body>
console.log(test.parentElement);
</script>

子级属性
childNodes

  childNodes是一个只读的类数组对象NodeList对象,它保存着该节点的第一层子节点

<ul id="myUl"><li><div></div></li></ul>
<script>
var myUl = document.getElementById(‘myUl‘);
//结果是只包含一个li元素的类数组对象[li]
console.log(myUl.childNodes);
</script>

children

  children是一个只读的类数组对象HTMLCollection对象,但它保存的是该节点的第一层元素子节点

<div id="myDiv">123</div>
<script>
var myDiv = document.getElementById(‘myDiv‘);
//childNodes包含所有类型的节点,所以输出[text]
console.log(myDiv.childNodes);
//children只包含元素节点,所以输出[]
console.log(myDiv.children);
</script>

childElementCount

  返回子元素节点的个数,相当于children.length

  [注意]IE8-浏览器不支持

<`ul` id="myUl">
    <li></li>
    <li></li>
</ul>
<script>
var myUl = document.getElementById(‘myUl‘);
console.log(myUl.childNodes.length);//5,IE8-浏览器返回2,因为不包括空文本节点
console.log(myUl.children.length);//2
console.log(myUl.childElementCount);//2,IE8-浏览器返回undefined
</script>

firstChild

  第一个子节点

lastChild

  最后一个子节点

firstElementChild

  第一个元素子节点

lastElementChild

  最后一个元素子节点 

  上面四个属性,IE8-浏览器和标准浏览器的表现并不一致。IE8-浏览器不考虑空白文本节点,且不支持firstElementChild和lastElementChild

//ul标签和li标签之间有两个空白文本节点,所以按照标准来说,ul的子节点包括[空白文本节点、li元素节点、空白文本节点]。但在IE8-浏览器中,ul的子节点只包括[li元素节点]
<ul>
<li></li>
</ul>

<ul id="list">
    <li>1</li>
    <li>2</li>
    <li>3</li>
</ul>
<script>
console.log(list.firstChild);//标准浏览器中返回空白文本节点,IE8-浏览器中返回<li>1</li>
console.log(list.lastChild);//标准浏览器中返回空白文本节点,IE8-浏览器中返回<li>3</li>
console.log(list.firstElementChild);//标准浏览器中<li>1</li>,IE8-浏览器中返回undefined
console.log(list.lastElementChild);//标准浏览器中<li>3</li>,IE8-浏览器中返回undefined
</script>

同级属性
nextSibling

  后一个节点

previousSibling

  前一个节点

nextElementSibling

  后一个元素节点

previousElementSibling

  前一个元素节点

  与子级属性类似,上面四个属性,IE8-浏览器和标准浏览器的表现并不一致。IE8-浏览器不考虑空白文本节点,且不支持nextElementSibling和previousElementSibling

<ul>
    <li>1</li>
    <li id="myLi">2</li>
    <li>3</li>
</ul>
<script>
var myLi = document.getElementById(‘myLi‘);
console.log(myLi.nextSibling);//空白节点,IE8-浏览器返回<li>3</li>
console.log(myLi.nextElementSibling);//<li>3</li>,IE8-浏览器返回undefined
console.log(myLi.previousSibling);//空白节点,IE8-浏览器返回<li>1</li>
console.log(myLi.previousElementSibling);//<li>1</li>,IE8-浏览器返回undefined
</script>

方法

包含方法
hasChildNodes()

  hasChildNodes()方法在包含一个或多个子节点时返回true,比查询childNodes列表的length属性更简单

<div id="myDiv">123</div>
<script>
var myDiv = document.getElementById(‘myDiv‘);
console.log(myDiv.childNodes.length);//1
console.log(myDiv.hasChildNodes());//true
</script>

<div id="myDiv"></div>
<script>
var myDiv = document.getElementById(‘myDiv‘);
console.log(myDiv.childNodes.length);//0
console.log(myDiv.hasChildNodes());//false
</script>

contains()

  contains方法接受一个节点作为参数,返回一个布尔值,表示参数节点是否为当前节点的后代节点。参数为后代节点即可,不一定是第一层子节点 


<div id="myDiv">
    <ul id="myUl">
        <li id="myLi"></li>
        <li></li>
    </ul>
</div>
<script>
console.log(myDiv.contains(myLi));//true
console.log(myDiv.contains(myUl));//true
console.log(myDiv.contains(myDiv));//true
</script>

  [注意]IE和safari不支持document.contains()方法,只支持元素节点的contains()方法

//IE和safari报错,其他浏览器返回true
console.log(document.contains(document.body));

关系方法
compareDocumentPosition()

  compareDocumentPosition方法用于确定节点间的关系,返回一个表示该关系的位掩码

000000    0     两个节点相同
000001    1     两个节点不在同一个文档(即有一个节点不在当前文档)
000010    2     参数节点在当前节点的前面
000100    4     参数节点在当前节点的后面
001000    8     参数节点包含当前节点
010000    16    当前节点包含参数节点
100000    32    浏览器的私有用途
<div id="myDiv">
    <ul id="myUl">
        <li id="myLi1"></li>
        <li id="myLi2"></li>
    </ul>
</div>
<script>

//20=16+4,因为myUl节点被myDiv节点包含,也位于myDiv节点的后面
console.log(myDiv.compareDocumentPosition(myUl));

//10=8+2,因为myDiv节点包含myUl节点,也位于myUl节点的前面
console.log(myUl.compareDocumentPosition(myDiv));

//0,两个节点相同
console.log(myDiv.compareDocumentPosition(myDiv));

//4,myLi2在myLi1节点的后面
console.log(myLi1.compareDocumentPosition(myLi2));

//2,myLi1在myLi2节点的前面
console.log(myLi2.compareDocumentPosition(myLi1));
</script>

isSameNode()和isEqualNode()

  这两个方法都接受一个节点参数,并在传入节点与引用节点相同或相等时返回true

  所谓相同(same),指的是两个节点引用的是同一个对象

  所谓相等(equal),指的是两个节点是相同的类型,具有相等的属性(nodeName、nodeValue等等),而且它们的attributes和childNodes属性也相等(相同位置包含相同的值)

  [注意]firefox不支持isSameNode()方法,而IE8-浏览器两个方法都不支持

<script>
var div1 = document.createElement(‘div‘);
div1.setAttribute("title","test");
var div2 = document.createElement(‘div‘);
div2.setAttribute("title","test");
console.log(div1.isSameNode(div1));//true
console.log(div1.isEqualNode(div2));//true
console.log(div1.isSameNode(div2));//false
</script>

0x03:DOM节点操作

一般地,提起操作会想到“增删改查”这四个字,而DOM节点操作也类似地对应于此,接下来将详细介绍DOM的节点操作方法。
前提
  DOM提供节点操作的方法是因为DOM节点关系指针都是只读的

  下列代码中想通过修改myUl的父级节点来修改其节点关系,但由于parentNode属性是只读的,所以修改无效,在IE8-浏览器下甚至会报错

<div id="myDiv"></div>
<ul id="myUl">
    <li id="myli"></li>
</ul>
<script>
console.log(myUl.parentNode);//<body>
myUl.parentNode = myDiv;
//标准浏览器下,依然返回<body>;而IE8-浏览器则会报错
console.log(myUl.parentNode);
</script>

  DOM节点操作方法包括创建节点、插入节点、删除节点、替换节点、查看节点和复制节点。查看节点指的是查看节点之间的关系,在节点关系部分已经做过详细介绍,就不再赘述

创建节点
createElement()

  document.createElement()方法可以创建新元素。这个方法接受一个参数,即要创建元素的标签名,这个标签名在HTML文档中不区分大小写

var oDiv = document.createElement("div");
console.log(oDiv);//<div>

  IE8-浏览器可以为这个方法传入完整的元素标签,也可以包含属性

var oDiv = document.createElement(‘<div id="box"></div>‘);
console.log(oDiv.id);//‘box‘

  利用这种方法可以避开IE7-浏览器在动态创建元素的下列问题  

  1、不能设置动态创建的<iframe>元素的name特性

  2、不能通过表单的reset()方法重设动态创建的<input>元素

  3、动态创建的type特性值为"reset"的<button>元素重设不了表单

  4、动态创建的一批name相同的单选按钮彼此毫无关系。name值相同的一组单选按钮本来应该用于表示同一选项的不同值,但动态创建的一批这种单选按钮之间却没有这种关系

var iframe = document.createElement("<iframe name = ‘myframe‘></iframe>");
var input = document.createElement("<input type=‘checkbox‘>);
var button = document.createElement("<button type = ‘reset‘></button>");
var radio1 = document.createElement("<input type=‘radio‘ name =‘choice‘ value = ‘1‘>");
var radio2 = document.createElement("<input type=‘radio‘ name =‘choice‘ value = ‘2‘>");

  所有节点都有一个ownerDocument的属性,指向表示整个文档的文档节点document;在使用createElement()方法创建新元素的同时,也为新元素设置了ownerDocument属性

<div id="myDiv"></div>
<script>
console.log(myDiv.ownerDocument);//document
var newDiv = document.createElement(‘div‘);
console.log(newDiv.ownerDocument);//document
console.log(newDiv.ownerDocument === myDiv.ownerDocument);//true
</script>

插入节点
appendChild()

  appendChild()方法用于向childNodes列表的末尾添加一个节点,并返回新增节点。添加节点后,childNodes中的新增节点、父节点和以前的最后一个子节点的关系指针都会相应地得到更新

<div id="box"></div>
<script>
var oBox = document.getElementById(‘box‘);
var newNode = document.createElement(‘ul‘);
var returnedNode = oBox.appendChild(newNode);
console.log(returnedNode.nodeName);//UL
console.log(returnedNode == newNode);//true
console.log(returnedNode == oBox.lastChild);//true
</script>

  如果插入的节点已经是文档的一部分了,那结果就是将该节点从原来的位置转移到新位置

<body>
<div id="oldDiv">第一个div</div>
<div id="newDiv">第二个div</div>
<button id="btn">变换位置</button>
<script>
btn.onclick = function(){
    document.body.appendChild(newDiv);
}    
</script>
</body>

insertBefore()

  insertBefore()方法接收两个参数:要插入的节点和作为参照的节点。插入节点后,被插入的节点会变成参照节点的前一个兄弟节点(previousSibling),同时被方法返回。如果参照节点是null,则insertBefore()与appendChild()方法执行相同的操作。同样地,如果插入的节点已经是文档的一部分了,那结果就是将该节点从原来的位置转移到新位置

referenceNode.parentNode.insertBefore(newNode,referenceNode);

<ul id="myUl" style="border:1px solid black;">
    <li id="myLi">
        <div id=‘oldDiv‘>oldDiv</div>
    </li>    
</ul>
<button id="btn1">插入oldDiv的前面</button>
<button id="btn2">插入myUl的前面</button>
<button id="btn3">插到oldDiv的里面</button>
<script>
var oDiv = document.createElement(‘div‘);
oDiv.innerHTML = ‘newDiv‘;
btn1.onclick = function(){
    console.log(myLi.insertBefore(oDiv,oldDiv));//<div>newDiv</div>
}
btn2.onclick = function(){
    console.log(document.body.insertBefore(oDiv,myUl));//<div>newDiv</div>
}
btn3.onclick = function(){
    console.log(oldDiv.insertBefore(oDiv,null));//<div>newDiv</div>
}
</script>

【小效果】

<ul class="list" id="list">
    <li class="in">1</li>
    <li class="in">2</li>
    <li class="in">3</li>
    <li class="in">4</li>
    <li class="in">5</li>
    <li class="in">6</li>        
</ul>
<script>
var oList = document.getElementById(‘list‘);
//新增一个li元素
var oAdd = document.createElement(‘li‘);
//设置新增元素的css样式
oAdd.className = "in";
oAdd.style.cssText = ‘background-color:red;border-radius:50%‘;
//添加到oList中
oList.insertBefore(oAdd,null);
var num = -1;
var max = oList.children.length;
function incrementNumber(){
    num++;
    //oList.getElementsByTagName(‘li‘)[max]相当于null,所以不报错
    oList.insertBefore(oAdd,oList.getElementsByTagName(‘li‘)[num]);    
    if(num == max){
        num = -1;
    }    
    if(num == 0){
        num = 1;
    }
    setTimeout(incrementNumber,1000);
}
setTimeout(incrementNumber,1000);
</script>

insertAfter()

  由于不存在insertAfter()方法,如果要插在当前节点的某个子节点后面,可以用insertBefore()和appendChild()封装方法

function insertAfter(newElement,targetElement){
    var parent = targetElement.parentNode;
    if(parent.lastChild == targetElement){
        parent.appendChild(newElement);
    }else{
        parent.insertBefore(newElement,targetElement.nextSibling)
    }
}
<div id=‘oldDiv‘>old</div>
<button id="btn">增加节点</button>
<script>
function insertAfter(newElement,targetElement){
    var parent = targetElement.parentNode;
    if(parent.lastChild == targetElement){
       return parent.appendChild(newElement);
    }else{
       return parent.insertBefore(newElement,targetElement.nextSibling)
    }
}    
var newDiv = document.createElement(‘div‘);
newDiv.innerHTML = ‘new‘;
btn.onclick = function(){
    insertAfter(newDiv,oldDiv);
}
</script>

insertAdjacentHTML()

  insertAdjacentHTML()方法作为终级办法,相当于前面三个方法的综合。该方法接收两个参数:插入的位置和要插入的HTML文本

  第一个参数必须是下列值之一,且这些值都必须是小写形式:

  "beforebegin"   在当前元素之前插入一个紧邻的同级元素
  "afterbegin"   在当前元素之下插入一个新的子元素或在第一个子元素之前再插入新的子元素
  "beforeend" 在当前元素之下插入一个新的子元素或在最后一个子元素之后再插入新的子元素
  "afterend" 在当前元素之后插入一个紧邻的同级元素
  第二个参数是一个HTML字符串,如果浏览器无法解析字符串,就会抛出错误

  [注意]该方法无返回值

<div id=‘target‘ style="border: 1px solid black;">This is the element content</div>
<button>beforebegin</button>
<button>afterbegin</button>
<button>beforeend</button>
<button>afterend</button>
<script>
var btns = document.getElementsByTagName(‘button‘);
for(var i = 0 ; i < 4; i++){
    btns[i].onclick = function(){
        var that = this;
        target.insertAdjacentHTML(that.innerHTML,‘<span id="test">测试</span>‘)    
    }
}
</script>    

移除节点
removeChild()

  removeChild()方法接收一个参数,即要移除的节点,被移除的节点成为方法的返回值

<div id="myDiv">等待移除的节点</div>
<button id="btn">移除节点</button>
<script>
btn.onclick = function(){
    document.body.removeChild(myDiv);
}
</script>

  

remove()

  相比于removeChild(),remove()方法不太常见,但是却非常简单。该方法不用调用其父节点,直接在当前节点使用remove()方法就可以删除该节点,无返回值

  remove()方法常用于删除元素节点和文本节点,不可用于特性节点

  [注意]IE浏览器不支持该方法

<div id="test" title=‘div‘>123</div>
<script>
//文本节点
console.log(test.childNodes[0]);//‘123‘
test.childNodes[0].remove();
console.log(test.childNodes[0]);//undefined

//特性节点
console.log(test.attributes.title);//‘div‘
//报错,remove()方法无法用于删除特性节点
try{test.attributes[0].remove()}catch(e){
    console.log(‘error‘);
}
//元素节点
console.log(test);
test.remove();
</script>

替换节点
replaceChild()

  replaceChild()接收的两个参数是要插入的节点和要替换的节点,要替换的节点将由这个方法返回并从文档树中移除,同时由要插入的节点占据其位置

oldChild.parentNode.replaceChild(newChild, oldChild);

<div id="div1">1</div>
<div id="div2">2</div>
<div id="div3">3</div>
<button id="btn1">新增节点替换(4替换2)</button>
<button id="btn2">原有节点替换(3替换1)</button>
<script>
btn2.onclick = function(){
    document.body.replaceChild(div3,div1);
}
btn1.onclick = function(){
    var div4 = document.createElement(‘div‘);
    div4.innerHTML = ‘4‘;
    document.body.replaceChild(div4,div2);
}
</script>

复制节点
cloneNode()

  cloneNode方法用于克隆一个节点。它接受一个布尔值作为参数,表示是否执行深复制。在参数为true时,执行深复制,也就是复制节点及整个子节点树。在参数为false的情况下,执行浅复制,即复制节点本身。复制后返回的节点副本属于文档所有,但并没有为它指定父节点。若参数为空,也相当于false 

  [注意]cloneNode()方法不会复制添加到DOM节点中的javascript属性,例如事件处理程序等。这个方法只复制特性和子节点,其他一切都不会复制

<ul id="list">
    <li>1</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
    <li>5</li>
    <li>6</li>        
</ul>
<script>
var oList = document.getElementById(‘list‘);
oList.index = 0;

var deepList = oList.cloneNode(true);
//成功复制了子节点
console.log(deepList.children.length);//6
//但并没有复制属性
console.log(deepList.index);//undefined
var shallowList = oList.cloneNode();
//浅复制不复制子节点
console.log(shallowList.children.length);//0
</script>

以上是关于javascript学习笔记:DOM节点关系和操作的主要内容,如果未能解决你的问题,请参考以下文章

JavaScript学习笔记系列1:Dom操作

高性能JavaScriptの笔记-- DOM操作

DOM (JavaScript学习笔记)

JavaScript学习笔记7 之DOM文档对象模型

原生JavaScript第四篇

Javascript-DOM笔记