阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > for...of循环原理

for...of循环原理

作者:陈川 阅读数:23223人阅读 分类: JavaScript

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()方法,该方法返回一个包含valuedone属性的对象:

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...offor...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循环可以通过breakreturnthrow提前终止:

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的优势在于可以使用breakcontinue,而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

上一篇:迭代器对象

下一篇:生成器函数语法

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