for...in和for...of的那些事

198 阅读5分钟

这次也算是重写之前的那篇博客了吧,想写的更仔细一些,更具体一些

提起for...in和for...of,好多人顺口就能说出来,for...in是用来遍历数组和对象的,for...of是用来遍历数组的,for...in遍历的是数组和对象的key,for...of遍历的是数组和对象的value,但是仅仅是这样吗?

for...of

首先说for...of,forOf主要是用来遍历具有Iterator的任何结构,包括Array,Map,Set等等,也就是并不仅仅是大家平时所说的数组,首先我们先来看一个例子

let arr = [1, 2, 3]for (let i of arr) {    console.log(i);}
// output: 1 2 3

上面我们可以看到,使用for... of遍历数组之后,会遍历每个数组的元素,我们可以对每个数组元素做不同的操作。

那么此时可能会有小伙伴问了,能不能用for...of遍历对象呢,那么接下来我们测试下,可不可以呢

let obj = { name: 1, age: 2}for(let i of obj) {    console.log(i)}


从截图上我们可以看出,是不可以的,原因就是我们的对象并不是具备Iterator的数据结构,所以for...of是只能用来遍历数组的

for...in

forIn主要是用于遍历非Symbol类型的可枚举属性(这个可以通过Object.defineProperty来设置,有兴趣的同学可以去自行了解一下)。

1. 首先我们来看for...in遍历对象

let obj = { name: 1, age: 2};

for(let i in obj) {    console.log(i)}
//output: name, age

由此我们可以看出使用for...in遍历对象,我们得到是对象的键

2. 下面我们再来看for...in遍历数组

let arr = [1, 2, 3]
for(let i, o in arr) {    console.log(i)}output: 0 1 2

我们的数组元素是1,2,3,为什么打印出来的却是0, 1, 2呢?是for...in在中间偷偷的做了什么操作吗,答案肯定不是的,for...in遍历数组的时候,遍历的是数组的下标, 此时可能会有小伙伴问了,为什么呢?

答案是这样的,在js中,任何数据结构都可以看做对象,数组则是被看做以下标为key,元素值为value的特殊对象, 结合前面for...in遍历对象得到的是对象的键所以小伙伴们知道为什么使用for...in遍历数组得到的是下标了吧。

但是我们在通过for...in进行遍历的时候,会出现一些意想不到的问题,比如说

let arr = [1, 2, 3]Array.prototype.adds = function() {    console.log(1)}for(let i in arr) {    console.log(i)}

// output: 0, 1, 2, adds

此时我们可以看到,除了打印了0, 1, 2之外,我们还将在Array原型上加的函数打印出来了,这是为什么呢?

当你在Array的原型上添加一个方法之后,你通过for...in遍历一个数组的时候,你会将这个方法遍历出来,因为for...in是会向原型上遍历的(在这里有个前提,我们通过手动添加的函数都是可枚举的属性,而自带的函数都是不可枚举的属性),在遍历的时候,会找到adds这个函数,然后会将它的键打印出来,即adds,很多时候我们不想将这个函数打印出来,我们该怎么办呢,有什么解决方法吗,此时有两种解决方案

1. 我们可以使用hasOwnProperty方法来解决这个方法

//改进版
let arr = [1, 2, 3]let obj = { name: 1, age: 2}Array.prototype.adds = function() {    console.log(1)}for(let i in arr) {
    if(arr.hasOwnProperty(i)) {
        console.log(i)

    }}
//output: 0, 1, 2

此时我们可以看到打印的是0,1,2了

2. 添加方法的时候通过Object.defineProperty()来添加

或许会有很多小伙伴不了解这个方法,这个方法的主要功能就是实现增加或者修改属性,并且会返回次对象, 下面我们就看下增加对象属性

let obj = {};Object.defineProperty(obj, 'add', {    value: 14,    writable: false})console.log(obj.add)

output: 14

修改对象的属性

let obj = {age: 14};Object.defineProperty(obj, 'age', {    value: 17,})console.log(obj.age)// output: 17

  • 参数

Object.defineProperty(obj, prop, descriptor)接收三个参数,obj是要操作的对象,prop是给对象添加或者修改的属性,前两个属性都是必填的,第三个descriptor是描述属性。

下面我们来了解下descriptor这个对象

1. configurable: 当且仅当该属性为true的时候,该属性的描述符才能被改变,同时该属性也能从对应的对象上删除

2. enumerable:当且仅当该值为true的时候,该属性才会出现在对象的枚举属性中,默认为false

3. value:属性的值,默认为undefined

4. writable:当且仅当该值为true时,才可以修改value,默认为false

介绍完defineProperty之后,我们也可以看到,通过设置某个对象的属性的descriptor,我们可以实现上面的需求,先上代码

let arr = [1, 2, 3]
Object.defineProperty(arr, 'adds', {    value: function() {        console.log(1)    }})
for(let i in arr) {
    console.log(i)
}

// output: 0, 1, 2

我们通过这种方法给arr绑定了一个函数,adds,此时我们通过for...in去遍历,是拿不到这个adds函数的,所以我们可以通过,因为通过Object.defineProperty()增加的属性其enumable默认是false,不可枚举的。而for...in只能遍历可枚举的属性,所以这种方法也能来杜绝上面出现的问题

此时有些人肯定会有疑问的,数组有这种情况,那么对象会有吗,答案是肯定的,对象也会有这种情况

let obj = { name: 1, age: 2}Object.prototype.adds = function() {    console.log(0)}for(let i in obj) {    console.log(i)}

output: name, age, adds

我们也可以通过上面的两种方法进行解决。

好了,这就是我们今天说的for...in和for...of了,如果有什么不对的地方,欢迎大神们提出来,希望大家一起学习共同进步