for...of
for...of
语句执行一个循环,该循环处理来自可迭代对象的值序列。可迭代对象包括内置对象的实例,例如 Array
、String
、TypedArray
、Map
、Set
、NodeList
(以及其他 DOM 集合),还包括 arguments
对象、由生成器函数生成的生成器,以及用户定义的可迭代对象。
尝试一下
语法
描述
for...of
循环按顺序逐个处理从可迭代对象获取的值。循环对值的每次操作被称为一次迭代,而循环本身被称为迭代可迭代对象。每次迭代都会执行可能引用当前序列值的语句。
当 for...of
循环迭代一个可迭代对象时,它首先调用可迭代对象的 Symbol.iterator]()
方法,该方法返回一个迭代器,然后重复调用生成器的 next()
方法,以生成要分配给 variable
的值的序列。
for...of
循环在迭代器完成时退出(即迭代器的 next()
方法返回一个包含 done: true
的对象)。你也可以使用控制流语句来改变正常的控制流程。break
语句退出循环并跳转到循环体后的第一条语句,而 continue
语句跳过当前迭代的剩余语句,继续进行下一次迭代。
如果 for...of
循环提前退出(例如遇到 break
语句或抛出错误),则会调用迭代器的 return()
方法来执行任何清理任务。
for...of
的 variable
部分可以接受任何在 =
运算符之前的内容。只要在循环体内部不重新赋值(可以在迭代之间更改,因为它们是两个独立的变量),你可以使用 const
来声明变量。否则,你可以使用 let
。
const iterable = [10, 20, 30];
for (let value of iterable) {
value += 1;
console.log(value);
}
// 11
// 21
// 31
备注:每次迭代都会创建一个新的变量。在循环体内部重新赋值变量不会影响可迭代对象(在本例中,是一个数组)的原始值。
你可以使用解构赋值来分配多个局部变量,或者使用属性访问器(如 for (x.y of iterable)
)来给对象属性赋值。
然而,有一条特别的规则禁止使用 async
作为变量名。这是无效语法:
let async;
for (async of [1, 2, 3]); // SyntaxError: The left-hand side of a for-of loop may not be 'async'.
这是为了避免与有效代码 for (async of => {};;)
出现语法歧义,该代码是一个 for
循环。
示例
迭代数组
const iterable = [10, 20, 30];
for (const value of iterable) {
console.log(value);
}
// 10
// 20
// 30
迭代字符串
字符串将会按 Unicode 码位迭代。
const iterable = "boo";
for (const value of iterable) {
console.log(value);
}
// "b"
// "o"
// "o"
迭代类型化数组
const iterable = new Uint8Array([0x00, 0xff]);
for (const value of iterable) {
console.log(value);
}
// 0
// 255
迭代 Map
const iterable = new Map([
["a", 1],
["b", 2],
["c", 3],
]);
for (const entry of iterable) {
console.log(entry);
}
// ['a', 1]
// ['b', 2]
// ['c', 3]
for (const [key, value] of iterable) {
console.log(value);
}
// 1
// 2
// 3
迭代 Set
const iterable = new Set([1, 1, 2, 2, 3, 3]);
for (const value of iterable) {
console.log(value);
}
// 1
// 2
// 3
迭代参数对象
你可以迭代 arguments
对象来检查传递给函数的所有参数。
function foo() {
for (const value of arguments) {
console.log(value);
}
}
foo(1, 2, 3);
// 1
// 2
// 3
迭代 NodeList
迭代用户定义的可迭代对象
迭代带有返回自定义迭代器的 [Symbol.iterator]()
方法的对象:
const iterable = {
[Symbol.iterator]() {
let i = 1;
return {
next() {
if (i <= 3) {
return { value: i++, done: false };
}
return { value: undefined, done: true };
},
};
},
};
for (const value of iterable) {
console.log(value);
}
// 1
// 2
// 3
迭代带有 [Symbol.iterator]()
生成器方法的对象:
const iterable = {
*[Symbol.iterator]() {
yield 1;
yield 2;
yield 3;
},
};
for (const value of iterable) {
console.log(value);
}
// 1
// 2
// 3
可迭代迭代器(带有返回 this
的 [Symbol.iterator]()
方法的迭代器)是一种相当常见的技术,用来使迭代器在期望可迭代对象的语法中使用,例如 for...of
。
let i = 1;
const iterator = {
next() {
if (i <= 3) {
return { value: i++, done: false };
}
return { value: undefined, done: true };
},
[Symbol.iterator]() {
return this;
},
};
for (const value of iterator) {
console.log(value);
}
// 1
// 2
// 3
迭代生成器
function* source() {
yield 1;
yield 2;
yield 3;
}
const generator = source();
for (const value of generator) {
console.log(value);
}
// 1
// 2
// 3
提前退出
在第一个循环中执行 break
语句会导致循环提前退出。迭代器尚未完成,因此第二个循环将从第一个循环停止的地方继续执行。
const source = [1, 2, 3];
const iterator = source[Symbol.iterator]();
for (const value of iterator) {
console.log(value);
if (value === 1) {
break;
}
console.log("这个字符串不会被输出。");
}
// 1
// 另一个使用相同迭代器的循环从上一个循环中断的地方继续。
for (const value of iterator) {
console.log(value);
}
// 2
// 3
// 迭代器已用完。该循环不会执行任何迭代。
for (const value of iterator) {
console.log(value);
}
// [没有输出]
生成器实现了 return()
方法,当循环退出时,该方法会使生成器函数提前返回。这使得生成器在循环之间不可重复使用。
function* source() {
yield 1;
yield 2;
yield 3;
}
const generator = source();
for (const value of generator) {
console.log(value);
if (value === 1) {
break;
}
console.log("这个字符串不会被输出。");
}
// 1
// 生成器已用完。该循环不会执行任何迭代。
for (const value of generator) {
console.log(value);
}
// [没有输出]
for...of
与 for...in
之间的区别
for...in
和 for...of
语句都用于迭代某个内容,它们之间的主要区别在于迭代的对象。
for...in
语句用于迭代对象的可枚举字符串属性,而 for...of
语句用于迭代可迭代对象定义的要进行迭代的值。
下面的示例展示了在迭代 Array
时,for...of
循环和 for...in
循环之间的区别。
Object.prototype.objCustom = function () {};
Array.prototype.arrCustom = function () {};
const iterable = [3, 5, 7];
iterable.foo = "hello";
for (const i in iterable) {
console.log(i);
}
// "0"、"1"、"2"、"foo"、"arrCustom"、"objCustom"
for (const i in iterable) {
if (Object.hasOwn(iterable, i)) {
console.log(i);
}
}
// "0" "1" "2" "foo"
for (const i of iterable) {
console.log(i);
}
// 3 5 7
iterable
对象继承了 objCustom
和 arrCustom
属性,因为其原型链中同时包含了 Object.prototype
和 Array.prototype
。
for...in
循环仅打印了 iterable
对象的可枚举属性。它不会打印数组中的元素 3
、5
、7
或 "hello"
,因为它们不是属性,而是值。它打印了数组的索引以及 arrCustom
和 objCustom
,它们是实际的属性。如果你对为什么迭代这些属性感到困惑,可以查看关于数组迭代和 for...in
工作原理的更详细解释。
第二个循环与第一个循环类似,但它使用 Object.hasOwn()
来检查找到的可枚举属性是否为对象的自有属性,即非继承属性。如果是,则打印该属性。属性 0
、1
、2
和 foo
被打印,因为它们是自有属性。属性 arrCustom
和 objCustom
没有被打印,因为它们是继承属性。
for...of
循环迭代并打印 iterable
按照数组(数组是可迭代的)定义要进行迭代的值。对象的元素 3
、5
、7
被打印,但对象的属性没有被打印。
规范
Specification |
---|
ECMAScript Language Specification # sec-for-in-and-for-of-statements |
浏览器兼容性
BCD tables only load in the browser