前言:尽管现在有很多优秀的框架,大大简化了我们的DOM操作,但是我们仍然要学好DOM知识来写原生JS,从根本上去理解,才更能在解决问题时举重若轻。
一、什么是DOM
D(document)O(object)M(model) 文档对象模型。
DOM(文档对象模型)是HTML和XML文档的编程接口。它提供了对文档的结构化的表述,并定义一种方式可以使从程序中对该结构进行访问,从而改变文档的结构、样式和内容。DOM将文档解析为一个由节点和对象(包含属性和方法的对象)组成的结构集合。
上述说法是MDN的解释,太官方,我们来换种说法来解释。
DOM就是一种想象的树形结构,它的作用是将网页转为一个 JavaScript 对象,从而可以用脚本进行各种操作(比如增删内容)。
如上图,是我们文档的树形结构,我们通过DOM模型将上述结构一一映射成节点(通过构造函数把页面中的节点变成实例对象,dom就是这样把文档变成对象的),这些节点就又构成了节点树,也就是我们说的想象中的那棵DOM Tree。
二、Node
DOM 的最小组成单位叫做节点(node)。文档的树形结构(DOM 树),就是由各种不同类型的节点组成。
节点主要有7种类型:
-
Document
:整个文档树的根节点 -
Document
:doctype标签节点,如<!DOCTYPE html>
-
Element
:网页的各种Html
标签,比如<body><div>
等 -
Attribute
:网页元素的属性 -
Text
:标签之间或者标签包含的文本 -
Comment
:注释 -
DocunmentFragment
:文档的片段
所以文档树对应的节点树如同下图:
注:DOM树有3种层级结构:
- 父节点关系(parentNode):直接的上级节点
- 子节点关系(childNodes):直接的下级节点
- 兄弟关系(sibling):拥有同一个父节点的同级节点
值得注意的是,在上图中只有根节点也就是<html>
对应的节点没有父节点。
三、Node接口
浏览器提供一个原生的节点对象Node
,上面的7种节点均继承了Node
,因此具有一些共同的属性和方法。这是DOM操作的基础。
1、属性
1.1 Node.prototype.nodeType
nodeType
属性返回一个整数值,表示节点的类型。
如上图,我们获取当前页面的body
标签下的第一个孩子,是一个div
标签,然后我们通过nodeType
来看一下它的节点类型,结果返回了一个数字1,这代表着是一个Element
节点。
【至于为什么会是返回一个数字而不是简单明了的返回Element
,这也是由于历史原因,早期计算机内存紧张,为了节省内存使用了并无规律的数字】
常见的有以下:
Node.ELEMENT_NODE
:1
Node.ATTRIBUTE_NODE
:2
Node.TEXT_NODE
:3
Node.COMMENT_NODE
:8
Node.DOCUMENT_NODE
:9
Node.DOCUMENT_TYPE_NODE
:10
Node.DOCUMENT_FRAGMENT_NODE
:11
1.2 Node.prototype.nodeName
nodeName
属性返回节点名称
注:在元素节点中,返回名称基本都是大写,只有<svg>
标签返回的是小写。
1.3 Node.prototype.nodeValue
nodeValue
属性返回一个字符串,表示当前节点本身的文本值,该属性可读写。
只有文本节点(text)、注释节点(comment)和属性节点(attr)有文本值,因此这三类节点的nodeValue
可以返回结果,其他类型的节点一律返回null
。
// html 代码如下
// <div id="d1">hello world</div>
var div = document.getElementById('d1');
div.nodeValue // null
div.firstChild.nodeValue //"hello world"
1.4 Node.prototype.textContent
textContent
返回节点及后代节点的文本 ,即获取文本
这里和innerText
一起讲:
早期并没有获取文本的API ,导致编码很繁琐,所以后来IE自己添加了一个API就是innerText
,然后火狐和opera也推出了textContent
两者的区别:
-
textContent
会获取所有元素的内容,包括<script>
和<style>
元素,然而innerText
不会获取这些内容。 -
innerText
可以意识到样式,它不会返回样式为display:none
也就是隐藏的文本,而textContent
会。 - 由于
innerText
会意识到样式,也就是会受样式的影响,因此会触发重排(reflow)导致性能低,而textContent
不会。 - 与
textContent
不同, 在 Internet Explorer (对于小于等于 IE11 的版本) 中对innerText
进行修改, 不仅会移除当前元素的子节点,而且还会永久性地破坏所有后代文本节点(所以不可能将节点再次插入到任何其他元素或同一元素中).
1.5 Node.prototype.nextSibling
Node.nextSibling
属性返回紧跟在当前节点后面的第一个同级节点。如果当前节点后面没有同级节点,则返回null
。
值得注意的是,该属性还包括文本节点和注释节点(``)。因此如果当前节点后面有空格或者回车,该属性会返回一个文本节点,内容为空格或回车。
document.body.nextSibling
返回了文本节点或者注释节点,而我们需要获得是元素节点, 也可以用document.prototype.nextElementSibling
直接获取该节点后面最接近的同级元素节点。
1.6 Node.prototype.previousSibling
previousSibling
属性返回当前节点前面的、距离最近的一个同级节点。如果当前节点前面没有同级节点,则返回null
。
Node.prototype.previousElementSibling
前一个同级元素节点
其他同1.5 一致
1.7 Node.prototype.firstChild,Node.prototype.firstElementChild
firstChild
属性返回当前节点的第一个子节点,如果当前节点没有子节点,则返回null
。
firstElementChild
属性返回当前节点的第一个元素节点。
1.8 Node.prototype.lastChild,Node.prototype.lastElementChild
lastChild
属性返回当前节点的最后一个子节点,如果当前节点没有子节点,则返回null
。
lastElementChild
属性返回当前节点的最后一个元素节点。
1.9 Node.prototype.childNodes
childNodes
属性返回一个类似数组的对象(NodeList
集合),成员包括当前节点的所有子节点。
值得注意的是,除了元素节点,childNodes
属性的返回值还包括文本节点和注释节点。如果当前节点不包括任何子节点,则返回一个空的NodeList
集合。由于NodeList
对象是一个动态集合,一旦子节点发生变化,立刻会反映在返回结果之中。
1.10 Node.prototype.children
children
属性返回一个类似数组的对象(HTMLCollection),成员包括当前节点的所有子元素节点。
值得注意的是,这里就不会返回文本节点和注释节点了,它只会返回元素节点。由于HTMLCollection
集合是动态集合,一旦子节点发生变化,立刻会反映在返回结果之中。
小tips : Nodelist
和 HTMLCollection
集合的区别
-
NodeList
可以包含各种类型的节点,HTMLCollection
只能包含 HTML 元素节点. -
NodeList
实例可能是动态集合,也可能是静态集合。目前,只有Node.childNodes
返回的是一个动态集合,其他的NodeList
都是静态集合。而HTMLCollection
实例都是动态集合,节点的变化会实时反映在集合中. - 与
NodeList
接口不同,HTMLCollection
没有forEach
方法,只能使用for
循环遍历。
2、方法
2.1 Node.prototype.appendChild()
appendChild
方法就是接受一个节点对象作为参数,将其作为最后一个子节点,插入到当前节点 。该方法的返回值就是插入的子节点。
注意:
1、如果参数节点是 DOM 已经存在的节点,appendChild
方法会将其从原来的位置移动到新位置。
2、如果appendChild
方法的参数是DocumentFragment
节点,那么插入的是DocumentFragment
的所有子节点,而不是DocumentFragment
节点本身。返回值是一个空的DocumentFragment
.
2.2 Node.prototype.hasChildNodes()
hasChildNodes
方法返回一个布尔值,表示当前节点是否有子节点。
注意:子节点包括所有类型的节点,并不仅仅是元素节点。哪怕节点只包含一个空格,hasChildNodes
方法也会返回true
判断一个节点是否有子节点,有以下3种方法:
1、node.hasChildNodes()
2、node.firstChild ! == null
3、node.childNodes && node.childNodes.length > 0
2.3 Node.prototype.cloneNode()
cloneNode
方法拷贝一个节点,并且可以接受一个布尔值,来表示是否同时拷贝子节点。它的返回值是一个克隆出来的新节点。
深拷贝:深入进去全部拷贝,包括子节点。
浅拷贝:只拷贝节点本身。
值得注意的是,该方法返回的节点不在文档中,无任何父节点,必须用如appendChild()
等方法添加。
2.4 Node.prototype.insertBefore()
insertBefore
方法用于将某个节点插入父节点内部的指定位置。
insertBefore
方法接受两个参数,第一个参数是所要插入的节点newNode
,第二个参数时父节点parentNode
内部的一个子节点referenceNode
。newNode
将插在referenceNode
这个子节点的前面。返回值是插入的新节点newNode
.
2.5 Node.prototype.removeChild()
removeChild
方法接受一个子节点作为参数,用于从当前节点移除该子节点。返回值是移除的子节点。
值得注意的是,被移除的节点依然存在于内存之中,但不再是DOM的一部分。所以,一个节点移除以后,依然可以使用它,比如插入到另一个节点下面。
如果参数节点不是当前节点的子节点,removeChild
方法将报错。
2.6 Node.prototype.replaceChild()
replaceChild
方法用于将一个新的节点,替换当前节点的某一个子节点。
创造一个新儿子取代掉旧儿子,旧儿子去哪儿了?旧儿子去内存了。
2.7 Node.prototype.contains()
contains
方法返回一个布尔值,表示参数节点是否为该节点的后代节点。
2.8 Node.prototype.isEqualNode(),Node.prototype.isSameNode()
isEqualNode
方法返回一个布尔值,用于检查两个节点是否相等。所谓相等的节点,指的是两个节点的类型相同、属性相同、子节点相同。
isSameNode
方法返回一个布尔值,表示两个节点是否为同一个节点。
image
所以说,isSameNode
就等同于 ===
严格相等运算符。
2.9 Node.prototype.normalize()
normailize
方法用于清理当前节点内部的所有文本节点(text)。它会去除空的文本节点,并且将毗邻的文本节点合并成一个,也就是说不存在空的文本节点,以及毗邻的文本节点
四、Document接口
document
节点对象代表整个文档,每张网页都有自己的document
对象。window.document
属性就指向这个对象。只要浏览器开始载入 HTML 文档,该对象就存在了,可以直接使用。
document
对象有不同的办法可以获取。
1、属性
1.1 用于指向其他节点(快捷获取某些特殊节点)的属性
-
document.doctype
指向<DOCTYPE>
节点,即文档类型节点。 -
document.documentElement
指向 DOM 的html
节点 -
document.activeElement
指向获得焦点的那个节点 -
document.fullscreenElement
返回当前以全屏状态展示的 DOM 元素。 -
document.body
指向<body>
节点 -
document.head
指向<head>
节点。 -
document.scrollingElement
返回文档的滚动元素。 - ……
1.2 返回文档特定元素的伪数组集合的属性
-
document.links
属性返回当前文档所有设定了href
属性的<a>
及<area>
节点。 -
document.forms
属性返回所有<form>
表单节点。 -
document.images
属性返回页面所有<img>
图片节点。 -
document.scripts
属性返回所有<script>
节点。 -
document.styleSheets
属性返回文档内嵌或引入的样式表集合 - ……
以上均为动态集合,而且除了document.styleSheets
,以上的集合属性返回的都是HTMLCollection
实例。
1.3 返回文档信息的属性
-
document.documentURI和document.URL
属性都返回一个字符串,表示当前文档的网址。 -
document.domain
属性返回当前文档的域名,不包含协议和接口。 -
document.Location
对象是浏览器提供的原生对象,提供 URL 相关的信息和操作方法。 -
document.title
属性返回当前文档的标题。 -
document.characterSet
属性返回当前文档的编码,比如UTF-8
-
document.referrer
属性返回一个字符串,表示当前文档的访问者来自哪里. - ……
1.4 返回文档状态的属性
-
document.hidden
属性返回一个布尔值,表示当前页面是否可见。 -
document.visibilityState
返回文档的可见状态。 - ……
2、方法
-
document.open
方法清除当前文档所有内容,使得文档处于可写状态,供document.write
方法写入内容。 -
document.close
方法用来关闭document.open()
打开的文档。 -
document.write
方法用于向当前文档写入内容。 -
document.writeln
方法与write
方法完全一致,除了会在输出内容的尾部添加换行符。 -
document.querySelector
接受一个 CSS 选择器作为参数,返回匹配该选择器的元素节点 -
document.querySelectorAll
方法与querySelector
用法类似,区别是返回一个NodeList
对象,包含所有匹配给定选择器的节点。 -
document.getElementsByTagName
搜索 HTML 标签名,返回符合条件的元素。 -
document.getElementsByClassName
返回一个类似数组的对象(HTMLCollection
实例),包括了所有class
名字符合指定条件的元素,元素的变化实时反映在返回结果中。 -
document.getElementsByName
方法用于选择拥有name
属性的 HTML 元素,返回一个类似数组的的对象(NodeList
实例). -
document.getElementById
方法返回匹配指定id
属性的元素节点 -
document.createElement
方法用来生成元素节点,并返回该节点。 -
document.createTextNode
方法用来生成文本节点(Text
实例) -
document.createDocumentFragment
方法生成一个空的文档片段对象(DocumentFragment
实例)。 -
document.hasFocus
方法返回一个布尔值,表示当前文档之中是否有元素被激活或获得焦点。 - ……
五、Element接口
1、属性
-
Element.id
属性返回指定元素的id
属性,该属性可读写。 -
Element.tagName
属性返回指定元素的大写标签名 -
Element.title
属性用来读写当前元素的 HTML 属性title
-
Element.attributes
属性返回一个类似数组的对象,成员是当前元素节点的所有属性节点 -
Element.className
属性用来读写当前元素节点的class
属性。 -
Element.classList
返回一个伪数组,成员是当前元素节点的每个class
。 -
Element.innerHTML
属性返回一个字符串,等同于该元素包含的所有 HTML 代码。 -
Element.clientHeight
属性返回一个整数值,表示元素节点的 CSS 高度 -
Element.clientWidth
属性返回元素节点的 CSS 宽度,同样只对块级元素 -
Element.scrollHeight
属性返回一个整数值(小数会四舍五入),表示当前元素的总高度(单位像素),包括溢出容器、当前不可见的部分。 -
Element.scrollWidth
属性表示当前元素的总宽度(单位像素),其他地方都与scrollHeight
属性类似。 -
Element.children
属性返回一个类似数组的对象(HTMLCollection
实例),包括当前元素节点的所有子元素。 -
Element.childElementCount
属性返回当前元素节点包含的子元素节点的个数,与Element.children.length
的值相同。 - ……
2、方法
-
getAttribute()
读取某个属性的值 -
getAttributeNames()
返回当前元素的所有属性名 -
setAttribute()
写入属性值 -
hasAttribute()
某个属性是否存在 -
hasAttributes()
当前元素是否有属性 -
removeAttribute()
删除属性 -
Element.querySelector
接受 CSS 选择器作为参数,返回父元素的第一个匹配的子元素。 -
Element.querySelectorAll
接受 CSS 选择器作为参数,返回一个NodeList
实例,包含所有匹配的子元素。 -
Element.remove
方法继承自 ChildNode 接口,用于将当前元素节点从它的父节点移除。 -
Element.getBoundingClientRect
方法返回一个对象,提供当前元素节点的大小、位置等信息,基本上就是 CSS 盒状模型的所有信息 -
Element.addEventListener()
:添加事件的回调函数 -
Element.removeEventListener()
:移除事件监听函数 -
Element.dispatchEvent()
:触发事件 - ……