for…of
ES6新增的 for…of 循环可以很方便对Array、Set、Map、类似数组的对象进行遍历,相较于数组的 forEach函数,它可以随时中断循环的进行,在数据量大的时候可以很好的提高性能。
const arr = [1, 2, 3, 4, 5]; // 1, 2, 3, 4, 5
const set = new Set(arr); // 1, 2, 3, 4, 5
const map = new Map([['a', 1], ['b', 2]]); // [ 'a', 1 ], [ 'b', 2 ]
但这么好用的循环在遍历 Object的时候竟然会报错!
const obj = {
a: 1,
b: 2
}
for (const i of obj) { // Uncaught TypeError: obj is not iterable
console.log(i);
}
obj变量是不可迭代的???
Iterator
精通js的都知道,for…of 判断一个变量能否遍历是看这个遍历有没有部署 iterator接口,for…of内部会调用变量的 Symbol.iterator属性(方法),该方法会返回一个指针对象,这个指针对象具有一个 next方法,调用 next方法会返回一个包含 value和 done两个属性的对象,value为当前循环的值,done是一个布尔值,表示循环有没有结束。
console.log([][Symbol.iterator]); // ƒ values() { [native code] }
console.log(new Set()[Symbol.iterator]); // ƒ values() { [native code] }
console.log(new Map()[Symbol.iterator]); // ƒ values() { [native code] }
console.log({}[Symbol.iterator]); // undefined
const it = [1,2][Symbol.iterator]()
it.next() // {value: 1, done: false}
it.next() // {value: 2, done: false}
it.next() // {value: undefined, done: true}
所以不难看出 Object不能使用 for…of遍历是因为没有部署 iterator接口,当给 Object加上 iterator接口时。
const obj = {
a: 1,
b: 2
};
obj[Symbol.iterator] = () => {
let i = 0;
const arr = Object.entries(obj);
return {
next() {
return i < arr.length
? {
value: arr[i++],
done: false
}
: {
value: undefined,
done: true
};
}
};
};
for (const i of obj) {
console.log(i); // [ 'a', 1 ], [ 'b', 2 ]
}
居然可以遍历了!!!for…of遍历对象居然没报错!!!
但每次都写这么长的迭代器属实是太麻烦了,有没有简单点的方法呢?
const obj = {
a: 1,
b: 2
};
obj[Symbol.iterator] = () => Object.entries(obj)[Symbol.iterator]();
这下确实简洁了许多,甚至比 Generator函数返回 Iterator接口还简洁。
Generator
Generator函数与普通函数不同,function关键字与函数名之间有一个星号(*),函数体内部使用yield表达式,定义不同的内部状态。调用时会返回一个指针对象。
function* gen() {
yield 1;
yield 2;
}
const it = gen(); // Object [Generator] {}
it.next(); // {value: 1, done: false}
it.next(); // {value: 2, done: false}
it.next(); // {value: undefined, done: true}
所以上述给 Object部署 Iterator接口用 Generator函数可以写成。
const obj = {
a: 1,
b: 2
};
obj[Symbol.iterator] = function* () {
let i = 0;
const arr = Object.entries(obj);
while (i < arr.length) {
yield arr[i++];
}
};
还是太麻烦了,想再简洁一点就不得不提到 yield* 表达式了。
yield* 表达式可以用来在一个 Generator函数里面执行另一个 Generator函数。
如果yield*后面跟着一个数组,由于数组原生支持遍历器,因此就会遍历数组成员。
所以上述代码可以再简化为。
const obj = {
a: 1,
b: 2
};
obj[Symbol.iterator] = function* () {
yield* Object.entries(obj);
};
所以想使用 for…of 遍历对象时,可以在代码里加上以下两段代码中的任意一段,这样就能轻松的遍历对象了
Object.prototype[Symbol.iterator] = function () {
return Object.entries(this)[Symbol.iterator]();
};
// or
Object.prototype[Symbol.iterator] = function* () {
yield* Object.entries(this);
};