DOM操作

作者: lyp82nkl | 来源:发表于2019-06-16 02:06 被阅读0次

什么是DOM

D(document)O(object)M(model) 文档对象模型。

MDN的解释:
DOM(文档对象模型)是HTML和XML文档的编程接口。它提供了对文档的结构化的表述,并定义一种方式可以使从程序中对该结构进行访问,从而改变文档的结构、样式和内容。DOM将文档解析为一个由节点和对象(包含属性和方法的对象)组成的结构集合。

  • DOM中的O指的是Object,是在内存中,按照树型结构,通过构造函数(如Node,Element(翻译为标签比较
    好),Document三个构造函数),构造出对象,将 DOM 展现到内存中

  • DOM的D指的是Document,可以认为是 HTML 文档

  • DOM的M指的是Model,HTML结构在内存中,用一个模型来表示,这个模型就是树型结构

  • DOM就是一种想象的树形结构,它的作用是将网页转为一个 JavaScript 对象,从而可以用脚本进行各种操作(比如增删内容)。


  • html 是特殊的 Element,单独有一个对象叫做Docment ,html节点又叫根节点,可以通过输入document.documentElement来获得html节点。

node

  • DOM 的最小组成单位叫做节点(node)。文档的树形结构(DOM 树),就是由各种不同类型的节点组成。
  • html 对应的构造者是 Docment , Docment造出了 html 这个对象。


  • Node 是 Docment Element Text 原型链的顶端
  • 页面中的节点,通过Docment Element Text Coment 这四个构造函数构造出对应的对象这,就是DOM 的主要功能。

节点类型(nodeType)

Element类型(标签节点)

(1)nodeType为1

(2)nodeName为元素标签名,tagName也是返回标签名

(3)nodeValue为null

(4)parentNode可能是Document或Element

(5)子节点可能是Element,Text,Comment,Processing_Instruction,CDATASection或EntityReference

Text类型(文本节点)

纯文本内容,不能包含html代码

(1)nodeType为3

(2)nodeName为#text

(3)nodeValue为文本内容

(4)parentNode是一个Element

(5)没有子节点

Attr类型(属性节点)

(1)nodeType值为2

(2)nodeName是特性的名称

(3)nodeValue是特性的值

(4)parentNode为null

Comment类型

Comment表示HTML文档中的注释,它有下面的几种特征:
(1)nodeType为8
(2)nodeName为#comment
(3)nodeValue为注释的内容
(4)parentNode可能是Document或Element
(5)没有子节点

Document

Document表示文档,在浏览器中,document对象是HTMLDocument的一个实例,表示整个页面,它同时也是window对象的一个属性。Document有下面的特性:
(1)nodeType为9
(2)nodeName为#document
(3)nodeValue为null
(4)parentNode为null
(5)子节点可能是一个DocumentType或Element

  • 检测一个元素是文字还是标签的方法:var isElement = div.nodeType === 1

  • 文本标签的tagName是undefined

  • tagName会返回一个大写的标签,只有一个特例 svg 是小写

节点创建型api
createElement(创建标签节点)
vardiv = document.createElement("div");

通过createElement创建的元素并不属于html文档,它只是创建出来,并未添加到html文档中,要调用appendChild或insertBefore等方法配合使用将其添加到HTML中。

createTextNode(创建文本节点)

var textNode = document.createTextNode("一个TextNode");

createTextNode接收一个参数,这个参数就是文本节点中的文本,和createElement一样,创建后的文本节点也只是独立的一个节点,同样需要appendChild将其添加到HTML文档树中

cloneNode

cloneNode是用来返回调用方法的节点的一个副本,它接收一个布尔值参数,用来表示是否复制子元素,使用如下:

var parent = document.getElementById("parentElement"); 
var parent2 = parent.cloneNode(true);// 传入true
parent2.id = "parent2";

这段代码通过cloneNode复制了一份parent元素,其中cloneNode的参数为true,表示parent的子节点也被复制,如果传入false,则表示只复制了parent节点。
我们看看这个例子

<div id="parent">
    我是父元素的文本
    <br/>
    <span>
        我是子元素
    </span>
</div>
<button id="btnCopy">复制</button>

var parent = document.getElementById("parent");
document.getElementById("btnCopy").onclick = function(){
    var parent2 = parent.cloneNode(true);
    parent2.id = "parent2";
    document.body.appendChild(parent2);
}

这段代码很简单,主要是绑定button事件,事件内容是复制了一个parent,修改其id,然后添加到文档中。
这里有几点要注意:
(1)和createElement一样,cloneNode创建的节点同样要调用appendChild方法才能添加到文档树中
(2)如果复制的元素有id,则其副本同样会包含该id,由于id具有唯一性,所以在复制节点后必须要修改其id
(3)调用接收的布尔值参数最好传入,如果不传入该参数,不同浏览器对其默认值的处理可能不同

