前言:尽管现在有很多优秀的框架,大大简化了我们的DOM操作,但是我们仍然要学好DOM知识来写原生JS,从根本上去理解,才更能在解决问题时举重若轻。

1.什么是DOM

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

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

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

我们通过DOM模型将上述结构一一映射成节点(通过构造函数把页面中的节点变成实例对象,dom就是这样把文档变成对象的),这些节点就又构成了节点树,也就是我们说的想象中的那棵DOM Tree。

2.Node

DOM 的最小组成单位叫做节点(node)。文档的树形结构(DOM 树),就是由各种不同类型的节点组成。

节点主要有7种类型:

  • Document:整个文档树的根节点
  • Document:doctype标签节点,如<!DOCTYPE html>
  • Element:网页的各种Html标签,比如<body>、<div>
  • Attribute:网页元素的属性
  • Text:标签之间或者标签包含的文本
  • Comment:注释
  • DocunmentFragment:文档的片段

注:DOM树有3种层级结构:

  • 父节点关系(parentNode):直接的上级节点
  • 子节点关系(childNodes):直接的下级节点
  • 兄弟关系(sibling):拥有同一个父节点的同级节点

值得注意的是,在上图中只有根节点也就是<html>对应的节点没有父节点。

3.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。

值得注意的是,该属性还包括文本节点和注释节点(<!-- comment -->)。因此如果当前节点后面有空格或者回车,该属性会返回一个文本节点,内容为空格或回车。

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方法返回一个布尔值,表示两个节点是否为同一个节点。

所以说,isSameNode就等同于 ===严格相等运算符。

2.9 Node.prototype.normalize()

normailize方法用于清理当前节点内部的所有文本节点(text)。它会去除空的文本节点,并且将毗邻的文本节点合并成一个,也就是说不存在空的文本节点,以及毗邻的文本节点

4.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方法返回一个布尔值,表示当前文档之中是否有元素被激活或获得焦点。
    ……

5. 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():触发事件
    ……