可知造成for in缺陷的原因?

195 阅读4分钟

for in最初设计的使用场景是用来遍历对象,但自从for ofObject.keys()Object.values()被推出,for in连遍历对象最后一份蛋糕也抢去,彻底被新开发者所摒弃,被老开发者所遗忘,这也是必然的,for in存在许许多多的问题:性能差、键值访问混乱、会去访问原型、无法访问Symbol`属性等.....

造成for in这些缺点的原因是什么喃?以史为鉴可以正衣冠,了解这些知识还是很有必要的~~~

for in的使用

  ```js
  for (key in object)  { }
  ```
  • object:被迭代的对象(基本类型、数组、对象都可以被迭代)
  • key :对象被迭代时,每一项的属性名(除Symbol以外的可枚举属性,包括原型上的可枚举属性)
let obj = {
    name: "李白",
    age: 18
}

for (const key in obj) {
    console.log(`key:${key}---value:${obj[key]}`);
}
// key:name---value:李白
// key:age---value:18

object是基本类型时,会通过Object()封装成对象

let str = "str"

for (const key in str) {
    console.log(`key:${key}---value:${str[key]}`);
}
// key:0---value:s
// key:1---value:t
// key:2---value:r

功能还是很简单的,这里就不过多介绍了!!!

使用缺点

键值访问混乱

let obj = {
    age: 18,
    3: 3
}
obj.name = "李白"

for (const key in obj) {
    console.log(`key:${key}---value:${obj[key]}`);
}
// key:3---value:3
// key:age---value:18
// key:name---value:李白

这样的顺序是不是很意外喃?按照常理来想,多个属性应该是从上到下的顺序被访问,也就是(age3name)的顺序,那为什么会按照当前这个顺序喃?是随机还是必然喃?

在V8引擎中,对象的属性分为两种,一种是整数型属性,另一种是非整数型属性,前者被称为索引属性,后者被称为命名属性

两种属性在对象内存中被分配在不同的两个区域,分别用 propertieselements 两个指针指向它们,储存它们采用数据结构都是线性表(对于特殊的整数型索引,还会采用散列表的方式,这里不讨论),借用V8 中的快慢属性与快慢数组 这篇文章的图

索引属性按照从小到大的顺序访问遍历,而对于命名属性则是按照设置的先后顺序遍历

对象的访问属性的顺序是:先按照从小到大的顺序访问索引属性,再按照先进先出的顺序读取命名属性

其实不止是for in访问熟悉是这样的,Object.keys也是这样的

let obj = {
    age: 18,
    3: 3
}
obj.name = "李白"
Object.keys(obj)  // ["3", "age", "name"]

所以对于键值访问混乱这个锅,其实不应该怪for in哦~~~

如何区分属性是索引属性还是命名属性喃?

索引属性中的整数索引指的是那些数据喃?

整数索引无需更改即可与整数相互转换的字符串,就是说一个整数值先转为字符串再转会整数,值是不变的,字符串同理

let oldVlaue = "49"
let newValue = String(Math.trunc(Number(oldVlaue)))	//"49"
console.log(oldVlaue === newValue);			// true,索引属性
let oldVlaue = "+49"
let newValue = String(Math.trunc(Number(oldVlaue)))	// 49
console.log(oldVlaue === newValue);			// false 不是整数索引
let oldVlaue = "1.2"
let newValue = String(Math.trunc(Number(oldVlaue)))	// 1
console.log(oldVlaue === newValue);					// false 不是整数索引

那每次遇到索引都要去判断一下吗?

当然不用啦,整数索引的整数和在数学中定义的整数是一样的,但是要特别注意这里的整数索引不仅包含数字也包含字符串,比如42"42"都是整数索引

注意:字符串的正整数索引不要加+号运算符

访问原型

let obj = {
    __proto__: {
        age: 18
    },
    name: "李白"
}

for (const key in obj) {
    console.log(`key:${key}---value:${obj[key]}`);
}
// key:name---value:李白
// key:age---value:18

访问原型造成了两个缺点,一个是性能差,还有一个是访问了不必要的数据

性能差
let obj1 = {
    __proto__: {
        age: 18,
        name: "李白"
    }
}
console.time("obj1")
for (const key in obj1) { }
console.timeEnd("obj1")			// obj1: 0.01220703125 ms


let obj2 = {
    age: 18,
    name: "李白"
}
console.time("obj2")
for (const key in obj2) { }
console.timeEnd("obj2")			// obj2: 0.003173828125 ms

拥有同样多的属性,原型属性比本身属性的访问时间是慢很多的~~~

访问到不必要的数据

如果你只想访问本身的属性,可以通过hasOwnProperty(),排除来自原型的属性

let obj = {
    __proto__: {
        age: 18
    },
    name: "李白"
}

for (const key in obj) {
    if (Object.hasOwnProperty.call(obj, key)) {
        console.log(`key:${key}---value:${obj[key]}`);
    }
}
// key:name---value:李白

Symbol属性无法访问

这个限制其实不仅仅出现在for in循环中,其实像Object.keys()也不行,如果有访问Symbol属性的需求,可以通过Object.getOwnPropertySymbols()获取所以的Symbol属性列表,再取值使用

let obj = {
    [Symbol("info")]: 222
}
Object.getOwnPropertySymbols(obj).forEach((key) => {
    console.log(obj[key]);
})

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 17 天,点击查看活动详情