NodeList 与 HTMLCollection

2024/01/23
共 1.4k 字
约 5 分钟
归档: 技术

这一次,搞清楚 querySelector 与 getElementById 的区别


现在大家都使用 vue 这样的框架,有了双向绑定后,很少再需要写获取页面中某个元素的代码了。虽然如此,但写一些小玩意的时候,原生的 js 还是用得很多的,自带且效率高。

我通常用 querySelector,因为它能接收所有的 CSS 选择器;偶尔也会用 getElementById,但给元素加个 id 多少有些不方便。获取元素后90%的概率是做监听,两者都能实现我的需求,一直很疑惑,他们这么相似,有什么区别?

在 CSS 里,class 选择器的效率要比 id 选择器高

而在 JavaScript 里则相反,用 querySelector,id 选择器效率更高

querySelector vs getElementsBy*

什么时候获得的是 NodeList/HTMLCollection/Node ?

要回答清楚这个问题很难,规范在迭代,有些浏览器也不一定遵循规范,导致非常混乱。网上众说纷纭,不如自己写个简单的代码测试一下,以下是 Chrome 120 上的结果:

function check(node) {
    if (node instanceof NodeList) {
        console.log("This is a NodeList");
    } else if (node instanceof HTMLCollection) {
        console.log("This is a HTMLCollection");
    } else if (node instanceof Node) {
        console.log("This is a Node");
    }
}

const ele1 = document.querySelector('.title'); // Node
const ele2 = document.querySelectorAll('.title'); // NodeList

const ele3 = document.getElementById('heading'); // Node
const ele4 = document.getElementsByClassName('title'); // HTMLCollection
const ele5 = document.getElementsByTagName('h1'); // HTMLCollection

check(ele1);
// 省略。。。

由结果可见:

  • querySelectorgetElementById,都是返回 Node

  • querySelectorAll 返回 NodeList

  • getElementsBy* 返回 HTMLCollection

效率比较

写一个测试程序也很简单,从结果来看,getElementsBy* 比 querySelectorAll 要快许多,
getElementById 也比 querySelector 更快一些:

console.time('querySelectorAll');
for (let i = 0; i < 1000; i++) {
    document.querySelectorAll('.title');
}
console.timeEnd('querySelectorAll');

console.time('getElementsByClassName');
for (let i = 0; i < 1000; i++) {
    document.getElementsByClassName('title');
}
console.timeEnd('getElementsByClassName');

// querySelectorAll: 0.30810546875 ms
// getElementsByClassName: 0.06396484375 ms
console.time('querySelector');
for (let i = 0; i < 1000; i++) {
    document.querySelector('#heading');
}
console.timeEnd('querySelector');

console.time('getElementById');
for (let i = 0; i < 1000; i++) {
    document.getElementById('heading');
}
console.timeEnd('getElementById');

// querySelector: 0.1240234375 ms
// getElementById: 0.0419921875 ms

转换成数组

需要注意的是,NodeList 与 HTMLCollection 都是类数组,
NodeList 可以使用 forEach 方法,而 HTMLCollection 不行。稳妥起见,想要遍历时,最好还是先转换成数组,方法有很多:

// 最常见的方法
const elements = Array.from(nodeList);

// 使用 ES6 扩展运算符
const elements = [...nodeList]

// 花里胡哨的方法
const elements = Array.apply(null, nodeList);
const elements = Array.prototype.slice.call(nodeList);
const elements = Array.prototype.slice.apply(nodeList);
const elements = Array.prototype.slice.bind(nodeList)();

HTMLCollection vs NodeList

元素的集合 vs 节点的集合

见得最多的就是 HTMLCollection 是动态集合,而 NodeList 是静态集合。

<ul class="ul">
    <li>One</li>
</ul>
const ul = document.querySelector('.ul');
const li = document.querySelectorAll('li');
console.log('li.length: ', li.length); // 1
const ele = document.createElement('li');
ul.appendChild(ele);
console.log('li.length: ', li.length); // 1
const ul = document.getElementsByClassName('ul')[0];
const li = document.getElementsByTagName('li');
console.log('li.length: ', li.length);
const ele = document.createElement('li'); // 1
ul.appendChild(ele);
console.log('li.length: ', li.length); // 2

HTMLCollection 属性和方法

  • length(只读)- 返回集合当中子元素的数目。

  • .item() - 根据给定的索引(从 0 开始),返回具体的节点。如果索引超出了范围,则返回 null。

  • .namedItem() - 根据 ID 返回指定节点,若不存在,则根据字符串所表示的 name 属性来匹配。根据 name 匹配只能作为最后的依赖,并且只有当被引用的元素支持 name 属性时才能被匹配。如果不存在符合给定 name 的节点,则返回 null。

NodeList 属性和方法

  • length - 节点个数。

  • .item() - 返回 NodeList 对象中指定索引的节点,如果索引越界,则返回null。等价的写法是 nodeList[i],不过,在这种情况下,越界访问将返回 undefined。

  • .forEach() - 用法与数组实例的 forEach 方法完全一致

  • .entries() - 遍历器,同时包含键名和键值

  • .keys() - 遍历器,只有键名

  • .values() - 遍历器,只有键值

.keys()/values()/entries() 这三个方法都返回一个 ES6 的遍历器对象,可以通过 for…of 循环遍历

CSS.escape()

顺便讲讲 CSS.escape()

假如现在有一个 uuid 作为元素的 id(我确实遇到过这样的案例),用 getElementById 是没有问题的,但当你用 querySelector,则会报错

let id = '30356954-44D9-6C43-FA93-5A7C03CB1FA1';
const eleById = document.getElementById(id);
// 找不到也就返回 null

const eleByQ = document.querySelector("#" + id);
// VM57:1 Uncaught DOMException: Failed to execute 'querySelector' on 'Document': '#30356954-44D9-6C43-FA93-5A7C03CB1FA1' is not a valid selector.

这是因为在 CSS 选择器中,标识符(包括选择器中的元素名称、类和ID)只能包含字符 [a-zA-Z0-9],以及连字符 - 和下划线 _。不能以数字、两个连字符或一个连字符后跟数字开头。而在 HTML 选择器中,则没有这个限制。

可以用属性选择器解决:

const eleByQ = document.querySelector(`[id='${id}']`);

而更好的方法是使用 CSS.escape() 把字符串转换成选择器使用:

const eleByQ = document.querySelector(`#${CSS.escape(id)}`);

CSS.escape() 是一个静态方法,它返回一个 DOMString,包含作为参数传递的转义字符串,主要用作 CSS 选择器的一部分。这个方法在处理 CSS 选择器时非常有用,因为某些字符在 CSS 选择器中有特殊的含义,需要被转义。

总结

简单地说,现在更推荐用 querySelector,可能效率稍差,但胜在灵活,可以传入任意的 CSS 选择器,代码长度也短,用起来方便才是王道。

参考资料:现代 JavaScript 教程Difference between getElementByID and querySelector

留言

本站已运行
© 2024 Jack  由 Hexo 驱动
复制成功