17. for-of 循环
目录
请支持本书:购买 (PDF, EPUB, MOBI)捐赠
(广告,请不要屏蔽。)

17. for-of 循环



17.1 概述

for-of 是 ES6 中新增的循环结构,它取代了 for-inforEach(),并支持新的迭代协议。

使用它可以遍历*可迭代*对象(数组、字符串、Map、Set 等;参见“可迭代对象和迭代器”一章)

const iterable = ['a', 'b'];
for (const x of iterable) {
    console.log(x);
}

// Output:
// a
// b

breakcontinuefor-of 循环中有效

for (const x of ['a', '', 'b']) {
    if (x.length === 0) break;
    console.log(x);
}

// Output:
// a

在遍历数组时访问元素及其索引(of 前面的方括号表示我们正在使用解构

const arr = ['a', 'b'];
for (const [index, element] of arr.entries()) {
    console.log(`${index}. ${element}`);
}

// Output:
// 0. a
// 1. b

遍历 Map 中的 [键,值] 条目(of 前面的方括号表示我们正在使用解构

const map = new Map([
    [false, 'no'],
    [true, 'yes'],
]);
for (const [key, value] of map) {
    console.log(`${key} => ${value}`);
}

// Output:
// false => no
// true => yes

17.2 介绍 for-of 循环

for-of 允许您遍历*可迭代*的数据结构:数组、字符串、Map、Set 等。可迭代性的工作原理在“可迭代对象和迭代器”一章中有详细解释。但是,如果您使用 for-of 循环,则无需了解详细信息

const iterable = ['a', 'b'];
for (const x of iterable) {
    console.log(x);
}

// Output:
// a
// b

for-of 遍历 iterable 的项,并在执行循环体之前将它们逐个赋值给循环变量 xx 的作用域是循环,它只在循环内部存在。

您可以使用 breakcontinue

for (const x of ['a', '', 'b']) {
    if (x.length === 0) break;
    console.log(x);
}

// Output:
// a

for-of 结合了以下优点

17.3 陷阱:for-of 只能用于可迭代值

of 子句的操作数必须是可迭代的。这意味着如果您想遍历普通对象,则需要一个辅助函数(请参阅“普通对象不可迭代”)。如果值类似于数组,则可以通过 Array.from() 将其转换为数组

// Array-like, but not iterable!
const arrayLike = { length: 2, 0: 'a', 1: 'b' };

for (const x of arrayLike) { // TypeError
    console.log(x);
}

for (const x of Array.from(arrayLike)) { // OK
    console.log(x);
}

17.4 迭代变量:const 声明与 var 声明

如果您使用 const 声明迭代变量,则每次迭代都会创建一个新的*绑定*(存储空间)。这可以从以下代码片段中看出,我们使用箭头函数保存了 elem 的当前绑定以供以后使用。之后,您可以看到箭头函数没有共享相同的 elem 绑定,它们每个都有不同的绑定。

const arr = [];
for (const elem of [0, 1, 2]) {
    arr.push(() => elem); // save `elem` for later
}
console.log(arr.map(f => f())); // [0, 1, 2]

// `elem` only exists inside the loop:
console.log(elem); // ReferenceError: elem is not defined

let 声明的工作方式与 const 声明相同(但绑定是可变的)。

如果使用 var 声明迭代变量,则情况会有所不同,这很有启发意义。现在,所有箭头函数都引用相同的 elem 绑定。

const arr = [];
for (var elem of [0, 1, 2]) {
    arr.push(() => elem);
}
console.log(arr.map(f => f())); // [2, 2, 2]

// `elem` exists in the surrounding function:
console.log(elem); // 2

每次迭代都有一个绑定,这在通过循环创建函数时非常有用(例如,添加事件侦听器)。

您还可以在 for 循环(通过 let)和 for-in 循环(通过 constlet)中获得每次迭代的绑定。详细信息在变量章节中解释。

17.5 使用现有变量、对象属性和数组元素进行迭代

到目前为止,我们只看到了使用声明的迭代变量的 for-of。但还有其他几种形式。

您可以使用现有变量进行迭代

let x;
for (x of ['a', 'b']) {
    console.log(x);
}

您还可以使用对象属性进行迭代

const obj = {};
for (obj.prop of ['a', 'b']) {
    console.log(obj.prop);
}

您还可以使用数组元素进行迭代

const arr = [];
for (arr[0] of ['a', 'b']) {
    console.log(arr[0]);
}

17.6 使用解构模式进行迭代

for-of 与解构结合使用对于 [键,值] 对(编码为数组)的可迭代对象特别有用。这就是 Map 的用途

const map = new Map().set(false, 'no').set(true, 'yes');
for (const [k,v] of map) {
    console.log(`key = ${k}, value = ${v}`);
}
// Output:
// key = false, value = no
// key = true, value = yes

Array.prototype.entries() 也返回一个 [键,值] 对的可迭代对象

const arr = ['a', 'b', 'c'];
for (const [k,v] of arr.entries()) {
    console.log(`key = ${k}, value = ${v}`);
}
// Output:
// key = 0, value = a
// key = 1, value = b
// key = 2, value = c

因此,entries() 为您提供了一种根据迭代项的位置对其进行不同处理的方法

/** Same as arr.join(', ') */
function toString(arr) {
    let result = '';
    for (const [i,elem] of arr.entries()) {
        if (i > 0) {
            result += ', ';
        }
        result += String(elem);
    }
    return result;
}

此函数的使用方法如下

> toString(['eeny', 'meeny', 'miny', 'moe'])
'eeny, meeny, miny, moe'
下一篇:18. 新的数组功能