Dom操作

5 分钟读完

原生JavaScript在网页中扮演的是控制网页行为的角色。而控制网页行为,则是通过操作DOM来完成的。所有对DOM的操作可以理解为以下的步骤:

  1. 查找(或创建)要进行操作的DOM元素
  2. 对该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():返回传入的属性名词对应的属性的值,不存在则返回null
  • Element.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官方文档中找到其定义,以及具有的方法和属性。

更新时间: