for…in 和 for…of 的区别是什么❓
先贴一个mdn上对于 for…in 和 for…of 的解释:
for…in
for…in 比较容易理解,用于迭代对象的可枚举字符串属性(包括原型链上的可枚举字符串属性),直接上 Demo:
const object1 = {
1: 44,
[Symbol.for('property4')]: 45
};
object1.__proto__.say = () => {}
Object.defineProperties(object1, {
property1: {
value: 42,
writable: true,
},
property2: {
value: 43,
enumerable: true,
},
});
for (let name in object1) {
console.log(name)
}
// 1 [object String]
// property2 [object String]
// say [object String]
打印结果只有 ‘1’ 和 ‘property2’ 和 'say’这三个属性,逐个分析:
- 1 作为对象的属性时默认会被转为 string 类型,且通过对象字面量方式定义的属性,默认为可枚举属性(enumerable 为 true);
- Symbol.for(‘property4’) 未打印,是因为 Symbol 类型不是字符串类型,因此 for…in 无法迭代;Symbol 类型的属性不会出现在for…in、for…of 循环中,也不会被 Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回。需要通过Object.getOwnPropertySymbols()方法,可以获取指定对象的所有 Symbol 属性名。
- property1 属性属于字符串类型,但在数据描述中未显示定义enumerable,默认为 false,为不可枚举属性,因此 for…in 无法迭代;(如果你想要所有以字符串为键(不包括 Symbol)的自有属性(不包括原型链),包括不可枚举的属性,可以使用 Object.getOwnPropertyNames()。)
- property2 属性属于字符串类型,在数据描述中显示定义enumerable为 true,为可枚举属性,因此 for…in 可以迭代;
- say 属性在 object1 的原型上,显示定义,可枚举;
for…of
接下来我们来看下 for…of 的解释:for…of 语句用于迭代可迭代对象定义的要进行迭代的值。
老规矩,我们先上个小 Demo:
// 继续延用 objetc1 对象
for (let value of object1) {
console.log(value, Object.prototype.toString.call(name))
}
// TypeError: object1 is not iterable
好家伙:直接报错了,报错信息告诉我们 object1 是不可迭代的,即 object1 并不是 可迭代对象。
让我们再仔细回看下 mdn 对于 for…of 的解释:for…of 语句用于迭代可迭代对象定义的要进行迭代的值。
这里有两个重点:一是可迭代对象,二是定义的要进行迭代的值。
那么问题来了,什么是可迭代对象 呢❓
顺其而然,迭代协议 就应运而生了,符合 迭代协议 的对象就是 可迭代对象 啦。
迭代协议 具体分为两个协议:可迭代协议和迭代器协议。可迭代对象 必须同时满足这两个协议,才能成为 可迭代对象。
在这可以先跟大家通俗的简单介绍一下这两个协议:
- 【可迭代协议】可以简单理解为是否有 @@iterator 方法;
- 【迭代器协议】可以简单理解为 @@iterator 方法返回的对象是否符合IteratorResult 接口类型定义(interface IteratorResult { value: any; done: boolean; })
想更多了解这两个协议的可以具体往下看:
【可迭代协议】允许 JavaScript 对象定义或定制它们的迭代行为,例如,在一个 for…of 结构中,哪些值可以被遍历到。一些内置类型同时是内置的可迭代对象,并且有默认的迭代行为,比如 Array 或者 Map,而其他内置类型则不是(比如 Object)。
要成为可迭代对象,该对象必须实现 @@iterator 方法,这意味着对象(或者它原型链上的某个对象)必须有一个键为 @@iterator 的属性,可通过常量 Symbol.iterator 访问该属性; @@iterator 方法是一个无参数的函数,其返回值为一个符合【迭代器协议】的对象。
当一个对象需要被迭代的时候(比如被置入一个 for…of 循环时),首先,会不带参数调用它的 @@iterator 方法,然后使用此方法返回的迭代器获得要迭代的值。
回看上面的例子,为什么在 for…of 迭代 object1 的时候会报错,因为 object1 是不可迭代对象,为啥是不可迭代对象,因为该对象未实现 @@iterator 方法。
那我们该如何让 object1 成为可迭代对象呢❓我们需要在 object1对象上 实现@@iterator 方法。
使用生成器函数:
const myIterable = {
*[Symbol.iterator]() {
yield 1;
yield 2;
yield 3;
}
};
Object.assign(object1, myIterable)
for (let value of object1) {
console.log(value, Object.prototype.toString.call(name))
}
// 1 '[object String]'
// 2 '[object String]'
// 3 '[object String]'
使用自定义迭代器对象(next()返回对象需要符合迭代器协议):
const myIterable = {
data: [1, 2, 3],
pointer: 0,
[Symbol.iterator]() {
return {
next: () => {
if (this.pointer < this.data.length) {
return { value: this.data[this.pointer++], done: false };
} else {
return { done: true };
}
}
};
}
};
Object.assign(object1, myIterable)
for (let value of object1) {
console.log(value, Object.prototype.toString.call(name))
}
// 1 '[object String]'
// 2 '[object String]'
// 3 '[object String]'
题外扩展:
Array.prototype.values() 方法返回一个新的数组迭代器对象,可以使用该迭代器对象迭代数组中每个元素的值,使用迭代器对象可以调用 next() 方法。
Array 内部实现了 @@iterator,因此可以直接放进 for…of 中进行迭代。
Array.prototype.values() 是 Array.prototype@@iterator 的默认实现。
Array.prototype.values === Array.prototype[Symbol.iterator]; // true
const arr = ["a", "b", "c", "d", "e"];
for (const value of arr) {
console.log(value);
} // "a" "b" "c" "d" "e"
const iterator = arr.values();
for (const letter of iterator) {
console.log(letter);
} // "a" "b" "c" "d" "e"
iterator.next(); // { value: undefined, done: true } 因为iterator迭代器每次都是一次性的,想要重新迭代使用必须再重新调用一个values()返回一个新的迭代器对象
for (const letter of iterator) {
console.log(letter);
} // 不再打印