温故知新——DOM操作之获取节点

467 阅读5分钟

DOM 是 JavaScript 操作网页的接口,全称为“文档对象模型”(Document Object Model)。它的作用是将网页转为一个 JavaScript 对象,从而可以用脚本进行各种操作

针对节点进行操作的第一步当然就是获取节点,目前已经衍生出了很多获取节点的方案

我们先写一段简单的 HTML 代码,

<body>
  <header id="title" class="main-title">
    <span class="text-heading">Hello,</span>
    <span class="text-heading name">Raaabbit</span>
  </header>
</body>

后面的代码就基于上面的 demo 展开

针对节点进行操作的第一步当然就是获取节点,目前已经衍生出了很多获取节点的方案

通过元素选择器获取

元素选择器相关的内容可以参考我的博客,CSS选择器一节

这种方法主要在于通过元素的 id、class 和标签名获取元素,常用以下三个方法:

  • getElementById()
  • getElementsByClassName()
  • getElementsByTagName()

⚠️注意: 这些方法既可以用于HTML文档对象,也可以用于element元素对象

下面分别进行详细介绍:

getElementById

let title = document.getElementById('title');

按照规范在 HTML 中,id 是唯一的,所以通过这样的方式可以获得唯一的元素,事实上若浏览器中出现多个id名的情况,CSS样式对所有该id名的元素都生效,但这个方法仅对第一个出现该id名的元素生效

getElementsByClassName

let textHeadings = document.getElementsByClassName('text-heading');
console.log(textHeadings[1]);

我们可以很容易通过这个方法名看出来,elements 就可以体现出,这个方法获得的不是单个节点,而是一个类数组对象 HTMLCollection,我们可以通过下标访问其中的每一个节点

⚠️注意: 我们可以用多个类名作为参数,类名的先后顺序不重要,如:

// 只有同时有类名 text-heading 和 name 时才可以获取到
let textHeadings = document.getElementsByClassName('text-heading name');

由于这个方法不支持 IE9 以下浏览器,所以需要使用兼容的写法:

function getElementsByClassName(node,className){
  if(node.getElementsByClassName){                    //特性侦测
    return node.getElementsByClassName(className);  //优先使用W3C规范
  }
  else{
    let results = new Array();
    let elems = node.getElementsByTagName("*");     //获取所有后代元素
    for(let i = 0;i < elems.length;i++){
      if(elems[i].className.indexOf(classname) != -1){
        results[results.length] = elems[i];     //扩容
      }
    }
    return results;
  }
}

getElementsByTagName

在上面给出的兼容方法中已经使用到了这个方法,getElementsByTagName 方法接收一个参数,即要取得元素的标签名,返回的也是类数组对象 HTMLCollection

let spans = document.getElementsByTagName('span');

通过 selector 获取

相信很多人都使用或者学习过 JQuery,其中有一个很方便的获取元素节点的方法 $,比如我们可以通过 $('.text-heading') 这样的方式快速获取节点,这里的节点是包装后的 JQuery 对象,并不是原生的 DOM 节点

而 HTML5 中添加了新的方法,通过 CSS 选择器更加灵活地获取节点

querySelector

querySelector 方法接收一个 CSS 选择器,返回与该模式匹配的第一个元素,如果没有找到匹配的元素,返回null

该方法既可用于文档document类型,也可用于元素element类型

let body = document.querySelector("body");

querySelectorAll

querySelectorAll 同样接收一个CSS选择符,和上一个方法的区别在于返回一个类数组对象 NodeList

let spans = document.querySelectorAll(".text-heading");

这两个方法有一个很重要的特点,他们的返回值不是动态的,更像是一个快照,我们先调用 querySelectorAll 方法获取节点后,对相关的节点进行添加/删除/修改操作后,这个值并不改变:

// 本文最上方的例子
let spans = document.querySelectorAll("span");
console.log(spans.length); // 2
let newSpan = document.createElement('span');
document.getElementById('title').appendChild(newSpan);

console.log(spans.length); // 2,实际上现在已经有 3 个 span了

通过节点关系获取

有的时候我们不仅需要对节点进行操作,还需要连带操作它的父子/兄弟节点,我们也可以调用以下方法;

  • 父子关系:
    • parentNode/parentElement
    • firstChild/lastChild
    • childNodes/childern
  • 兄弟关系
    • previousSibling/nextSibling
    • previousElementSibling/nextElementSibling

使用例子如下:

let title = document.getElementsByClassName('main-title')[0];
console.log("firstChild ==> ", title.firstChild);
console.log("firstElementChild ==> ", title.firstElementChild);
console.log("childNodes ==> ", title.childNodes);
console.log("children ==> ", title.children);
let textHeading0 = document.getElementsByClassName('text-heading')[0];
console.log("parentElement ==> ", textHeading0.parentElement);
console.log("parentNode ==> ", textHeading0.parentNode);
console.log("nextSibling ==> ", textHeading0.nextSibling);
console.log("nextElementSibling ==> ", textHeading0.nextElementSibling);

结果如下:

由于随着目前前端的交互性越来越强,页面上元素变化较大,这些方法有比较大的局限性,可维护性比较差

从另一个角度来说,有时我们不知道要获取的元素的 id、class 甚至不知道 tag,通过节点关系获取元素也是一种不错的方案

总结

本文中列出的一些方法特性如下,多种方法结合使用效果更佳~

name only document sole(唯一的) live(动态的)
getElementById * *
getElementsByTagName *
getElementsByClassName *
querySelectorAll
querySelector *

扩展内容 Node 节点和 Element 节点

通过上文中的一个图片(全文中好像只有一张图)我们可以看到,在获取节点或者节点列表中出现了两种不同的结果,分别是 NodeListHTMLCollection 这是两种不同的集合,成员分别是 Node 节点和 Element 节点

NodeList 实例对象是一个类数组对象,它的成员是节点对象,包括childNodes和querySelectorAll()方法返回值

HTMLCollection 集合包括getElementsByTagName()、getElementsByClassName()、getElementsByName()等方法的返回值,以及children

这两种集合的主要区别在于是否具有动态性(见上文)

Node 和 Element 具有继承关系,Element 继承了 Node 接口,因此Element 上具有 Node 的属性和方法

除了 element 之外 node 还包括很多类型,列举如下;

  • 元素节点:ELEMENT
  • 属性节点:ATTRIBUTE
  • 文本节点:TEXT
  • CDATA节点:CDATA_SECTION
  • 实体引用名称节点:ENTRY_REFERENCE
  • 实体名称节点:ENTITY
  • 处理指令节点:PROCESSING_INSTRUCTION
  • 注释节点:COMMENT
  • 文档节点:DOCUMENT
  • 文档类型节点:DOCUMENT_TYPE
  • 文档片段节点:DOCUMENT_FRAGMENT
  • DTD声明节点:NOTATION

这些不同的 node 类型具有共同的属性(nodeType、nodeName、nodeValue等)和方法,还有一些特有的属性和方法,在此不过多赘述