小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
通过本篇文章,你可了解到以下知识点:
for of
的常用用法- 和其他循环的区别
- 不能遍历
Object
对象?- 如何遍历
Object
对象?- 关乎
for of
不能遍历的Iterator
介绍- 如何给
Object
增加Symbol.iterator
方法
如果你想了解更详细的解说,请认真往下阅读;如果你想快速查看结论,请看『总结』目录。
前言
随着前端的不断发展,光循环就出现了好多种方法,for
、forEach
、do..while
、for...in
等等,不过这些循环也都各有各的应用场景和优缺点。
ES6
又为我们提供了新的循环方法for...of
,它可以循环字符串
、数组
及其他类数组对象,那作为最普遍存在的Object对象
,按理,可以循环?
我们看一下下方的代码示例:
{
// 迭代数组
const iterable = ['a', 'b'];
for (const value of iterable) {
console.log(value);
}
// output: a b
}
{
// 普通对象
const obj = {
a: 'A',
b: 'B'
}
for(const item of obj){
console.log(item)
}
// Uncaught TypeError: obj is not iterable
}
oh no,报错了:Uncaught TypeError: obj is not iterable
。提示obj是不可迭代的,显然直接用for...of
去遍历Object
对象是不行的。
本篇文章就带你深入了解一下,可以遍历大部分数据结构的for...of
为何不能遍历Object
对象。
for of
的概念和常用用法
概念
ES6
引入for...of
循环,作为遍历所有数据结构的统一的方法,创建一个循环来迭代可迭代的数据结构,包括数组、Set 和 Map 结构、某些类似数组的对象(比如arguments
对象、DOM NodeList 对象)、Generator 对象,以及字符串。
一个数据结构只要部署了Symbol.iterator
属性,就被视为具有 iterator 接口,就可以用for...of
循环遍历它的成员。也就是说,for...of
循环内部调用的是数据结构的Symbol.iterator
方法。
语法
- variable:每个迭代的属性值被分配给该变量。
- iterable:一个具有可枚举属性并且可以迭代的对象。
for (variable of iterable) {
statement
}
常用用法
数组
数组原生具备iterator
接口(即默认部署了Symbol.iterator
属性),for...of
循环本质上就是调用这个接口产生的遍历器。
{
// 迭代数组
const iterable = ['a', 'b'];
for (const value of iterable) {
console.log(value);
}
// output: a b
}
对比for...in
循环,for...in
循环只能获得对象的键名,不能直接获取键值。而ES6
提供的for...of
允许遍历获得键值。
const iterable = ['a', 'b', 'c', 'd'];
for (let value in iterable) { // value为键名索引
console.log(value);
}
// output: 0 1 2 3
for (let value of iterable) { // value为键值,如需获取索引可以借助数组实例的`entries`方法和`keys`方法
console.log(value);
}
// output: a b c d
Set和Map结构
Set 和 Map 结构也原生具有 Iterator 接口,可以直接使用for...of
循环。
const iterable = new Set(["a", "b", "b", "c"]);
for (let value of iterable) {
console.log(value);
}
// output: a b c
const iterable = new Map();
es6.set("a", 1);
es6.set("b", 2);
es6.set("c", 3);
for (let [name, value] of iterable) {
console.log(name + ": " + value);
}
// output:
// a: 1
// b: 2
// c: 3
类数组对象
1. 字符串
// 字符串
let str = "hello";
for (let s of str) {
console.log(s); // h e l l o
}
2. DOM NodeList 对象
// DOM NodeList对象
let paras = document.querySelectorAll("p");
for (let p of paras) {
p.classList.add("test");
}
3. arguments对象
// arguments对象
function printArgs() {
for (let x of arguments) {
console.log(x);
}
}
printArgs('a', 'b');
// 'a'
// 'b'
对象(不能使用for...of遍历)
// 普通对象
const obj = {
a: 'A',
b: 'B'
}
for(const item of obj){
console.log(item)
}
// Uncaught TypeError: obj is not iterable
我们发现,对于普通的对象,for...of
结构不能直接使用,会报错,提示obj is not iterable
,也就是说普通对象默认没有Iterator
接口,必须部署了 Iterator 接口后才能使用。
和其他循环的区别
循环名称 | 循环对象 | 是否可中断循环 | 是否有返回值 |
---|---|---|---|
for | for 循环体的length | 可以 | 无返回值 |
forEach | 仅可循环数组、map、set 等,不可循环字符串、普通对象 | 不可以 | 无返回值 |
do...while | 满足某种条件,则可一直循环,至少循环一次 | 可以 | 无返回值 |
while | 满足某种条件,则可一直循环 | 可以 | 无返回值 |
map | 组成新的数组成员,仅可循环数组,不可循环字符串、普通对象,set、map | 不可中断 | 返回新数组,不影响原数组 |
filter | 过滤数组成员,仅可循环数组,不可循环字符串、普通对象,set、map | 不可中断 | 返回新数组,不影响原数组 |
for...in | 可循环数组、对象 ,不可循环map、set 。可遍历数字键名,还可遍历手动添加的其他键,甚至包括原型链上的键 | 可以 | 无返回值 |
for...of | 循环可迭代的对象,不可循环普通对象(统一数据结构遍历) | 可以 | 无返回值 |
Array.prototype.newArr = () => {};
Array.prototype.anotherNewArr = () => {};
const array = ['foo', 'bar', 'baz'];
for (const value in array) {
console.log(value); // 0 1 2 newArr anotherNewArr
}
for (const value of array) {
console.log(value); // 'foo', 'bar', 'baz'
}
不能遍历Object
对象?
在for...of
介绍章节,我们提到了一个可迭代
数据结构。那什么是可迭代
数据结构呢?
实际上,任何具有 Symbol.iterator
属性的数据结构都是可迭代的。
当用for...of
迭代普通对象时,会报obj is not iterable
。也就是说普通对象是不可迭代的,没有Symbol.iterator
属性。
我们查看几个可被for...of
迭代的对象原型,看看和普通对象有何不同:
Set数据结构
Array数组
Object对象
可以看到,这些可被for of
迭代的对象,都实现了一个Symbol(Symbol.iterator)
方法,而普通对象没有这个方法。
简单来说,for...of
语句创建一个循环来迭代可迭代的对象,可迭代的对象内部实现了Symbol.iterator
方法,而普通对象没有实现这一方法,所以普通对象是不可迭代的。
关乎for of
不能遍历的Iterator
介绍
ES6
为了统一集合类型数据结构的处理,增加了 iterator
接口,供 for...of
使用,简化了不同结构数据的处理。而 iterator
的遍历过程,则是类似 Generator
的方式,迭代时不断调用next
方法,返回一个包含value
(值)和done
属性(标识是否遍历结束)的对象。
如何遍历Object
对象
通过前面的用法示例,我们知道,for...of
可以迭代数组、Map等数据结构,顺着这个思路,我们可以结合对象的Object.values()
、Object.keys()
、Object.entries()
方法以及解构赋值的知识来用for...of
遍历普通对象。
1. Object扩展方法values()、keys()、entries()
Object.values()
返回普通对象的键值组成的数组
Object.keys()
返回普通对象的键名组成的数组
Object.entries()
返回普通对象的键名键值组成的二维数组
const obj = {
a: 'A',
b: 'B'
}
// Object.values()
console.log(Object.values(obj)) // ["A", "B"]
// Object.keys()
console.log(Object.keys(obj)) // ["a", "b"]\
// Object.entries()
console.log(Object.entries(obj)) // [["a","A"],["b","B"]]
for (let [key, value] of Object.entries(obj)) {
console.log(key, value);
}
// a A
// b B
2. Generator 函数重新包装对象
const obj = { a: 1, b: 2, c: 3 }
function* entries(obj) {
for (let key of Object.keys(obj)) {
yield [key, obj[key]];
}
}
for (let [key, value] of entries(obj)) {
console.log(key, '->', value);
}
// a -> 1
// b -> 2
// c -> 3
Object
增加Symbol.iterator
方法
给普通对象增加Symbol.iterator
方法,不用担心[Symbol.iterator]
属性会被Object.keys()
获取到导致遍历结果出错,因为Symbol.iterator
这样的Symbol
属性,需要通过Object.getOwnPropertySymbols(obj)
才能获取,Object.getOwnPropertySymbols()
方法返回一个给定对象自身的所有 Symbol
属性的数组。
// 普通对象
const obj = {
a: 'A',
b: 'B',
[Symbol.iterator]() {
// 这里Object.keys不会获取到Symbol.iterator属性
const keys = Object.keys(obj);
let index = 0;
return {
next: () => {
if (index < keys.length) {
// 迭代结果 未结束
return {
value: this[keys[index++]],
done: false
};
} else {
// 迭代结果 结束
return { value: undefined, done: true };
}
}
};
}
}
// 可以直接使用for of遍历obj对象了
for (const value of obj) {
console.log(value); // A B
};
// 也可以将对象转成数组
console.log([...obj]); // ['A', 'B']
默认调用Iterator
接口的场合
- 扩展运算符(...):ES6数组增加了扩展运算符,任何定义了遍历器(
Iterator
)接口的对象,都可以用扩展运算符转为真正的数组。也就是说,只要某个数据结构部署了Iterator
接口,就可以对它使用扩展运算符,将其转为数组(毫不意外的,代码[...{}]
会报错,而[...'123']
会输出数组['1','2','3']
)。 - 变量的结构赋值: 数组和可迭代对象的解构赋值(解构是ES6提供的语法糖,其实内在是针对
可迭代对象
的Iterator接口
,通过遍历器
按顺序获取对应的值进行赋值。而普通对象解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。 - yield*:
_yield*
后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口; - 由于数组的遍历会调用遍历器接口,所以任何接受数组作为参数的场合,其实都调用;
- 字符串:S6 为字符串添加了遍历器接口,使得字符串可以被
for...of
循环遍历。
总结
for of
不能遍历Object
对象?
for...of
不能遍历Object
是因为for...of
内部没有iterator
接口,没有Symbol.iterator()
这个属性,for...of
只能遍历有iterator
接口的数据结构。
for of
如何遍历Object
对象?
1. Object扩展方法
Object.values()
返回普通对象的键值组成的数组
Object.keys()
返回普通对象的键名组成的数组
Object.entries()
返回普通对象的键名键值组成的二维数组
使用Object
扩展方法处理后的数组,再进行for...of
遍历。
2. Generator 函数重新包装对象
明细看具体章节
3. 给Object增加Symbol.iterator属性
明细看具体章节
本篇文章到这里就结束啦!知识有木有多一点点~~~
一起努力!一起进步!