您的位置:首页 > Web前端

通俗易懂的讲解DOM

2016-11-21 22:22 218 查看
DOM是所有前端开发每天打交道的东西,但是随着jQuery等库的出现,大大简化了DOM操作,导致大家慢慢的“遗忘”了它的本来面貌。不过,要想深入学习前端知识,对DOM的了解是不可或缺的,所以本文力图系统的讲解下DOM的相关知识,如有遗漏或错误,还请大家指出一起讨论^ ^。

一、DOM是什么

DOM(文档对象模型)是针对HTML和XML文档的一个API,通过DOM可以去改变文档。这个说法很官方,大家可能还不明白。

举个例子:我们有一段HTML,那么如何访问第二层第一个节点呢,如何把最后一个节点移动到第一个节点上面去呢?

DOM就是定义了如何做类似的操作,那么应该怎么做的标准呢,比如用
getElementById
来访问节点,用
insertBefore
来插入节点。

当浏览器载入HTML时,会生成相应的DOM树。

简而言之,DOM可以理解为一个访问或操作HTML各种表情的实现标准。

对于一个HTML来说,文档节点
Document
(这个是看不到的)是他的根节点,对应的对象便是
document
对象(严格的讲是子类
HTMLDocument
对象,下面单独介绍
Document
类型时会指出)。

换句话说存在一个文档节点
Document
,然后他有子节点,比如通过
document.getElementsByTagName(“html”)
,得到类型为元素节点的Element html。

每一段HTML标记都可以用相应的节点来表示,例如:

HTML元素通过元素节点表示,注释通过注释节点表示,文档类型通过文档类型节点表示等。

一共定义了12种节点类型,而这些类型又都继承自
Node
类型。

所以我们首先讲
Node
类型,因为这个类型的方法是所有节点都会继承的。

二、Node类型(基类,所有节点都继承了它的方法)

Node
是所有节点的基类型,所有节点都继承自它,所有所有节点都有一些共同的方法和属性。

先讲
Node
类型的属性

首先是
nodeType
属性,用来表明节点类型的,例如:

document.nodeType
;返回9,其中
document
对象为文档节点的
Document
的实例。

这里面,9代表的就是
DOCUMENT_NODE
节点的意思,可以通过
Node.DOCUMENT_NODE
查看对应的数字
document.nodeType === Node.DOCUMENT_NODE;  //true


至于一共有哪些节点,每个节点对应的数字又是多少,这个可以问问百度。反正最常用的元素节点是Element(对应数字为1)和文本几点Text(对应数字为3)

然后常用的还有
nodeName
nodeValue


对于元素节点
nodeName
就是标签名,
nodeValue
就是
null


对于文本节点
nodeName
为”#text”(chrome里面测试的),
nodeValue
就是实际的值

每个节点还有
childNodes
属性,这是个十分重要的属性,他保存了这个节点所有直接子元素

调用
childNodes
返回的就是一个
NodeList
对象,它极其像一个数组,但是有一个关键的地方,他是动态查询的,也就是说每次调用他都会对DOM结构查询,所以对它的使用需要谨慎,注意性能。

访问
childNodes
可以使用数组下表或者
item
方法

然后各个节点还存在各种属性让它们可以相互访问,下图是个很好的总结



比较有用的属性和方法:

1.
hasChildNodes()


  如果包含子节点就返回
true
,比查询
childNodes
length
来的简单。

  

2.
ownerDocument


  返回文档节点的引用(在html里面也就是
document
对象)

比较有用的操作DOM的方法:

appendChild()
方法可以在节点的
childNodes
的末尾添加一个节点,值得注意的是如果这个节点是已经存在于文档中的,那么变回删除原节点,感觉上就像是移动节点一样。

insertBefore()
方法接受两个参数,一个是插入的节点,另外一个是参照的节点。如果第二个参数为
null
,则
insertBefore
appendChild
的效果一样。否则便会把节点插入到参照节点之前。这里要注意的是,如果第二个参数不为
null
,那么插入的节点不能是已经存在的节点,否则结果同上。

replaceChild()
方法可以替换节点,接受两个参数,需要插入的节点和需要替换的节点。返回被替换掉的节点。

removeChild()
移除节点。这里有个常见的需求,比如我有一个节点#node,那么如何移除它呢?

var node = document.getElementById("node");
node.parentNode.removeChild(node);  //先拿到父节点,再调用父节点的removeChild删除自己


这里大家需要注意一下,上面的四个方法都是操作某个节点的子节点,也就是说,操作前必须先找到父节点(通过
parentNode
来找)

复制节点的方法

