在 JavaScript 的性能优化传说中,流传着一个古老而著名的技巧:在 for 循环中缓存数组的 length 属性。
你一定见过这样的代码:
// “优化”前的代码
for (let i = 0; i < someArray.length; i++) {
// ... do something
}
// “优化”后的代码
for (let i = 0, len = someArray.length; i < len; i++) {
// ... do something
}
这个建议的逻辑很简单:每次循环都去访问 someArray.length 会产生额外的开销,不如用一个局部变量 len
把它存起来,这样可以提高循环性能。
那么, length 检查真的很慢吗?
对于标准数组, length 快得惊人
在现代 JavaScript 引擎中,访问 .length 的速度几乎是恒定的,因为它只是一个简单的属性读取,引擎内部直接存储了这个值。
所以,对于标准数组,以下代码在性能上几乎没有区别,但后者明显更简洁、更易读,也更符合现代 JavaScript 的编码风格:

对于 DOM 集合的 length
既然普通数组的 length 很快,那这个流传已久的优化技巧到底是从何而来的呢?
答案指向了浏览器环境中的一个特殊对象: ** DOM 集合 ** ,尤其是 **NodeList ** 和 **HTMLCollection ** 。
当我们使用 document.getElementsByTagName() 或 element.childNodes
这样的方法时,得到的不是一个真正的 JavaScript Array ,而是一个 ** 实时的集合 ** 。
每当我们访问它的 length 属性时,浏览器 ** 必须重新去查询 DOM ** ,计算符合条件的元素数量。
现在,想象一下这个可怕的场景:
const divs = document.getElementsByTagName('div'); // 这是一个实时的 HTMLCollection
// 每次循环,浏览器都会重新去 DOM 树里数一遍有多少个 div
for (let i = 0; i < divs.length; i++) {
// 假设我们在这里创建并插入一个新的 div
const newDiv = document.createElement('div');
document.body.appendChild(newDiv);
}
在这个例子中,每次循环都会发生:
- **
i < divs.length** :浏览器重新查询 DOM,计算divs.length - 循环体执行,一个新的
<div>被添加到document.body中 - 下一次循环,
divs.length的值变大了
这不仅会导致无限循环,还会在每次循环条件判断时,触发一次昂贵的 DOM 重查询。
正确姿势
现在我们知道了问题的根源。那么,正确的做法是什么?
** 将 DOM 集合转换为真正的数组 **
在对 DOM 集合进行复杂操作之前,先把它变成一个静态的、真正的 JavaScript 数组。既可避免 length
性能陷阱,还能使用所有数组方法( map , filter , 等)。
// 推荐!使用 Array.from()
const divs = Array.from(document.getElementsByTagName('div'));
// 或者使用展开语法
const divs = [...document.getElementsByTagName('div')];
// 现在 divs 是一个真正的数组了,可以安全、高效地遍历
divs.forEach(div => {
// ...
});
for (let i = 0; i < divs.length; i++) {
// 这里的 .length 访问非常快!
}
现代浏览器中, querySelectorAll() 返回的是静态的 NodeList ,其 length
不会动态变化。但 getElementsByTagName 等仍然返回实时集合。为了统一和安全,将所有 DOM
集合在使用前转换为数组是一个好习惯。
对于普通 Array 对象,不需要再手动缓存 length ,优先选择 for...of 或高阶函数(如 forEach , map )来提升代码可读性;对于 DOM,先转数组。