从TypeScript视角看HTML DOM(三):NodeList与HTMLCollection

“理解NodeList和HTMLCollection,是从整体上透彻理解DOM的关键所在。“

——《JavaScript高级程序设计》

解决上面的问题之后,我们有必要进一步深入一下。NodeList和HTMLCollection这两个类,在方法上的差异是毫无疑问的。按照Stack Overflow上的说法:

Both interfaces are collections of DOM nodes. They differ in the methods they provide and in the type of nodes they can contain. While a NodeList can contain any node type, an HTMLCollection is supposed to only contain Element nodes.

An HTMLCollection provides the same methods as a NodeList and additionally a method called namedItem.

Collections are always used when access has to be provided to multiple nodes, e.g. most selector methods (such as getElementsByTagName) return multiple nodes or getting a reference to all children (element.childNodes).

大概是说,HTMLCollection比NodeList多了一个namedItem方法;NodeList可以包含任何结点,而HTMLCollection只包含元素结点。

这些在前文已经提过了,而且有了TypeScript的类型,这些都是很显然的。说起来,这个item方法和方括号语法是等价的,倒有点像运算符重载……或者说,Python里的魔术方法?

事实上,NodeList还额外拥有一个forEach方法,可以用来遍历。

如何获得这两个集合?方法很多,就不一一列举了。主要来说,有getElementsByClassNamegetElementsByNamegetElementsByTagNamechildrenchildNodes,等等。最神秘的是他们的返回值大多不相同,所以用的时候一定要慎重。至于为什么会这样……大概还是历史遗留问题吧。还好我没有生在那个各大浏览器混战的时代。

此外,还有一个很特殊的地方。可能你之前已经注意到了,这两个集合是动态的,会随着DOM的改变而改变。但是有一个方法例外:querySelectorAll,这个方法返回的集合是静态的。但是,为什么呢?TypeScript也许可以给我们一个答案:

interface ParentNode {
	querySelector<K extends keyof HTMLElementTagNameMap>(selectors: K)
	: HTMLElementTagNameMap[K] | null;
	querySelectorAll<K extends keyof HTMLElementTagNameMap>(selectors: K)
	: NodeListOf<HTMLElementTagNameMap[K]>;
    // ...
}

interface HTMLElementTagNameMap {
    "a": HTMLAnchorElement;
    "body": HTMLBodyElement;
    "br": HTMLBRElement;
    "button": HTMLButtonElement;
    "div": HTMLDivElement;
    "h1": HTMLHeadingElement;
    "hr": HTMLHRElement;
    "html": HTMLHtmlElement;
    "img": HTMLImageElement;
    "input": HTMLInputElement;
    "li": HTMLLIElement;
    "p": HTMLParagraphElement;
    "span": HTMLSpanElement;
    "strong": HTMLElement;
    "style": HTMLStyleElement;
    "table": HTMLTableElement;
    "ul": HTMLUListElement;
	// ...
}

因为同类和重载的方法比较多,就不一一列举了,只找其中一个来看;原理反正是一样的(笑)。HTMLElementTagNameMap这个类其实就是一个标签到具体元素的映射。从方法中可以看出,querySelector方法的返回值是HTMLElementTagNameMap[K],也就是对应元素的引用,所以是动态的;而querySelectorAll返回的是NodeListOf<HTMLElementTagNameMap[K]>,经过了一次包装(有了一个包装对象!),保存的是当时状态的快照,所以是静态的。

因为这两者之前存在差异,而且动态改变这个特性大多数时候都很烦人,因为我们往往需要的是一个静态的副本(或者叫快照snapshot吧),所以我们需要一个解决措施。可能大家都知道,我们可以将这个集合转换成数组:

Array.prototype.slice.call(collection);

但是这个方法并不很优雅。感觉上好像有一个简单一点的写法:

[].slice.call(collection);

比刚才那个好一点。事实上,到了ES6,我们还可以这么写:

Array.from(collection);

这个已经很好了。但是还有更tricky的:

[...collection]

利用解构运算符来实现优雅的转换集合,可以说是很有意思了。

参考资料
  1. MDN-HTMLCollection

  2. MDN-Element

  3. Element和Node的区别你造吗?

  4. HTMLCollection vs. NodeList

  5. DOM中的NodeList与HTMLCollection

目录

从TypeScript视角看HTML DOM(一):前言

从TypeScript视角看HTML DOM(二):Node与Element

从TypeScript视角看HTML DOM(四):Event Flow

从TypeScript视角看HTML DOM(五):Event

从TypeScript视角看HTML DOM(六):Scripting

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值