原文发布在我的独立博客上 ~: 从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
对象getElementsByName('myName')
获取标记了name属性为myName的元素,它的返回值是NodeList
对象getElementById('myId')
获取唯一id属性为myId的元素。有趣的是,当访问该元素的constructor.name属性时,可以得到不同的值。form元素对应HTMLFormElement对象, main标签则对应HTMLElement对象,这应该是从面向对象的角度看,不同类型的元素属于不同的对象实例。querySelector
和querySelectorAll
方法是HTML5新增的Web API,它们接受selector参数,selector正是我们常用的CSS选择器。不同之处在于,querySelector('form')返回的是页面中的第一个'form'元素,而querySelectorAll('form')返回NodeList
类型,它们是所有form的列表。
NodeList与HTMLCollection
接着来看这两个对象,它们都由多个元素组成一个“列表”,或者说“数组”,我们也可以像使用数组一样方便地用下标访问单个元素。但是它们仅仅是Array-like,并没有Array对象的其他常用方法,比如forEach.
javascript
var 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>
javascript
mydiv = 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属性就是它的类型。
图片截自 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
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。