深入解析JavaScript中for循环与for...in循环的性能优化及陷阱

作者:听澜时间:2026-10-29 17:08:23浏览:83

本文详细对比JavaScript中for循环与for...in循环的使用场景与差异。重点解析在遍历HTMLCollection等实时DOM集合时,缓存数组长度对提升循环性能的关键作用。同时深入探讨for...in循环遍历原型链属性的潜在风险,并提供使用hasOwnProperty()及Object.prototype.hasOwnProperty.call()进行安全过滤的最佳实践,帮助开发者写出更高效、安全的迭代代码。

JavaScript迭代方式概述

在JavaScript开发中,迭代对象通常有两种主要方式:

  • 基础的for循环;
  • 以及for...in循环;

for循环的性能优化策略

一、for循环

这种基础写法存在以下不足:

主要问题在于每次迭代时都需要重新获取数组的长度; 此外,循环的终止条件需要明确指定; 在for循环中,你可以遍历数组或类数组对象(如arguments和HTMLCollection对象)。常见的循环写法如下:


// 次佳的循环
for (var i = 0; i < myarray.length; i++) {
 // 使用myarray[i]做点什么
}

上述写法的缺点在于,每次循环迭代时都要重新计算数组长度。这会降低代码执行性能,特别是当myarray不是普通数组,而是HTMLCollection对象时,性能损耗更为明显。

HTMLCollection是指由DOM方法返回的集合对象,例如:

document.getElementsByName() document.getElementsByClassName() document.getElementsByTagName()

此外,还有一些在DOM标准制定前引入并沿用至今的HTMLCollections,包括:

document.images: 页面中所有图片元素 document.links : 所有超链接元素 document.forms : 页面中所有表单 document.forms[0].elements : 第一个表单内的所有输入域

这些集合的麻烦之处在于它们会实时查询底层文档(HTML页面)。这意味着每次你访问集合的length属性时,都会触发一次对DOM的实时查询,而DOM操作通常开销较大。

因此,在循环获取值时,缓存数组(或集合)的长度是一种更优的做法,如下面代码所示:


for (var i = 0, max = myarray.length; i < max; i++) {
 // 使用myarray[i]做点什么
}

通过这种方式,在循环过程中,你只需要检索一次长度值。

在所有浏览器中,循环时缓存HTMLCollections的长度都能显著提升速度,性能提升幅度在2倍(Safari3)到190倍(IE7)之间。//此数据貌似很老

需要注意的是,当你明确需要在循环中修改集合(例如动态添加更多DOM元素)时,你可能希望长度值能实时更新,而不是使用常量。

结合单var声明形式,你可以将变量提取到循环外部,如下所示:


function looper() {
 var i = 0,
  max,
  myarray = [];
 // ...
 for (i = 0, max = myarray.length; i < max; i++) {
  // 使用myarray[i]做点什么
 }
}

这种形式的好处是保持一致性,因为你遵循了单var声明风格。其不足在于代码重构时,复制和粘贴整个循环会变得困难。例如,当你将一个函数中的循环复制到另一个函数时,你必须确保能将i和max引入新函数(如果在新函数中未使用,则可能需要从原函数中删除它们)。

最后,需要对循环进行的调整是使用以下表达式之一替换i++。


i = i + 1
i += 1

JSLint提示这样做,原因是++和--促进了“过分棘手(excessive trickiness)”。如果你直接忽略它,JSLint的plusplus选项将设为false(默认值为default)。

两种变化形式:

  • 减少一个变量(无需max)
  • 递减至0通常更快,因为与0进行比较比与数组长度或其他非0值比较更有效率01

//第一种变化的形式:

var i, myarray = [];
for (i = myarray.length; i–-;) {
 // 使用myarray[i]做点什么
}

//第二种使用while循环:

var myarray = [],
 i = myarray.length;
while (i–-) {
 // 使用myarray[i]做点什么
}

这些微小的改进仅体现在性能上,此外JSLint会对使用i--加以抱怨。

