先记住这个答案
getElementById 只接受 ID 字符串,直接返回对应元素或 null,性能好但功能单一。querySelector 接受任意 CSS 选择器,返回文档中匹配的第一个元素,无匹配也返回 null;若选择器非法会抛 SyntaxError。querySelector 更灵活,但需要注意 ID 值中的特殊字符需转义。
- getElementById 仅按 ID 精确查找。
- querySelector 支持任意 CSS 选择器。
- 选择器无效时 querySelector 抛异常。
查找机制与匹配策略的差异
getElementById 的实现基于文档的 ID 索引,浏览器维护一个哈希表,将 ID 映射到元素。调用时直接查表,返回对应元素或 null。匹配过程不涉及 CSS 解析,因此速度较快,且不会因选择器语法出错。注意,HTML 规范要求 ID 唯一,但若文档中存在重复 ID,getElementById 返回第一个在树中出现的元素,行为与 querySelector 一致。
querySelector 需要将传入的字符串解析为 CSS 选择器,然后按照 CSS 选择器的匹配规则,在文档中进行深度优先的前序遍历,返回第一个匹配的元素。这个过程涉及复杂的匹配逻辑,尤其当选择器包含后代、属性、伪类等时。此外,querySelector 对选择器字符串有严格语法要求,任何非法输入都会抛出 SyntaxError,而 getElementById 不会。
表单校验场景中的选择策略
假设页面有一个表单,其中包含多个输入框,但我们需要获取 name 属性为 'username' 的那个。如果用 getElementById,必须保证该输入框有唯一的 id,这要求在 HTML 中额外添加 id 属性。而 querySelector 可以直接写 form input[name='username'],无需修改 HTML 结构。如果存在多个匹配,querySelector 返回第一个。实际处理:我们决定使用 querySelector,因为它能利用现有属性定位,保持标记整洁。
但需注意,如果输入框的 name 值包含特殊字符,比如 'user[name]',在选择器中用引号包裹属性值即可,如 form input[name="user[name]"],这样方括号会被解析为字面量,无需额外转义。如果未使用引号直接写 form input[name=user[name]] 才会解析失败。场景中 name 是简单字符串,所以没问题。选择 querySelector 后,如果表单动态增加输入框,只要第一次调用时元素存在,就能正确获得引用;但如果随后删除并重建了该输入框,缓存引用会失效,需要重新查询。
动态文档与选择器转义的边界
当页面通过脚本不断增删元素时,getElementById 和 querySelector 返回的引用都不会自动更新:它们都是查询时刻的快照。如果你保存了引用,之后元素被移除,引用依然指向那个已脱离文档的节点;只有重新调用方法才能获得新的引用。对于 querySelector,如果选择器依赖动态添加的类名或属性,而元素尚未插入,就会返回 null。因此,在 SPA 中,依赖用户交互后出现的元素时,需要在合适的生命周期阶段查询。
另一个边界是 ID 值并非合法 CSS 标识符。例如 id="a.b",直接 querySelector('#a.b') 会把 a 当成 ID,把 b 当成类,导致匹配失败;getElementById('a.b') 则直接命中。处理方式是使用 CSS.escape('a.b') 得到 a\.b,再拼接选择器;或者优先使用 getElementById。这个转义成本在动态生成 ID 时容易忽视,容易引发难以调试的问题。
容易答错的地方
- querySelector 更慢所以不用
- 误以为 querySelector 总是比 getElementById 慢,其实性能差异在多数页面微不足道。真正应该关注的是选择器复杂度。如果只是按 ID 找元素,getElementById 稍快且代码简洁;若需要复杂查找,querySelector 节省的代码可维护性远超那次查找的耗时。
- querySelector 返回所有匹配元素
- 错误认为 querySelector 返回所有匹配元素的集合。实际上它只返回第一个匹配的元素,与 getElementById 类似。若想获取所有匹配,必须使用 querySelectorAll,后者返回静态 NodeList。这个混淆常导致代码只处理到首个元素而遗漏其余。
面试官还会怎么问?
querySelectorAll 返回的 NodeList 是动态的吗?
NodeList 是静态的,不会随 DOM 变化自动更新。而 getElementsByClassName 等返回的 HTMLCollection 是动态的。如果需要在元素变化后同步列表,需要重新调用方法。
ID 包含冒号时如何用 querySelector?
冒号在 CSS 中用于伪类,直接写会解析错误。需用反斜杠转义,如 #foo\:bar,或使用 CSS.escape('foo:bar') 生成转义后的字符串。getElementById 无需转义,直接传原 ID 即可。
为什么 getElementById 无法使用类名?
getElementById 的设计就是专用于 ID 查找,内部利用 ID 索引,不解析 CSS。类名查找应该用 getElementsByClassName 或 querySelector。浏览器 API 设计职责分离,避免将两种逻辑混杂。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。