除此之外,我们还有一个需要注意的点:
如果被复制的节点绑定了事件,则副本也会跟着绑定该事件吗?这里要分情况讨论:
(1)如果是通过addEventListener或者比如onclick进行绑定事件,则副本节点不会绑定该事件
(2)如果是内联方式绑定比如

<div onclick="showParent()"></div>

这样的话,副本节点同样会触发事件。

页面修改型API
appendChild

appendChild我们在前面已经用到多次,就是将指定的节点添加到调用该方法的节点的子元素的末尾。调用方法如下:

parent.appendChild(child);

child节点将会作为parent节点的最后一个子节点。

appendChild这个方法很简单,但是还有有一点需要注意:如果被添加的节点是一个页面中存在的节点,则执行后这个节点将会添加到指定位置,其原本所在的位置将移除该节点,也就是说不会同时存在两个该节点在页面上,相当于把这个节点移动到另一个地方。我们来看例子

<div id="child">
    要被添加的节点
</div>
<br/>
<br/>
<br/>
<div id="parent">
    要移动的位置
</div>      
<input id="btnMove" type="button" value="移动节点" />

document.getElementById("btnMove").onclick = function(){
    var child = document.getElementById("child");
    document.getElementById("parent").appendChild(child);
}

这段代码主要是获取页面上的child节点,然后添加到指定位置,可以看到原本的child节点被移动到parent中了。
这里还有一个要注意的点:如果child绑定了事件,被移动时,它依然绑定着该事件。
insertBefore

insertBefore用来添加一个节点到一个参照节点之前,用法如下:

parentNode.insertBefore(newNode,refNode);

parentNode表示新节点被添加后的父节点
newNode表示要添加的节点
refNode表示参照节点,新节点会添加到这个节点之前
我们来看这个例子

<div id="parent">
    父节点
    <div id="child">                
        子元素
    </div>
</div>
<input type="button" id="insertNode" value="插入节点" />

var parent = document.getElementById("parent");
var child = document.getElementById("child");
document.getElementById("insertNode").onclick = function(){
    var newNode = document.createElement("div");
    newNode.textContent = "新节点"
    parent.insertBefore(newNode,child);
}

这段代码创建了一个新节点,然后添加到child节点之前。
和appendChild一样,如果插入的节点是页面上的节点,则会移动该节点到指定位置,并且保留其绑定的事件。

关于第二个参数参照节点还有几个注意的地方:
(1)refNode是必传的,如果不传该参数会报错
(2)如果refNode是undefined或null,则insertBefore会将节点添加到子元素的末尾

removeChild

removeChild顾名思义,就是删除指定的子节点并返回,用法如下:

var deletedChild = parent.removeChild(node);

注意:如果被删除的节点不是其子节点,则程序将会报错。我们可以通过下面的方式来确保可以删除:

if(node.parentNode){
    node.parentNode.removeChild(node);
}

通过节点自己获取节点的父节点,然后将自身删除。

replaceChild

replaceChild用于使用一个节点替换另一个节点,用法如下

parent.replaceChild(newChild,oldChild);

newChild是替换的节点,可以是新的节点,也可以是页面上的节点,如果是页面上的节点,则其将被转移到新的位置

oldChild是被替换的节点

总结

(1)不管是新增还是替换节点,如果新增或替换的节点是原本存在页面上的,则其原来位置的节点将被移除,也就是说同一个节点不能存在于页面的多个位置

(2)节点本身绑定的事件会不会消失,会一直保留着。

节点查询型API
document.getElementById

根据元素id返回元素,返回值是Element类型,如果不存在该元素,则返回null。

使用这个接口有几点要注意:

(1)元素的Id是大小写敏感的,一定要写对元素的id

(2)HTML文档中可能存在多个id相同的元素,则返回第一个元素

(3)只从文档中进行搜索元素,如果创建了一个元素并指定id,但并没有添加到文档中,则这个元素是不会被查找到的

document.getElementsByTagName

根据标签名返回元素,得到的是一个集合

“*”表示所有标签

document.getElementsByName

通过指定的name属性来获取元素,它返回一个即时的NodeList对象。

使用这个接口主要要注意几点:

(1)返回对象是一个即时的NodeList,它是随时变化的

(2)在HTML元素中,并不是所有元素都有name属性,比如div是没有name属性的,但是如果强制设置div的name属性,它也是可以被查找到的

(3)在IE中,如果id设置成某个值,然后传入getElementsByName的参数值和id值一样,则这个元素是会被找到的,所以最好不好设置同样的值给id和name

document.getElementsByClassName

这个API是根据元素的class返回一个即时(表示该集合是随时变化的,也就是是文档中有几个div,它会随时进行变化,当我们新增一个div后,再访问HTMLCollection时,就会包含这个新增的div)的HTMLCollection

