2

原文发布在我的独立博客上 ~: 从DOM选择器的返回值说起

抛开大大解放生产力的jQuery,使用JS获取元素要使用getElementById方法,或类似的getElementsByTagName, getElementsByClassName,getElementsByName. 第一种情况下,根据ID获取时,返回值是唯一的元素;而根据TagName, ClassName 等获取时候,返回值是包含所有符合条件的多个元素的“列表”。此处“列表”要加双引号,是因为严格来说,JavaScript乃至DOM元素中并没有所谓“列表”或List的数据类型/对象,既然不能称为列表,那么它们到底是啥?

这篇blog就试图从getElement(s)的返回值说起,往上扯一些早就该了解,但总是似懂非懂的简单的DOM基础知识。

getElements方法的返回值

  • getElementsByClassName('myClass')获取指定类名为myClass的元素,getElementsByTagName('some-tag')获取标签为'some-tag'的元素,它们的返回值都是 HTMLCollection 对象
    Screen-20150510-006.png

  • getElementsByName('myName')获取标记了name属性为myName的元素,它的返回值是NodeList对象
    Screen-20150510-007.png

  • getElementById('myId')获取唯一id属性为myId的元素。有趣的是,当访问该元素的constructor.name属性时,可以得到不同的值。form元素对应HTMLFormElement对象, main标签则对应HTMLElement对象,这应该是从面向对象的角度看,不同类型的元素属于不同的对象实例。
    Screen-20150510-008.png

  • querySelectorquerySelectorAll方法是HTML5新增的Web API,它们接受selector参数,selector正是我们常用的CSS选择器。不同之处在于,querySelector('form')返回的是页面中的第一个'form'元素,而querySelectorAll('form')返回NodeList类型,它们是所有form的列表。
    Screen-20150510-010.png

NodeList与HTMLCollection

接着来看这两个对象,它们都由多个元素组成一个“列表”,或者说“数组”,我们也可以像使用数组一样方便地用下标访问单个元素。但是它们仅仅是Array-like,并没有Array对象的其他常用方法,比如forEach.

javascriptvar buttons = document.getElementsByTagName('button');
console.log(buttons[1]);  // 输出第2个button元素
console.log(buttons.forEach); // undefied
console.log(buttons.filter); //undefied

var next_nodes = document.getElementsByName('next');
console.log(next_nodes[0]); //输出列表中第1个元素
console.log(next_nodes.forEach);    //undefied

除了以上提到的getElements方法之外

  • NodeList也是Node.childNodes, document.querySelector的返回值类型
  • HTMLCollection也是Node.children, document.forms等的对象类型

在使用getElements方法时,NodeList和HTMLCollection好像并没什么不一一样的,但是从字面上讲,一个是节点列表,一个是HTML(元素)集合,并不是一回事。他们的不同可以从另外两个方法看出,它们是childNodes和children,下面是一个合适的例子。

Node.childNodes与Node.children的区别

html<div><!-- this is a comment -->
  text in div
  <a>Link</a>
  <strong>
    Strong Text
    <a>Strong Link</a>
  </strong>
</div>
javascriptmydiv = document.querySelector('div');
console.log(mydiv.children);    // 不含#text "text in div"和注释
console.log(mydiv.childNodes);  // 含文字"text in div"和注释
  • mydiv.children获得的,是<div>的子标签代表的元素,所以示例中的text in span并不属于span.children
  • mydiv.childNodes获得的,是包含文本内容在内的所有子节点。所谓节点,正是浏览器在构造DOM树的每一个不可或缺的元素,当然少不了必须的文本节点。

所以它们不同就在于一个获取的是元素,一个获取所有节点。

从Element到Node到DOM

到底元素和节点有什么不同,看原型链。

<a>元素为例,调用mydiv.children[0].constructor.name,可知<a>Link</a>元素的类型HTMLAnchorElement; HTMLAnchorElement的原型链为:

HTMLAnchorElement --> HTMLElement --> Element --> Node --> EventTarget --> Object

调用mydiv.childNodes[0].constructor.name,可知<!-- this is a comment -->节点的对象类型Comment,类似也可以得到文本节点的类型Text,它们的原型链为:

Comment --> CharacterData --> Node --> EventTarget --> Object
Text --> CharacterData -- Node -- EventTarget --> Object

看到原型链就可以豁然开朗了,Node是包含Element和Text, Comment在内的概念,而HTMLElement只是Node的一个子集。

除了上面示例的几个节点类型,Node包含的类型如下;对任意一个节点myNode,myNode.nodeType属性就是它的类型。
Screen-20150510-009.png

图片截自 MDN, thumb down图标代表废弃API,是不推荐使用的类型

终于扯到最基本的DOM概念了,DOM事关重大,然而原理其实也就是两句话的事儿。

DOM == Document Object Modle(文档结构模型),浏览器收到一个HTML页面后,根据页面结构构建一个DOM树,DOM树就是由不同类型的节点(Node)所组成的。在HTML文档中,一条注释语句是一个Node,一个HTML元素也是Node,甚至<!DOCTYPE html>也是一个Node。有了Node对象,一生二,二生三,三生万物;除了事件和ajax请求,前端编程说白了就剩下来使用浏览器提供的DOM API来对Node进行各种操作。

当然,观察Node的原型链,可以看到处于它上一层的是 EventTarget对象,这意味着Node都继承了EventTarget的属性和方法,最常见的当然是.addEventListener。这正是javascript在前端能够实现各种可能性的原因,一切节点都可以绑定事件


参考:
1. DOM概述 | MDN
2. Node - Web API Interfaces | MDN
3. NodeList - Web API Interfaces | MDN
4. HTML5中类jQuery选择器querySelector的使用 - SegmentFault


rianma
629 声望111 粉丝