我们先拆分一下这个题目,如果想要解答这个问题,我们就需要先了解两个知识:
- 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'
}