for...in循环与原型链陷阱

二、for …in 循环—也被称为“枚举”

for …in 循环常用于迭代对象的属性或数组的每个元素。在for…in循环中,循环计数器是字符串,而不是数字,它包含当前属性的名称或当前数组元素的索引。下面直接给出几个例子:

当遍历一个对象时,变量 i 即循环计数器代表对象的属性名:


//使用for..in循环遍历对象属性 
varperson={ 
 name: "Admin", 
 age: 21, 
 address:"shandong" 
}; 
for(var i in person){ 
 console.log(i); 
} 

执行结果为:

name

age

address

当遍历一个数组时,变量 i 即循环计数器代表当前数组元素的索引:


//使用for..in循环遍历数组 
vararray = ["admin","manager","db"] 
for(vari in array){ 
 console.log(i); 
} 

执行结果:

0

1

2

然而,现在看来for .. in循环似乎挺好用的,但别高兴得太早,请看下面的例子:


var array =["admin","manager","db"]; 
//给Array的原型添加一个name属性 
Array.prototype.name= "zhangsan"; 
for(var i in array){ 
 alert(array[i]); 
} 

运行结果:

admin

manager

db

zhangsan 咦,奇怪了,怎么平白无故地冒出来一个zhangsan 现在,再看看使用for循环会怎样?


vararray = ["admin","manager","db"]; 
//给Array的原型添加一个name属性 
Array.prototype.name = "zhangsan"; 
for(var i =0 ; i<array.length; i++){ 
 alert(array[i]); 
}; 

运行结果:

admin

manager

db 哦,现在明白了,for..in循环会把某个类型的原型(prototype)中的方法与属性也给遍历出来,这可能会导致代码中出现意外的错误。为了避免这个问题,我们可以使用对象的hasOwnProperty()方法来过滤,如果对象的属性或方法是非继承的,那么hasOwnProperty()方法返回true。即这里的检查不涉及从其他对象继承的属性和方法,只会检查在特定对象自身中直接创建的属性。


vararray = ["admin","manager","db"]; 
Array.prototype.name= "zhangshan"; 
for(var i in array){ 
//如果不是该对象自身直接创建的属性(也就是该属//性是原型中的属性),则跳过显示 
 if(array.hasOwnProperty(i)){ 
  alert(array[i]); 
  }
} 

运行结果:

admin

manager

db 另一种使用hasOwnProperty()的形式是直接引用Object.prototype上的方法,如下所示:


// 对象
var man = {
 hands: 2,
 legs: 2,
 heads: 1
};
for (var i in man) {
 if (Object.prototype.hasOwnProperty.call(man, i)) { // 过滤
  console.log(i, ":", man[i]);
 }
}

其好处在于,当man对象重新定义hasOwnProperty时,可以避免命名冲突。同时也避免了长属性查找对象的所有方法,你可以使用局部变量“缓存”它。


var i, hasOwn = Object.prototype.hasOwnProperty;
for (i in man) {
 if (hasOwn.call(man, i)) { // 过滤
  console.log(i, ":", man[i]);
 }
}

严格来说,不使用hasOwnProperty()并不构成错误。根据任务需求以及你对代码的自信程度,你可以跳过它以换取些许的循环速度提升。但是,当你对当前对象内容(及其原型链)不确定时,添加hasOwnProperty()检查会更加保险。

格式化的变化(这通不过JSLint检测)会直接忽略掉花括号,把if语句放到同一行上。其优点在于循环语句读起来像一个完整的想法(每个元素都有一个自己的属性”X”,使用”X”干点什么):


// 警告: 通不过JSLint检测
var i, hasOwn = Object.prototype.hasOwnProperty;
for (i in man) if (hasOwn.call(man, i)) { // 过滤
 console.log(i, ":", man[i]);
}

以上就是介绍了JavaScript提供的两种方式迭代对象:for循环和for...in循环,希望这篇文章对大家的学习有所帮助。

作者最新文章