JavaScript常见原生DOM操作API总结

基本概念

DOM

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

DOM 只是一个接口规范,可以用各种语言实现。所以严格地说,DOM 不是 JavaScript 语法的一部分,但是 DOM 操作是 JavaScript 最常见的任务,离开了 DOM,JavaScript 就无法控制网页。

节点

DOM 的最小组成单位叫做节点(node)。文档的树形结构(DOM 树),就是由各种不同类型的节点组成。每个节点可以看作是文档树的一片叶子。

几种常见的节点:

  • Document:整个文档树的顶层节点
  • DocumentTypedoctype标签(比如<!DOCTYPE html>
  • Element:网页的各种HTML标签(比如<body><a>等)
  • Attribute:网页元素的属性(比如class="right"
  • Text:标签之间或标签包含的文本
  • Comment:注释
  • DocumentFragment:文档的片段

Node 对象定义了几个常量,对应这些类型值。

1
document.nodeType === Node.DOCUMENT_NODE // true

Node有一个属性nodeType表示Node的类型,它是一个整数,其数值分别表示相应的Node类型,不同节点的nodeType属性值和对应的常量如下:

  • 文档节点(document):9,对应常量Node.DOCUMENT_NODE
  • 元素节点(element):1,对应常量Node.ELEMENT_NODE
  • 属性节点(attr):2,对应常量Node.ATTRIBUTE_NODE
  • 文本节点(text):3,对应常量Node.TEXT_NODE
  • 文档片断节点(DocumentFragment):11,对应常量Node.DOCUMENT_FRAGMENT_NODE
  • 文档类型节点(DocumentType):10,对应常量Node.DOCUMENT_TYPE_NODE
  • 注释节点(Comment):8,对应常量`Node.COMMENT_NODE

上面说完了DOM的基本概念,下面将分类学习一下常用的DOM操作API。

节点查询API

document.getElementById

根据元素id查找元素,大小写敏感,返回值是Element类型,如果不存在该元素,则返回null,如果有多个结果,只返回第一个。

1
var elem = document.getElementById('para1');

document.getElementsByTagName

根据标签查找元素,*表示查询所有标签,返回一个HTMLCollection

1
var paras = document.getElementsByTagName('p');

document.getElementsByName

根据元素的name属性查找,返回一个NodeList

1
2
3
// 表单为 <form name="x"></form>
var forms = document.getElementsByName('x');
forms[0].tagName // "FORM"

document.getElementsByClassName

根据类名查找元素,多个类名用空格分隔,返回一个HTMLCollection,另外,不仅仅是document,其它元素也支持getElementsByClassName方法.

1
var elements = element.getElementsByClassName(names);

如果要获取2个以上classname,可传入多个classname,每个用空格相隔。

1
var elements = document.getElementsByClassName('foo bar');

document.querySelector和document.querySelectorAll

这两个api很相似,通过css选择器来查找元素,注意选择器要符合CSS选择器的规则。

document.querySelector返回第一个匹配的元素,如果没有匹配的元素,则返回null。

这个例子中,会返回当前文档中第一个类名为 “myclass“ 的元素:

1
var el = document.querySelector(".myclass");

document.querySelectorAll的不同之处在于它返回的是所有匹配的元素,而且可以匹配多个选择符。

下面的例子返回一个文档中所有的class为”note“或者 “alert“的div元素.

1
var matches = document.querySelectorAll("div.note, div.alert");

节点创建API

createElement

通过传入指定的一个标签名来创建一个元素,如果传入的标签名是一个未知的,则会创建一个自定义的标签,注意:IE8以下浏览器不支持自定义标签。

1
var div = document.createElement("div");

使用createElement要注意:通过createElement创建的元素并不属于html文档,它只是创建出来,并未添加到html文档中,要调用appendChildinsertBefore等方法将其添加到HTML文档树中。

1
2
var div = document.createElement("div");
document.body.appendChild(div);

createTextNode

用来创建一个文本节点:

1
2
var node = document.createTextNode("我是文本节点");
document.body.appendChild(node);

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

cloneNode

cloneNode是用来返回调用方法的节点的一个副本,它接收一个bool参数,用来表示是否采用深度克隆,如果为true,则该节点的所有后代节点也都会被克隆,如果为false,则只克隆该节点本身。

1
2
var p = document.getElementById("para1"),
var p_prime = p.cloneNode(true);

createDocumentFragment

createDocumentFragment用来创建一个新的空白的文档片段。

DocumentFragment是一个存在于内存的 DOM 片段,不属于当前文档,常常用来生成一段较复杂的 DOM 结构,然后再插入当前文档。这样做的好处在于,因为DocumentFragment不属于当前文档,对它的任何改动,都不会引发网页的重新渲染,比直接修改当前文档的 DOM 有更好的性能表现。

1
2
3
4
5
6
7
8
9
10
var docfrag = document.createDocumentFragment();

[1, 2, 3, 4].forEach(function (e) {
var li = document.createElement('li');
li.textContent = e;
docfrag.appendChild(li);
});

var element = document.getElementById('ul');
element.appendChild(docfrag);

上面代码中,文档片断docfrag包含四个<li>节点,这些子节点被一次性插入了当前文档。

节点修改API

appendChild

appendChild我们在前面已经用到多次,接受一个节点对象作为参数,将其作为最后一个子节点,插入当前节点。该方法的返回值就是插入文档的子节点。

1
2
var p = document.createElement('p');
document.body.appendChild(p);

如果参数节点是 DOM 已经存在的节点,appendChild方法会将其从原来的位置,移动到新位置。

如果appendChild方法的参数是DocumentFragment节点,那么插入的是DocumentFragment的所有子节点,而不是DocumentFragment节点本身。返回值是一个空的DocumentFragment节点

insertBefore

insertBefore方法用于将某个节点插入父节点内部的指定位置。

1
var insertedNode = parentNode.insertBefore(newNode, referenceNode);

insertBefore接受两个参数,第一个参数是所要插入的节点newNode,第二个参数是父节点parentNode内部的一个子节点referenceNodenewNode将插在referenceNode这个子节点的前面。返回值是插入的新节点newNode

1
2
var p = document.createElement('p');
document.body.insertBefore(p, document.body.firstChild);

上面代码中,新建一个<p>节点,插在document.body.firstChild的前面,也就是成为document.body的第一个子节点。

关于第二个参数参照节点还有两个注意的地方:

  1. refNode是必传的,如果不传该参数会报错
  2. 如果refNode是undefined或null,则insertBefore会将节点添加到子元素的末尾

removeChild

removeChild顾名思义,就是从当前节点移除该子节点。返回值是移除的子节点。

1
var deletedChild = parent.removeChild(node);

被移除的节点依然存在于内存之中,但不再是 DOM 的一部分。所以,一个节点移除以后,依然可以使用它,比如插入到另一个节点下面。

如果被删除的节点不是其子节点,removeChild方法将报错。

replaceChild

replaceChild方法用于将一个新的节点,替换当前节点的某一个子节点。

1
var replacedNode = parentNode.replaceChild(newChild, oldChild);

上面代码中,replaceChild方法接受两个参数,第一个参数newChild是用来替换的新节点,第二个参数oldChild是将要替换走的子节点。返回值是替换走的那个节点oldChild

节点关系API

节点树

一个文档的所有节点,按照所在的层级,可以抽象成一种树状结构。这种树状结构就是 DOM 树。它有一个顶层节点,下一层都是顶层节点的子节点,然后子节点又有自己的子节点,就这样层层衍生出一个金字塔结构,倒过来就像一棵树。

浏览器原生提供document节点,代表整个文档。

文档的第一层只有一个节点,就是 HTML 网页的第一个标签<html>,它构成了树结构的根节点(root node),其他 HTML 标签节点都是它的下级节点。

除了根节点,其他节点都有三种层级关系。

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

DOM 提供操作接口,用来获取这三种关系的节点。

父关系API

  • parentNode:每个节点都有一个parentNode属性,它表示元素的父节点。Element的父节点可能是Element,Document或DocumentFragment。
  • parentElement:返回元素的父元素节点,与parentNode的区别在于,其父节点必须是一个Element元素,如果不是,则返回null;

子关系API

  • children:返回一个实时的HTMLCollection,子节点都是Element,IE9以下浏览器不支持;
  • childNodes:返回一个实时的NodeList,表示元素的子节点列表,注意子节点可能包含文本节点、注释节点等;
  • firstChild:返回第一个子节点,不存在返回null,与之相对应的还有一个firstElementChild
  • lastChild:返回最后一个子节点,不存在返回null,与之相对应的还有一个lastElementChild

兄弟关系型API

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

元素属性API

getAttribute

Element.getAttribute方法接受一个字符串作为参数,返回同名属性的值。如果没有该属性,则返回null

1
2
var mydiv = document.getElementById('mydiv');
var id = mydiv.getAttribute('id');

上面代码读取mydivid的值。

setAttribute

Element.setAttribute方法用于为当前节点设置属性。如果属性已经存在,将更新属性值,否则将添加该属性。该方法没有返回值。

1
2
3
var b = document.querySelector('button');
b.setAttribute('name', 'myButton');
b.setAttribute('disabled', true);

上面代码中,button元素的name属性被设成myButtondisabled属性被设成true

这里有两个地方需要注意,首先,属性值总是字符串,其他类型的值会自动转成字符串,比如布尔值true就会变成字符串true;其次,上例的disable属性是一个布尔属性,对于<button>元素来说,这个属性不需要属性值,只要设置了就总是会生效,因此setAttribute方法里面可以将disabled属性设成任意值。

removeAttribute

Element.removeAttribute方法移除指定属性。该方法没有返回值。

1
document.getElementById('div1').removeAttribute('id')

元素样式API

直接修改元素的样式

1
2
3
elem.style.color = 'red';
elem.style.setProperty('font-size', '16px');
elem.style.removeProperty('color');

window.getComputedStyle

window.getComputedStyle方法,就用来返回浏览器计算后得到的最终规则。它接受一个节点对象作为参数,返回一个 CSSStyleDeclaration 实例,包含了指定节点的最终样式信息。所谓“最终样式信息”,指的是各种 CSS 规则叠加后的结果。

1
2
3
var div = document.querySelector('div');
var styleObj = window.getComputedStyle(div);
styleObj.backgroundColor

上面代码中,得到的背景色就是div元素真正的背景色。

getBoundingClientRect

getBoundingClientRect用来返回元素的大小以及相对于浏览器可视窗口的位置,用法如下:

1
var clientRect = element.getBoundingClientRect();

clientRect是一个DOMRect对象,包含width、height、left、top、right、bottom,它是相对于窗口顶部而不是文档顶部,滚动页面时它们的值是会发生变化的。

参考内容