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

2023-12-21 11:33:57

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

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

对象扩展属性

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

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 获取到的将会和数组本身的序列不一致

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 的结果不一致

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万数据量的数组的性能

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 来处理

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

const keys = Object.keys(obj)
let sum = 0
keys.forEach(key => {
  sum += obj[key]
})

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

const values = Object.values(obj)
let sum = 0
values.forEach(value => {
  sum += value
})

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

文章来源:https://blog.csdn.net/QD_ANJING/article/details/135126040
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。