区别

  • for ... infor ... of 语句上迭代的值是不同的。

for ... in 迭代对象的可枚举属性键。而 for ... of 迭代对象的数字属性的值。

const list = ['a', 'b', 'c']

for (let i in list) {
  console.log(i) // '0', '1', '2'
}

for (let i of list) {
  console.log(i) // 'a', 'b', 'c'
}
  • for ... of 不像 for ... in,它不支持迭代普通对象:
const person = {
  firstName: 'Foo',
  lastName: 'Bar',
  age: 42
}

// TypeError: `person` is not iterable
for (let k of person) {
  // ...
}

这是因为普通对象不可迭代。为了解决这个问题,我们可以使用 Object.keys() 方法来迭代对象属性:

for (let k of Object.keys(person)) {
  console.log(k, ':', person[k])
}

// firstName: Foo
// lastName: Bar
// age: 42
  • for ... of 支持迭代 Unicode 字符串。
const msg = 'Hell😀 W😀rld'

// for ... in
for (let i in msg) {
  console.log(msg[i])
}

// 'H', 'e', 'l', 'l', '�', ' ', 'W', '�', '�', 'r', 'l', 'd'

// for ... of
for (let c of msg) {
  console.log(c)
}

// Output:
// 'H', 'e', 'l', 'l', '😀', ' ', 'W', '😀', 'r', 'l', 'd'
  • for ... of 循环可以通过 await 关键字在每次迭代中等待异步任务完成:
for await (... of ...) {
  // ...
}

建议

  • 不建议向原始对象(如 ArrayBooleanNumberString 等)添加自定义方法,因为 for ... in 声明遍历枚举的特性,它将包括添加到原型中的新方法。
Array.prototype.isEmpty = function () {
  return (this.length = 0)
}

const a = ['cat', 'dog', 'mouse']
for (let i in a) {
  console.log(i) // '0', '1', '2', 'isEmpty'
}
  • for ... in 已被弃用。相反,使用for ... of
const addressBook = new Map()
addressBook.set('Foo', '111-222-333')
addressBook.set('Bar', '444-555-666')

for (const [name, phone] of addressBook) {
  console.log(name, ':', phone)
}

// Foo: 111-222-333
// Bar: 444-555-666

默认情况下,数组或对象的所有属性都将出现在 for ... in。但是,这种行为是可以避免的。使用 Object.defineProperty 可以决定属性是否可枚举。

let person = {
  firstName: 'Foo',
  lastName: 'Bar'
}

// age 属性不可枚举
Object.defineProperty(person, 'age', {
  value: 42,
  enumerable: false
})

for (let i in person) {
  console.log(i) // 'firstName', 'lastName'
}