为何不推荐在 JavaScript 中使用 for...in

在 JavaScript 中,经常会对对象和数组进行循环操作。但是,尽管 for...in 是一个用于遍历对象可枚举属性的方法,但在实际开发中却常常被建议避免使用。

for...in 主要用于遍历对象的可枚举属性(除了 Symbol),包括继承的可枚举属性。这个方法用于获取对象的属性。因为所有数据类型的祖先都是 object 类型,所以也可以用 for...in 来遍历数组。然而,在实际使用中存在一些问题。

对象扩展属性

不论是 Array 还是 Object,通过 prototype 扩展属性后,使用 for...in 遍历时会将扩展属性一并遍历出来。这可能导致异常情况,尤其是当第三方对原始类型对象进行扩展后,使用插件时出现异常。

javascript 复制代码
Object.prototype.abc = {}
Array.prototype.abc = {}

const arr = [1,2]
const obj = {
  a: 0,
  b: 1
}

for(const key in arr) {
  console.log(key) // 0 1 abc 返回的是数组的索引
}

for(const key in obj) {
  console.log(key) // a b abc 返回的是属性
}

若对遍历的产物有后续的处理,则可能会出现错误

因此在编写插件的时候不推荐使用 for...in 用于遍历,避免第三方对原始类型对象进行扩展后,使用插件时出现异常

任意顺序的数组

在特定情况下,for...in 的行为可能与预期不符。比如,创建一个任意顺序的数组,在 JScript(IE <= 8)上通过 for...in 获取到的将会和数组本身的序列不一致

php 复制代码
var array = [];
array[2] = 'c';
array[1] = 'b';
array[0] = 'a';

for (var key in array) {
  //... key will be "2", "1" and "0" on JScript
}

以上场景只在 IE <= 8 上才会出现,因此在现有的浏览器中基本上不会出现以上问题

任意数量的数组

创建一个随机数量元素的数组,for...in 将会过滤掉未设置下标的元素,与 for 的结果不一致

css 复制代码
let a = []; 
a[5] = 5;   
for (let i = 0; i < a.length; i++) {
    console.log(a[i]);
}

/* 
   undefined
   undefined
   undefined
   undefined
   undefined
   5
*/

for (let key in a) {
    console.log(key); // 5
}

但在有大量数据处理时,上述场景的 for...in 将会比 for 节省了更多的计算耗时

性能

因为上面的任意数量的数组场景里涉及到了性能,那么对比一下 for...in 和常规的 for 循环 100万数据量的数组的性能

ini 复制代码
let a = new Array(100*10000);
a.fill(1)

// 执行10次,减少其他因素的影响
for(let n = 0; n < 10; n++) {
  console.time('for-in');
  let sumIn = 0
  for (let key in a) {
      sumIn += key
  }
  console.timeEnd('for-in');
}

for(let n = 0; n < 10; n++) {
  console.time('for');
  let len = a.length
  sumIn = 0
  for (let i = 0; i < len; i++) {
      sumIn += i
  }
  console.timeEnd('for');
}

通过上面的方式使用 for 和 for...in 分别遍历计算一个100万数据量的数组,计算其运行的耗时,在 Chrome 120.0.6099.110 浏览器中运行的结果如下

通过上面的数据可以看出,在对大数据量的数组进行遍历时,for...in 的性能相比 for 要慢十倍不止,所以在对有数值的大数据量数组进行遍历时,也不是很推荐使用 for...in

既然是考虑性能,那就将所有的循环方式都进行测试一下,为了更直观一点,将每个循环的测试次数调整为 50 次,生成一个可视化图

通过上面的图可以看出,除了 for-in,其他的循环函数耗时都差不多

一定要用?

一定要用 for...in ?若想要避免循环过程中因为对原始类型扩展属性造成的影响,可通过 hasOwnProperty 来处理

scss 复制代码
for(const key in arr) {
  if (arr.hasOwnProperty(key)) {
  	console.log(key) // 0 1 
  }
}

for(const key in obj) {
  if (obj.hasOwnProperty(key)) {
  	console.log(key) // a b 
  }
}

但谁又能确定 Object 上的 hasOwnProperty 是否有被改写?虽然被改写的概率很小

总结

通过上面的介绍

遍历数组时:追求性能推荐用 for, 兼顾可读性跟性能推荐用 forEach、map 和 reduce

编译对象时:可结合 Object.keys、Object.entries、Object.values 与遍历数组的方式

如 Object.keys 结合forEach

ini 复制代码
const keys = Object.keys(obj)
let sum = 0
keys.forEach(key => {
  sum += obj[key]
})

也可以结合 Object.values 与 forEach 直接获取对象属性值

ini 复制代码
const values = Object.values(obj)
let sum = 0
values.forEach(value => {
  sum += value
})

在编写代码时,可以根据需求选择合适的循环方式,并充分考虑性能和可读性的平衡。

相关推荐
To_OC1 小时前
别再串行写 await 了,Promise.all 才是并行请求的正确打开方式
前端·javascript·promise
To_OC1 小时前
LC 17 电话号码的字母组合:我的回溯算法,就是从这道题开窍的
javascript·算法·leetcode
vipbic1 小时前
中后台越做越乱后,我用插件化把它救回来了
前端·vue.js
Hyyy1 小时前
Computer Use 适合做什么,不适合做什么——一次真实使用后的思考
前端
小和尚同志2 小时前
前端 AI 单元测试思考与落地
前端·人工智能·aigc
invicinble3 小时前
c端系统,其实更像一个信息展示平台
前端
你怎么知道我是队长3 小时前
JavaScript的变量和数据类型介绍
开发语言·javascript·ecmascript
李姆斯4 小时前
管理是否可以被完全量化
前端·产品经理·团队管理
名字还没想好☜4 小时前
Next.js 中间件实战:鉴权、重定向与 A/B 分流
开发语言·前端·javascript·中间件·react·next.js
广州灵眸科技有限公司5 小时前
瑞芯微RV1126B开发板(EASY-EAI-PI2) INI文件操作
java·前端·javascript·网络·人工智能