Dom操作
原生JavaScript在网页中扮演的是控制网页行为的角色。而控制网页行为,则是通过操作DOM来完成的。所有对DOM的操作可以理解为以下的步骤:
- 查找(或创建)要进行操作的DOM元素
- 对该DOM进行操作(更改属性/从DOM树中删除/增加到DOM树中)
当然,这只是一个宏观上流程,具体的每一步又有很多内容。比如如何根据不同的情况去控制元素的属性,如何合适的创建一组DOM元素等等,这些都需要开发者考虑。
***
DOM操作可以分为在创建新元素、在DOM中获取元素、增加元素到DOM、从DOM中删除元素、改变元素的属性5类,下面来分别讨论。
1.创建新元素
可以将节点元素分为两类,一类是在DOM树中的,另一类是已经存在但是还没成为DOM树的一部分的元素,这类元素可以称为“文档碎片”(document fragment),
document.createElement(tagName)
:创建一个新元素节点- (创建文本节点的方法为
document.createTextNode(text)
,在这里仅讨论元素节点,故不做过多讨论)。 document.createDocumentFragment()
:创建一个DocumentFragment
(文档碎片)对象。(该对象可以用appendChild()
方法将众多节点加入其中,然后一次性添加到DOM树中,可以提高性能,本文不过多讨论。)Node.cloneNode([deep])
:克隆一个该节点的克隆对象并返回该对象,如果deep参数指定为true则同时也克隆该节点的所有后代节点应该也被克隆,否则就只克隆该节点本身。
2.在DOM中获取元素
注意,这里所说的是获取元素,而不只是查找元素——查找元素是根据查找条件查找,此外也可以通过在DOM树中的(父子兄弟)关系来获取,甚至还可以直接通过属性获取某类元素集合。
根据查找条件查找:
document.getElementById()
:根据id查找元素节点,返回一个Element
对象document.getElementsByClassName()
/Element.getElementsByClassName()
:根据类名查找元素节点,返回一个HTMLCollection
(动态更新的元素集合类型),IE9+的浏览器可以使用。document.getElementsByName()
:根据name属性查找元素节点,返回一个动态更新的NodeList
对象(有些情况下是动态更新的,有些则不是)。document.getElementsByTagName()
/Element.getElementsByTagName()
:根据节点类型查找元素节点,返回一个HTMLCollection
,可以指定参数为"*"
来查找所有节点document.querySelector()
/Element.querySelector()
:根据CSS选择字符串查找元素节点,返回第一个匹配的元素节点,IE8+的浏览器可以使用document.querySelectorAll()
/ParentNode.querySelectorAll()
:根据css选择字符串查找元素节点,返回包含匹配的元素的一个静态的NodeList
,IE8+的浏览器可以使用,但只支持CSS2选择器,IE9+可以正常使用
通过在DOM树中的(父子兄弟)关系来获取:
ParentNode.firstElementChild
:返回该节点的第一个子元素节点,浏览器兼容性为IE9+,在早期浏览器中,可以自己实现,原理是遍历childNodes属性,找到第一个nodeType为1的节点。ParentNode.lastElementChild
:返回该节点的最后一个子元素节点,兼容情况同上ParentNode.children
:返回包含该节点中所有子元素节点(注意不是后代节点,而是直接子节点。在本文中不特别强调元素节点,皆指代所有类型的节点。)的HTMLCollection
,浏览器兼容性为IE9+,IE6,7,8支持该属性,但是会返回元素节点和注释节点。,也可自己实现以兼容老版本:通过遍历childNodes,获取所有nodeType为1的节点。Node.childNodes
:返回该节点中的所有子节点(包括所有类型的节点,比如文本节点,注释节点,文档类型节点等)Node.firstChild
:返回该节点的第一个子节点,如果没有子节点则返回null
Node.lastChild
:返回该节点的最后一个子节点,如果没有子节点则返回null
Node.parentElement
:返回该节点的父元素节点,如果没有父节点则返回null
Node.parentNode
:返回该节点的父节点,如果没有父节点则返回null
。注意,元素节点的父节点只会是Element/Document/DocumentFragment三种类型的节点。Element.nextElementSibling
:返回该元素节点的下一个同父亲的子节点,如果该元素节点已经是最后一个子节点了,则返回null,浏览器支持性为IE9+,但是在之前的浏览器可以通过借Node.nextSibling
属性去实现。Element.previoustElementSibling
:同上
通过属性获取某类元素集合:
document.images
:返回包含文档中的所有图片元素节点的HTMLCollection
document.forms
:返回包含文档中的所有<form>
节点的HTMLCollection
document.links
:返回所有包含href属性的<a>
节点的HTMLCollection
注:
1.这些属性和方法分别属于不同类型的对象(Document,Element,ParentNode…),具体这些对象之间的关系,在下一篇文章中我将会讨论。
2.节点有很多类,包括元素节点(element node,nodeType属性的值为1),文本节点(textNode,nodeType属性的值为3)等等
3.增加元素到DOM
Node.appendChild(child)
:将传入的节点(或者是一个DocumentFragment对象)添加到该节点的最后一个儿子处,如果传入的是一个节点则返回该节点;如果是一个DocumentFragment则会传入其包含的内容,不包括其本身,并返回一个空的DocuFragment。注意,如果传入的是一个已经在DOM树中存在的节点的引用,则会先从原来的位置移除该节点,然后再添加到新位置。即同一个节点对象只允许在DOM树中出现一次。要想传入一个相同的节点,可以先用Node.cloneNode()
方法克隆一个新节点,然后再添加到某个位置。Node.insertBefore(newNode,targetNode)
:将newNode传入到Node(方法调用者)的子节点targetNode之前。如果targetNode不是Node的子节点,会报错;如果targetNode为null
,则会将newNode添加到Node的最后一个子节点处。返回值是newNode,至于DoucmentFragment的情况,和appendChild()
方法相同。Element.innerHTML
:该属性是当前元素节点内的HTML格式的字符串,这个属性是可读写的,也就是说我们可以通过操作innerHTML来向元素节点中添加元素节点,但是这个属性的使用有一些需要注意的地方,比如因为通过它传入的字符串在渲染的时候会直接渲染,这就可能导致XSS安全问题,而且innerHTML操作也不够灵活。Element.insertAdjacentElement(position, element)
:向当前元素的相邻位置插入元素节点。position有四种取值,分别为:- beforebegin:在当前元素之前插入
- afterbegin:插入为当前元素的第一个子节点
- beforeend:插入为当前元素的最后一个子节点
- afterend:在当前元素后插入
Element.insertAdjacentHTML(position, text)
:position同上,但需要注意传入的text为HTML字符串,也不会转义,可能会导致XSS问题。
4.从DOM中删除元素
Node.removeChild(child)
:从DOM树中移除该节点的子节点,返回被移除的子节点。如果返回值被接收,则仍然存在于内存中,否则,则因为没有指向该节点的引用而被自动回收机制删除。Node.replaceChild(newChild, oldChild)
:用newChild替换Node中的子节点oldChild,并返回被移除的oldChild。
5.修改DOM元素的属性
在实际的操作中,我们的流程往往是:通过获取一些属性,然后控制在某种条件下,修改元素的属性(比如通过获取当前页面滚动的高度的值,控制当滚动值大于某个固定值时,修改某些元素的属性)。这就涉及到很多API,下面我就将一些我经常使用到的:
HTMLElement.style
:获取和设置元素的内联样式,CSS样式表中的样式是不可查也不可设置的。因为此方法将行为和样式混合在一起,所以不推荐大量使用,而取而代之用增删类名来实现样式的切换。Node.textContent
:返回节点中的所有后代中的文本内容Element.className
:返回节点中的class属性的字符串,会保留所有空白符Element.classList
:返回一个包含所有节点中的所具有的类(去除空白符)的DOMTokenList对象,这种类型的对象有一些实用的方法,比如常用增删改查操作,以及toggle操作(删除则返回false,添加返回true)等等。但是浏览器的兼容性不太好,为IE10+。-
因为借助
Element.classList
的remove方法移除类名的兼容性不好,所以我们可以自己写一个移除的polyfill/shim,来适配那些早版本的浏览器:function removeClass(el, className) { if (el.classList) el.classList.remove(className) else { var reg = new RegExp('(\\s|^)' + className + '(\\s|$)'); if (el.className.match(reg) ) //如果存在该类名 el.className = el.className.replace(reg, ' '); // 删除该类名 } }
Node.hasChildNodes()
:判断是否有儿子节点Node.contains()
/Node.hasChildNodes()
:返回一个布尔值表示其后代节点中是否含有某个节点,前者兼容性为IE5+(在IE中只能检查元素节点),后者为IE7+;Element.attributes
:返回一个包含所有节点属性的NameNodeMap对象,可以通过该对象length属性用下标遍历每个属性。Element.getAttribute()
:返回传入的属性名词对应的属性的值,不存在则返回nullElement.hasAttribute()
:返回一个表示当前元素是否有该属性的布尔值,兼容性为IE8+Element.setAttribute(name, value)
:设置一个属性和属性值,如果已存在则更新该值。Element.removeAttribute(attrName)
:移除一个属性。
和元素节点定位相关的:
HTMLElement.offsetTop
,HTMLElement.offsetLeft
:返回HTML元素的边框距离最近的相对定位的元素的上部和左部的像素距离,返回一个数值,没有单位,HTMLElement.offsetWidth
,HTMLElement.offsetHeight
:返回HTML元素的宽度和高度,包括边框,内边距,滚动条。返回一个数值。Element.clientTop
,Element.clientLeft
:前者表示元素上边框的像素大小,后者表示元素的外边距内部到内边距外部的像素大小(因为不光有边框,当文字方向从右向左时,左边还可能有滚动条)Element.clientWidth
,Element.clientHeight
:对于有有效宽度的盒模型(不包括inline和没有指定宽度的元素)来说,代表content区域和padding区域(不包括滚动栏)的宽度和高度。Element.scrollTop
,Element.scrollLeft
:读取或设置元素被滚动的位置(上或左)的像素大小,始终是一个正数或者0,当页面缩放时可能是一个小数。(通俗点讲,就是在一个可以滚动的元素向下滚动了多少,或者向右滚动了多少)Element.scrollWidth
,Element.scrollHeight
:返回表示当前元素的实际内容(包括content和padding)的宽度和高度。(包括因为滚动栏内未展现出来的内容。)
本文所讲的DOM操作很明显都涉及了几种继承了Node对象的对象,如Document,HTMLElement,Element,ParentNode等等,所以我们就是利用这些组成DOM的对象,来完成对DOM的操作。每种对象类型,都可以在Mozilla官方文档中找到其定义,以及具有的方法和属性。