cloneNode()
复制节点,接受一个参数
true
或者
false
。如果
true
就是复制那个节点和它的子节点,如果是
false
,就是复制节点本身(复制出来的节点没有任何子元素)。这方法返回复制的节点,如果如要操作它,那么需要借助前面讲的四个方法来把这个节点放到html中去。

上面这些就是Node类型中常用的属性和方法。所有的类型都继承自Node类型所以这些属性和方法是所有节点都有的。

三、Document类型

最开始讲DOM的时候提到了
Document
类型。其实关于这个类型最重要的就是它的一个子类
HTMLDocument
有一个实例对象
document
。而这个
document
对象是我们最常用的一个对象了。

document
对象又是在
window
对象上,所以浏览器就可以直接方法
document
了。

document对象的常用属性

document.childNodes
继承自上面讲的
Node
类型,可以放文档的直接子节点(通常包括文档声明和html节点)

document.documentElement
可以直接拿到html节点的引用(等价于
document.getElementsByTagName("html")[0])


document.body
body
节点的引用

document.title
页面
title
,可以修改,会改变浏览器标签上的名字

document.URL
页面的
url


document.referrer
取得
referrer
,也就是打开这个页面的那个页面的地址,做来源统计时候比较有用

document.domain
取得域名,可以设置,但是同城只能设置为不包含子域名的情况,在一些子域名跨域情况下有效。

document对象常用的方法

getElementById
,传入id,得到元素节点。里面的参数区分大小写(IE8不区分)。注意:如果有多个id相同的元素,则返回第一个。IE7里面表单元素的
name
也会被当做id来使用。

getElementsByTagName
根据标签取得元素,得到的是
HTMLCollection
类型。如果传入的是”*”,则可以得到全部的元素。

还有一个只有
HTMLDocument
类型(也就是
document
对象)才有的方法
getElementsByName
,就是根据那么返回元素。

document
对象还有一些集合,例如
document.forms
可以返回所有的
form
表单。类型也是
HTMLCollection
.

说到
HTMLCollection
HTMLCollection
就是一个包含一个或者多个元素的集合,和讲的
NodeList
很像。
HTMLCollection
这个类型有两个方法,一个是通过下标(或者
.item()
)得到具体元素,还有就是通过
['name']
(或者
.namedItem()
)获得具体元素。

最后,关于
document
对象还有一套比较重要的方法,那就是

write()
,
writeln()
,
open()
,
close()


open
close
分别是打开和关闭网页的输出流,在页面加载的过程中,就相当于
open
状态。这两个方法一般不会去用它。

write
writeln
都是向页面写入东西,区别就是后者会多一个换行符。

需要注意的是:在页面加载过程中,可以使用这两个方法向页面添加内容。如果页面已经加载完了,再调用write,会重写整个页面。

还有一点,如果要动态的写入脚本,例如
<script>xxx</script>
这样的,那么就要把
</script>
分开拼装,否则会被误以为是脚本结束的标志,导致这个结束符匹配到上面一个开始符。可以这样写“”

四、Element类型

下面我们来说一下最重要也是最常见的一个类型,
Element
类型。

我们平时所操作的都是
Element
类型(实质上是
HTMLElement
,这里为了方便理解,就简单这么说),比如

document.getElementById("test")
返回的就是
Element
类型。我们平时所说的”DOM对象”,通常也就是指
Element
类型的对象。

Element类型常见的属性

最常用的当然就是
Node
类型上的那些属性和方法,这里就不在重复说了,主要说一下它独有的

首先是
tagName
,这个和继承自Node类型的
nodeName
一样。都是返回标签名,通常都是大写,有的浏览器会返回小写。所以在比较的时候最好调用一下类似
toLowerCase()
这种方法再做比较。

上面我们提到了
HTMLElement
类型,
HTMLElement
类型继承自Element类型,也就是HTML元素的实际类型,我们在浏览器里用的元素都是这个类型。

这个类型的元素都具有一些标准属性,比如:

  
id
元素的唯一标识,
title
通常是鼠标移上去时显示的信息,
className
类名等等,这几个属性是可读写的,也就是说你改变它们以后会得到相应的效果。

除了属性外,还有几个重要的方法。首先说一下操作节点属性的方法,
getAttribute
setAttribute
removeAttribute
这三个方法。这些是操作属性最常用的方法了,怎么用就不说了,大家应该都会用。还有一个attributes属性,保存了元素的全部属性。

这里要说一个问题,
ele.className
ele.getAttribute("class")
返回的结果是不是同一个东西?

要理解这个问题,首先要说一个重要的知识点,一个元素的属性结构是这么来的,比如一个input元素

<input id="test" checked="checked" />


那么这个元素的属性被包含在
input.attributes
里面,比如你在
html
元素上看到的
class
id
或者你自己定义的
data-test
这种属性。

