面试官又让我用 for...of 遍历对象?

108 阅读3分钟

我们先拆分一下这个题目,如果想要解答这个问题,我们就需要先了解两个知识:

  • for...of的概念以及用法
  • 为什么object不能使用for...of?

带着这两个问题逐步去解开这个题目。

for...of

看看如何使用用法

const array1 = ["a", "b", "c"];

for (const element of array1) {
  console.log(element);
}

// "a"
// "b"
// "c"

了解一下概念

for...of 语句执行一个循环,该循环处理来自 可迭代对象 的值序列。

读懂这句话,就需要了解一下 可迭代对象 的概念了。

可迭代对象

为了方便理解,首先我们先列举一下常见的可迭代对象:Array,String,Map,Set;

这些对象为什么被称为可迭代对象?

因为他们都满足了可迭代协议,如果一个对象想成为可迭代对象,就必须实现  [Symbol.iterator]()  方法,或者本身(或者原型链上)有 [Symbol.iterator] 的属性。

验证一下

const arr = [1, 2, 3];

// 查看数组原型上的 Symbol.iterator
console.log(Array.prototype[Symbol.iterator]); 
// 输出: function values() { [native code] }

可以看到 数组的原型链上确实存在了这个属性。

这又是什么玩意儿呢?

[Symbol.iterator]

概念:Symbol.iterator 为每一个对象定义了默认的 迭代器 。该迭代器可以被 [for...of] 循环使用。

注意:你很少直接调用这个方法,这个方法像 for...of 循环这样的迭代语法会 自动 调用这个方法来获取用于进行循环的迭代器。

好好好,又来一个名词 iterator

Iterator

概念:Iterator 对象是一个符合迭代器协议的对象,其提供了 next() 方法用以返回迭代器结果对象。

我们试着调用一下:

const arr = [1, 2, 3];

// 查看数组原型上的 Symbol.iterator
console.log(Array.prototype[Symbol.iterator]); 
// 输出: function values() { [native code] }

// 获取数组的迭代器
const iterator = arr[Symbol.iterator]();
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: undefined, done: true }

可以看到我们通过调用这个迭代器的next()的方法,会逐个返回可迭代对象的每个value值,直到done的值为true为止,代表完成。

到现在为止,我们已经将所有名词都介绍了,我们再来总结一下 for...of

首先for...of 是一个循环语句,用来遍历可迭代对象的,可迭代对象是指实现了[Symbol.iterator] 方法的对象。在使用 for...of 遍历可迭代对象时,会 自动 调用这个方法返回一个可迭代的对象Iterator,然后重复调用迭代器的 next()方法,直到done返回true为止。

现在我们就回答了开篇的第一个问题 for...of的概念以及用法

很轻松的我们也可以回答第二个问题了,为什么object不能使用for...of?:因为for...of是用来遍历可迭代对象的,但是object不满足,因为没有object的原型链上不存在Symbol.iterator的属性。

for...of 遍历object

当我们搞明白上面两个问题之后,也就能回答面试官的问题了,只需要将object变为一个可迭代对象就可以,也就是给object添加Symbol.iterator的属性。

方法一:为对象添加 [Symbol.iterator] 方法

const obj = { a: 1, b: 2, c: 3 };

// 添加迭代器方法
obj[Symbol.iterator] = function() {
  const keys = Object.keys(this);
  let index = 0;
  
  return {
    next: () => {
      return {
        value: this[keys[index]], // 当前值
        done: index++ >= keys.length // 是否结束
      };
    }
  };
};

// 现在可以用 for...of 遍历值
for (const value of obj) {
  console.log(value); // 输出: 1, 2, 3
}

方法二:使用更简洁的实现方式:生成器函数

const obj = { a: 1, b: 2, c: 3 };

obj[Symbol.iterator] = function* () {
  for (const key of Object.keys(this)) {
    yield this[key];
  }
};

for (const value of obj) {
  console.log(value); // 输出: 1, 2, 3
}

这时候我们就完成了面试官的问题了。

扩展

列几个平时使用到的遍历对象的方法

const obj = { a: 1, b: 2, c: 3 };

// 遍历键
for (const key of Object.keys(obj)) {
  console.log(key); // 输出: 'a', 'b', 'c'
}

// 遍历值
for (const value of Object.values(obj)) {
  console.log(value); // 输出: 1, 2, 3
}

// 遍历键值对
for (const [key, value] of Object.entries(obj)) {
  console.log(`${key}: ${value}`); 
  // 输出: 'a: 1', 'b: 2', 'c: 3'
}