for...in和for...of的区别

120 阅读2分钟

JavaScript的对象

JavaScript的对象就是一组名/值对的无序集合

const myObj = {
  name: "John",
  age: 30,
  sayHello() {
    console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
  }
};

for...in和for...of都是遍历对象的方法,这两种方法有什么区别呢?
书本上定义是这么说的:
for...in 循环用于遍历对象可枚举的属性,而 for...of 循环用于迭代可迭代对象的值
直接看定义有点抽象,不如先看两个例子:

// 使用 for...in 遍历对象的属性名
for (let propName in myObj) {
  console.log(propName);
}
// 输出:
// name
// age
// sayHello

// 使用 for...of 遍历对象的属性值
for (let propValue of Object.values(myObj)) {
  console.log(propValue);
}
// 输出:
// "John"
// 30
// function() {console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);}

从例子中可以看到,for...in遍历的是对象的属性,而且是可枚举的属性,for...of遍历的是对象的值,并且要求这个对象是可迭代的。

什么是可枚举?

可枚举是针对对象的属性来说的,JavaScript对象属性可分为数据属性和访问器属性,其中两种属性都有一个特性:enumerable,只要enumerable为ture,那么对象的这个属性就是可枚举的。
以下是一个JavaScript中数据属性的例子:

let obj = {};

// 定义一个名为"myProp"的数据属性
Object.defineProperty(obj, "myProp", {
  value: "Hello, World!",  // 属性值
  writable: true,  // 可写性
  enumerable: true,  // 可枚举性
  configurable: true  // 可配置性
});

以下是一个JavaScript中访问器属性的例子:

let obj = {
  firstName: "John",
  lastName: "Doe"
};

// 定义一个名为"fullName"的访问器属性
Object.defineProperty(obj, "fullName", {
  get: function() {
    return this.firstName + " " + this.lastName;
  },
  set: function(value) {
    let names = value.split(" ");
    this.firstName = names[0];
    this.lastName = names[1];
  },
  enumerable: true,// 可枚举性
  configurable: true
});

从上面的例子可知,obj的数据属性myProp和访问器属性fullName的特性enumerable都设置为ture,所以obj的myProp和fullName都是可迭代的,可以用for...in访问到。

什么是可迭代?

可迭代是针对对象的。一个对象只要实现了Iterator接口,那么这个对象就是可迭代的。
具体做法是实现对象的Symbol.iterator 方法,Symbol.iterator 方法应该返回一个迭代器对象,
这个迭代器对象应该至少实现一个 next() 方法,next() 方法返回一个对象,该对象应该至少包含以下两个属性:

  • value:表示下一个元素的值。
  • done:表示是否还有更多的元素需要迭代。如果迭代器已经迭代完所有元素,则返回 done:true。
const myArray = [1, 2, 3, 4, 5];
const myIterator = {
  [Symbol.iterator]() {
    let index = 0;
    return {
      next: () => {
        if (index < myArray.length) {
          return { value: myArray[index++], done: false };
        } else {
          return { done: true };
        }
      }
    }
  }
}

for (const element of myIterator) {
  console.log(element);
}
// 输出:
// 1
// 2
// 3
// 4
// 5

只要一个对象实现了Iterator接口,这个对象就是可迭代的,可以用for...of遍历这个对象的值。
JavaScript的数据类型中已经有自动实现Iterator接口的,它们是:

  1. 字符串(String)
  2. 数组(Array)
  3. TypedArray 对象(例如 Uint8Array 和 Float64Array)
  4. 类数组对象(例如 arguments 和 NodeList 对象)
  5. Map 对象
  6. Set 对象
  7. Generator 对象

但是也有两个数据类型没有实现Iterator接口,所以是不可迭代的:

  1. Symbol
  2. Object