for...in本身是Object的遍历方法,js中的数组也继承自Object,所以自然而然也能使用for...in遍历出属性。然而for...in有一些难以注意到的细节,稍不注意就可能被坑。
1. 细节一:遍历的的属性值是字符串,而不是数字!(相信初接触js的人都要被坑一次吧)
const list = [1, 2, 3] for (let i in list) { console.log(i, i + 1, typeof i) // 0 01 string }
可以看到typeof i的返回值是“string”,这个最坑的地方在于我们通过下标加减想取别的元素时,就会出现异常,像上述输出的i + 1一样,并不是数字相加,而是字符串拼接!
2.2. 细节二:遍历的是对象的枚举属性,包括自身属性以及原型链上的属性
const obj = { a: 'value_a', b: 'value_b' } Object.prototype.c = 'proto_value_c' Object.defineProperty(obj, 'd', { get() { return 'value_d' }, enumerable: false, }) for (let key in obj) { console.log(key, obj[key]) // a value_a // b value_b // c proto_value_c } // 可以看到,原型上的属性c也打印出来了,但是通过Object.defineProperty定义的不可枚举属性d // (enumerable: false)没有被遍历到。
3.3. 细节三:遍历顺序是对象属性的枚举顺序,并不一定按数组的下标顺序遍历
for...in的遍历顺序是枚举顺序,对于数组而言,规范并没有约束各浏览器的实现。因此即便在一定范围内是按顺序遍历的,也应该尽量不依赖for...in遍历的顺序。MDN文档也明确指出,不建议使用for...in遍历数组,特别是想按照索引顺序遍历的时候:
此外,因为有稀疏数组的存在,其实JS里的数组不一定是顺序结构存储的。当数组的键分布较为稀疏,为了充分节约空间,数组可能会退化为像对象一样的哈希表存储结构。
因为for...in本身是对象的遍历方法,并不适用于数组,对于数组,还是for...of、for循环、forEach等遍历比较好。