for...of循环原理
for...of
循环是ECMAScript 6引入的一种遍历数据结构的新语法,它基于迭代器协议实现,能够以统一的方式处理数组、字符串、Map、Set等可迭代对象。与传统for
循环和forEach
方法相比,它提供了更简洁的语法和更强大的功能。
for...of循环的基本语法
for...of
循环的语法结构非常简单:
for (const item of iterable) {
// 循环体
}
其中iterable
可以是任何实现了可迭代协议的对象。下面是一个遍历数组的简单示例:
const fruits = ['apple', 'banana', 'orange'];
for (const fruit of fruits) {
console.log(fruit);
}
// 输出:
// apple
// banana
// orange
迭代器协议与可迭代对象
for...of
循环的核心在于迭代器协议。一个对象要成为可迭代对象,必须实现@@iterator
方法,即具有Symbol.iterator
属性。这个属性是一个函数,它返回一个迭代器对象。
迭代器对象必须实现next()
方法,该方法返回一个包含value
和done
属性的对象:
const arrayIterator = [1, 2, 3][Symbol.iterator]();
console.log(arrayIterator.next()); // { value: 1, done: false }
console.log(arrayIterator.next()); // { value: 2, done: false }
console.log(arrayIterator.next()); // { value: 3, done: false }
console.log(arrayIterator.next()); // { value: undefined, done: true }
内置可迭代对象
ECMAScript 6中许多内置对象都是可迭代的:
数组
const numbers = [10, 20, 30];
for (const num of numbers) {
console.log(num * 2);
}
// 输出: 20, 40, 60
字符串
字符串中的每个字符会被依次迭代:
const greeting = 'Hello';
for (const char of greeting) {
console.log(char);
}
// 输出: H, e, l, l, o
Map和Set
const map = new Map();
map.set('name', 'John');
map.set('age', 30);
for (const [key, value] of map) {
console.log(`${key}: ${value}`);
}
// 输出: name: John, age: 30
const set = new Set([1, 2, 3]);
for (const item of set) {
console.log(item * 2);
}
// 输出: 2, 4, 6
NodeList
DOM中的NodeList也是可迭代的:
const divs = document.querySelectorAll('div');
for (const div of divs) {
div.style.color = 'red';
}
与for...in循环的区别
for...of
和for...in
虽然语法相似,但有本质区别:
const arr = ['a', 'b', 'c'];
arr.foo = 'bar';
// for...in 遍历可枚举属性(包括原型链)
for (const key in arr) {
console.log(key); // 输出: 0, 1, 2, foo
}
// for...of 遍历可迭代对象的值
for (const value of arr) {
console.log(value); // 输出: a, b, c
}
自定义可迭代对象
我们可以创建自己的可迭代对象:
const myIterable = {
[Symbol.iterator]() {
let step = 0;
return {
next() {
step++;
if (step <= 3) {
return { value: `step ${step}`, done: false };
}
return { value: undefined, done: true };
}
};
}
};
for (const item of myIterable) {
console.log(item);
}
// 输出:
// step 1
// step 2
// step 3
提前终止迭代
for...of
循环可以通过break
、return
或throw
提前终止:
function* generateNumbers() {
yield 1;
yield 2;
yield 3;
}
for (const num of generateNumbers()) {
if (num > 2) break;
console.log(num);
}
// 输出: 1, 2
异步迭代
ES2018引入了异步迭代器和for await...of
语法:
async function* asyncGenerator() {
yield await Promise.resolve(1);
yield await Promise.resolve(2);
yield await Promise.resolve(3);
}
(async function() {
for await (const num of asyncGenerator()) {
console.log(num);
}
})();
// 输出: 1, 2, 3
解构赋值与for...of
for...of
可以与解构赋值结合使用:
const users = [
{ name: 'Alice', age: 25 },
{ name: 'Bob', age: 30 },
{ name: 'Charlie', age: 35 }
];
for (const { name, age } of users) {
console.log(`${name} is ${age} years old`);
}
性能考虑
在大多数现代JavaScript引擎中,for...of
循环的性能已经接近传统for
循环。但在性能关键代码中,仍需要注意:
// 传统for循环
for (let i = 0; i < array.length; i++) {
// 可能更快
}
// for...of循环
for (const item of array) {
// 更简洁但可能稍慢
}
常见使用场景
遍历类数组对象
function sum() {
let total = 0;
for (const num of arguments) {
total += num;
}
return total;
}
console.log(sum(1, 2, 3)); // 6
遍历生成器函数
function* fibonacci() {
let [prev, curr] = [0, 1];
while (true) {
[prev, curr] = [curr, prev + curr];
yield curr;
}
}
for (const num of fibonacci()) {
if (num > 1000) break;
console.log(num);
}
同时获取索引和值
使用entries()
方法:
const colors = ['red', 'green', 'blue'];
for (const [index, color] of colors.entries()) {
console.log(`Color at position ${index} is ${color}`);
}
与其他迭代方法的比较
与forEach比较
// forEach
[1, 2, 3].forEach((item) => {
console.log(item);
});
// for...of
for (const item of [1, 2, 3]) {
console.log(item);
}
for...of
的优势在于可以使用break
和continue
,而forEach
不行。
与for循环比较
// 传统for循环
for (let i = 0; i < array.length; i++) {
if (array[i] === 'stop') break;
console.log(array[i]);
}
// for...of
for (const item of array) {
if (item === 'stop') break;
console.log(item);
}
for...of
更简洁,但传统for
循环可以更灵活地控制索引。
浏览器兼容性与转译
虽然现代浏览器都支持for...of
,但在旧环境中需要使用Babel等工具转译:
// 转译前
for (const item of iterable) {
// ...
}
// 转译后
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = iterable[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var item = _step.value;
// ...
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return != null) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益,请来信告知我们删除。邮箱:cc@cccx.cn