为何不推荐在 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
})

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

相关推荐
Lupino6 分钟前
被 React “玩弄”的 24 小时:为了修一个不存在的 Bug,我给大模型送了顿火锅钱
前端·react.js
米丘13 分钟前
了解 Javascript 模块化,更好地掌握 Vite 、Webpack、Rollup 等打包工具
前端
Heo14 分钟前
深入 React19 Diff 算法
前端·javascript·面试
滕青山15 分钟前
个人所得税计算器 在线工具核心JS实现
前端·javascript·vue.js
小怪点点16 分钟前
手写promise
前端·promise
国思RDIF框架25 分钟前
RDIFramework.NET Web 敏捷开发框架 V6.3 发布 (.NET8+、Framework 双引擎)
前端
颜酱26 分钟前
从0到1实现LFU缓存:思路拆解+代码落地
javascript·后端·算法
Mintopia26 分钟前
如何在有限的时间里,活出几倍的人生
前端
炫饭第一名27 分钟前
速通Canvas指北🦮——变形、渐变与阴影篇
前端·javascript·程序员
Neptune128 分钟前
让我带你迅速吃透React组件通信:从入门到精通(上篇)
前端·javascript