注意:
(1)返回结果是一个即时的HTMLCollection,会随时根据文档结构变化
(2)IE9以下浏览器不支持
(3)如果要获取2个以上classname,可传入多个classname,每个用空格相隔,例如

var elements = document.getElementsByClassName("test1 test2");
document.querySelector和document.querySelectorAll

这两个api很相似,通过css选择器来查找元素,注意选择器要符合CSS选择器的规则。
首先来介绍一下document.querySelector。
document.querySelector返回第一个匹配的元素,如果没有匹配的元素,则返回null。

注意,由于返回的是第一个匹配的元素,这个api使用的深度优先搜索来获取元素。我们来看这个例子:

<div>
    <div>
        <span class="test">第三级的span</span>  
    </div>
</div>
<div class="test">          
    同级的第二个div
</div>
<input type="button" id="btnGet" value="获取test元素" />

document.getElementById("btnGet").addEventListener("click",function(){
    var element = document.querySelector(".test");
    alert(element.textContent);
})

两个class都包含“test”的元素,一个在文档树的前面,但是它在第三级,另一个在文档树的后面,但它在第一级,通过querySelector获取元素时,它通过深度优先搜索,拿到文档树前面的第三级的元素。
document.querySelectorAll的不同之处在于它返回的是所有匹配的元素,而且可以匹配多个选择符

<div class="test">
    class为test
</div>
<div id="test">
    id为test
</div>
<input id="btnShow" type="button" value="显示内容" />

document.getElementById("btnShow").addEventListener("click",function(){
    var elements = document.querySelectorAll("#test,.test");    
    for(var i = 0,length = elements.length;i<length;i++){
        alert(elements[i].textContent);
    }   
})

这段代码通过querySelectorAll,使用id选择器和class选择器选择了两个元素,并依次输出其内容。要注意两点:

(1)querySelectorAll也是通过深度优先搜索,搜索的元素顺序和选择器的顺序无关

(2)返回的是一个非即时的NodeList,也就是说结果不会随着文档树的变化而变化

节点关系型API
父关系型

parentNode:每个节点都有一个parentNode属性,它表示元素的父节点。Element的父节点可能是Element,Document或DocumentFragment。

parentElement:返回元素的父元素节点,与parentNode的区别在于,其父节点必须是一个Element,如果不是,则返回null

得到一个元素的父节点的方法:

  1. children.parentNode

  2. children.parentElement

兄弟关系型

previousSibling:节点的前一个同级节点,如果该节点是第一个节点,则为null。注意有可能拿到的节点是文本节点或注释节点,与预期的不符,要进行处理一下。
previousElementSibling:返回前一个元素节点,前一个节点必须是Element,注意IE9以下浏览器不支持。

nextSibling:节点的后一个节点,如果该节点是最后一个节点,则为null。注意有可能拿到的节点是文本节点,与预期的不符,要进行处理一下。
nextElementSibling:返回后一个元素节点,后一个节点必须是Element,注意IE9以下浏览器不支持。

子关系型

childNodes:选出全部的孩子,它是标准属性,它返回指定元素的子元素集合,包括HTML节点,所有属性,文本节点(嫡出)

children:一个即时的HTMLCollection,子节点都是Element

firstNode:第一个子节点

lastNode:最后一个子节点

hasChildNodes方法:可以用来判断是否包含子节点。

得到一个元素的所有子代标签的方法:

1.不包含文本:div.children

2.包含文本:div.childNodes

元素属性型API
setAttribute

setAttribute:设置或更改一个元素的属性,用法:

element.setAttribute(name, value);//name属性名,value属性值
getAttribute

getAttribute返回指定的属性名相应的值,如果不存在,则返回null或空字符串。用法如下:

varvalue = element.getAttribute("id");

相关补充:

得到一个a标签里面的href的方法

a.href:带http协议的,不推荐使用

  1. a.getAttribute(‘href’):你写的是什么获取的就是什么,推荐使用
元素样式型API
window.getComputedStyle

我们想要获得css 的样式, box.style.left box.style.backgorundColor

但是它只能得到 行内的样式。

window.getComputedStyle可以得到的元素内嵌(使用<style>)或者外链(使用<link>)的CSS样式

window.getComputedStyle("元素", "伪类") 
两个选项是必须的, 没有伪类 用 null 替代

HTMLCollection与NodeList的区别
1.HTMLCollection实例对象的成员只能是Element节点,NodeList实例对象的成员可以包含其他节点。

2.HTMLCollection实例对象可以用id属性或name属性引用节点元素,NodeList只能使用数字索引引用。

相关常用API补充:
事件监听就是事件函数

事件函数里面传入一个事件参数,当你调用这个参数.preventDefault()的时候就会阻止默认动作;

Window.onclick = function(x){

x.preventDefault();

}