然后
getAttribute
setAttribute
removeAttribute
这三个方法可以认为是快捷的取
attributes
集合的方法。而直接
input.id
或者
input.className
都是直接挂在
input
下的属性,和
attributes
是同级的,所以返回的东西也许看上去是一样的,但是实质上是不一样的,大家可以试一下
input.checked
input.getAttribute("checked")
,结果是前者返回
true
,后者返回
checked


<input id="btn1" checked="checked"/>
var test = document.getElementById("btn1");
console.log(a.getAttribute("checked"));     //checked
console.log(a.checked)      //true


总的来说,这三个方法通常是用于处理自定义的属性,而不是
id
class
等这样的共有特性。

下面来说一下创建元素

document.createElement()
可以创建一个元素,比如:
document.createElement("div")
创建了一个
div
元素,之后的操作一般都是为元素设置属性,常用的两种方法,一种是直接
node.property
,另一种是
node.setAttribute("propertyName","value")


做完这些以后元素并不是在页面中,所以还要通过最上面讲的像
appendChild()
这些方法来将元素添加到页面中。

在IE中,还可以直接将整个HTML字符串加进去来创建元素,比如
document.createElement("<div>test</div>")


最后,元素节点也支持
HTMLDocument
类型的那些查找方法,比如
getElementsByTagName
。不过它只会找自己后代的节点,所以代码可以这样写:

document.getElementById("test").getElementsByTagName("div")
//找到id为test元素下面的所有div节点


五、Text类型

这个类型比较特殊,也是第三常见的类型(前两个分别是
Document
Element
),这个节点简单的来说就是一个字符串。

它还有个重要的特征就是它没有子元素(文本节点,什么都没有),访问text节点的文本内容,可以通过
nodeValue
或者
data
属性。

下面加单说一下它的一下方法

appendData();       //在Text末尾添加内容
deleteData(offset,count);       //从offset指定的位置开始删除count个字符


还有
insertData
replaceData
splitText
等方法,因为用的特别少,几乎不用,就不一一说了,用的时候可以再查阅。

它还有一个
length
属性,返回的是字符串的长度。

这里说一下一个比较常见的错误,初学者经常会遇到的坑,先来看下面的一段代码:

<ul id="test">
<li>北京</li>
<li>上海</li>
</ul>
<script>
var ul = document.getElementById("test");
var first = ul.firstChild;
console.log(first);     //#text
</script>


这里我们会发现ul的第一个子节点输出的是#text,这是为什么呢,难道不应该是
<li>北京</li>
吗,不是的,因为
<li>北京</li>
<ul>
之间还有一段空格,这些空格被看做为文本节点,所以会它的第一个节点是文本节点。

这是一个常见的问题就是遍历
ul
childNodes
的时候,遍历的时候一定要判断
nodeType
是不是等于1(等于1代表是元素节点),这样才能跳出这个坑。

创建文本节点的方法是
document.createTextNode
,然后接下来的操作和
Element
类型一样。

六、其他的一些类型 Comment、DocumentType和DocumentFragment

这些不常用的就简单的说一下吧,

Comment
是注释节点

DocumentType
就是
doctype
节点,通过
document.doctype
来访问。

DocumentFragment
这个节点是一个文档片段,偶尔会用到。

比如一种常见的用法是,在一个
ul
中插入三个
li
,如果你连续循环插入三次,那么浏览器就渲染三次,对性能有挺大的影响,所以大家一般这么做:

var fragment = document.createDocumentFragment();


创建一个
fragment
,用
appendChild
li
插入到
fragment
中,最后再把
fragment
插入到
u
l中,这样就会优化性能。

七、DOM扩展

通过上面说的一些节点类型,大家对DOM的了解也会深一点,下面来说一下关于DOM扩展的一下东西。

浏览器为了方便开发者,扩展了一下DOM功能,因为是浏览器自己扩展的,所以使用的时候一定要做兼容性测试

判断标准模式和混杂模式,通过
document.compatMode
document.documentMode


上面说了一个文本节点是第一子元素的问题,所以浏览器又实现了一个
children
属性,这个属性只包含元素节点。

为了方便判断A节点是不是B节点的子节点,引入了
contains
方法,比如

B.contains(A);      //true代表是,false代表不是


针对访问元素,又提供了四个方法:
innerText
innerHTML
outerText
ouerHTML
,通过这些方法,可以读写元素,其中TEXT返回的文本内容,*HTML返回的是html文本,而outer则代表包含元素本身。

重要的是,这几个方法又心梗问题,比如在IE中,通过
inner*
删除的节点,其绑定的事件依然存在内存中,会消耗大量的内存。

还有一个技巧是,插入大量的html代码,用
innerHTML
是非常快的,建议使用。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  前端开发 DOM