offsetTop:返回当前元素相对于其 offsetParent 元素的顶部的距离。

scrollTo(x,y):滚动到某个位置,x:横坐标,y:纵坐标

Math.abs():求绝对值

isEqualNode()与isSameNode()的区别

isEqualNode是否是相等的

isSameNode 是否是(同一个)相同的,等价于===

<body>
<div class=”red”>
<div>
Div 1
</div>
</div>
<div class=”red”>
<div>
Div 1
</div>
</div>
</body>
 
var div1 = document.body.children[0];
var div2 = document.body.children[1];
console.log(div1.isEqualNode(div2))  //true
console.log(div1.isSameNode(div2))  //false 这两个相等但不是同一个
normalize()

Node.normalize() 方法将当前节点和它的后代节点”规范化“(normalized)。在一个"规范化"后的DOM树中,不存在一个空的文本节点,或者两个相邻的文本节点。

注1:“空的文本节点”并不包括空白字符(空格,换行等)构成的文本节点。

注2:两个以上相邻文本节点的产生原因包括:

  • 通过脚本调用有关的DOM接口进行了文本节点的插入和分割等。
  • HTML中超长的文本节点会被浏览器自动分割为多个相邻文本节点。
var wrapper = document.createElement("div");
 
wrapper.appendChild(document.createTextNode("Part 1 "));
wrapper.appendChild(document.createTextNode("Part 2 "));
// 这时(规范化之前),wrapper.childNodes.length === 2// wrapper.childNodes[0].textContent === "Part 1 "// wrapper.childNodes[1].textContent === "Part 2 "
 
wrapper.normalize();// 现在(规范化之后), wrapper.childNodes.length === 1// wrapper.childNodes[0].textContent === "Part 1 Part 2 "
方法
close()
<script>

document.write(1);

document.write(2);

</script>

当运行上面两行代码页面中会被连续的写入1和2,也就是说document.write(2)不会覆盖前面的document.write(1);但是如果在上面在加一行代码

setTimeout(()=>{

document.write(3)

},1000)

页面里的1和2就会被覆盖掉,变成3

原因是:

一个document有三个动作:首先open 然后write 最后close

如果页面已经close了就不能往里写东西了,当这个页面加载完成之后了就自动close了(当页面里出现了延时或异步操作同样也会重新open)而这时如果你再次write就要重新回到open动作下,依次按顺序执行open write close,也就是这时候的write会把之前的内容覆盖掉,所以尽量少用write;

典型例题解析:

<div id="parent"></div>

parent的值为:如果有父窗口,就是父窗口,如果没有,就是当前窗口

原因:

上面的parent会打印出一个window对象,因为parent是一个全局属性不能直接用作类名、id名和变量名;

parent是一个全局对象window下的属性,也可以说window下的所有属性都是全局属性(在浏览器下又可以叫做全局变量,就如同你声明一个全局变量var a;同样也可以用window.a来访问这时候a就可以说是属性,也可以是变量),全局属性不能当作类名,id名和变量名;

1.要注意全局属性可耻,不能用做类名,id名和变量名,要不然会互相覆盖;

<div id="self"></div>
var parent = document.querySelector('#self');
//改变了原来的parent属性
console.log(window.parent)  //<div id="self"></div>

2.我们可以用局部变量来代替全局变量(借用函数作用域)

function x(){
   //函数体内用var声明就是局部变量
   var parent = document.querySelector('#self');
   console.log(window.parent)
}
x()//Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}

3.如果只是想用一个局部变量,那么就声明一个匿名函数直接调用他(立即调用函数)

function(){
   var parent = document.querySelector('#self');
   console.log(window.parent)
}()

4.上面的代码会被认为是语法错误,立即调用函数的正确写法:

//1.把代码整体放在一个()里
(function(){
   var parent = document.querySelector('#self');
   console.log(window.parent)
}())

//2.把匿名函数放在一个()里
(function(){
   var parent = document.querySelector('#self');
   console.log(window.parent)
})()

//3.前面加一个-号或+号(触发浏览器,告诉浏览器后面是一个值)
-function(){
   var parent = document.querySelector('#self');
   console.log(window.parent)
}()

5.用var声明的单独的代码块的作用域也是全局(因为var会被变量提升)

{
   //下面的parent是全局变量
   var parent = document.querySelector('#self');
   console.log(window.parent)  //<div id="self"></div>
}

//等价于
var parent;
{
  parent = document.querySelector('#self');
}

6.用let声明的代码块的作用域是当前代码块(不能逃出花括号,不会被提升)

//全局变量a
let a =2;
{
   //a的作用域在这个花括号里,是局部变量
   let a =1
}
console.log(a)  //2

相关文章

网友评论

      本文标题:DOM操作

      本文链接:https://www.haomeiwen.com/subject/dgnifctx.html