阿里云主机折上折
  • 微信号
Current Site:Index > The principle of the for...of loop

The principle of the for...of loop

Author:Chuan Chen 阅读数:23225人阅读 分类: JavaScript

The for...of loop is a new syntax introduced in ECMAScript 6 for traversing data structures. It is based on the iterator protocol and provides a unified way to handle iterable objects such as arrays, strings, Maps, and Sets. Compared to traditional for loops and the forEach method, it offers a more concise syntax and more powerful functionality.

Basic Syntax of for...of Loop

The syntax of the for...of loop is very simple:

for (const item of iterable) {
  // Loop body
}

Here, iterable can be any object that implements the iterable protocol. Below is a simple example of traversing an array:

const fruits = ['apple', 'banana', 'orange'];

for (const fruit of fruits) {
  console.log(fruit);
}
// Output:
// apple
// banana
// orange

Iterator Protocol and Iterable Objects

The core of the for...of loop lies in the iterator protocol. For an object to be iterable, it must implement the @@iterator method, meaning it must have a Symbol.iterator property. This property is a function that returns an iterator object.

The iterator object must implement a next() method, which returns an object with value and done properties:

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 }

Built-in Iterable Objects

Many built-in objects in ECMAScript 6 are iterable:

Arrays

const numbers = [10, 20, 30];
for (const num of numbers) {
  console.log(num * 2);
}
// Output: 20, 40, 60

Strings

Each character in a string is iterated sequentially:

const greeting = 'Hello';
for (const char of greeting) {
  console.log(char);
}
// Output: H, e, l, l, o

Maps and Sets

const map = new Map();
map.set('name', 'John');
map.set('age', 30);

for (const [key, value] of map) {
  console.log(`${key}: ${value}`);
}
// Output: name: John, age: 30

const set = new Set([1, 2, 3]);
for (const item of set) {
  console.log(item * 2);
}
// Output: 2, 4, 6

NodeList

DOM NodeLists are also iterable:

const divs = document.querySelectorAll('div');
for (const div of divs) {
  div.style.color = 'red';
}

Differences from for...in Loop

Although for...of and for...in have similar syntax, they are fundamentally different:

const arr = ['a', 'b', 'c'];
arr.foo = 'bar';

// for...in iterates over enumerable properties (including the prototype chain)
for (const key in arr) {
  console.log(key); // Output: 0, 1, 2, foo
}

// for...of iterates over the values of iterable objects
for (const value of arr) {
  console.log(value); // Output: a, b, c
}

Custom Iterable Objects

We can create our own iterable objects:

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);
}
// Output:
// step 1
// step 2
// step 3

Early Termination of Iteration

The for...of loop can be terminated early using break, return, or throw:

function* generateNumbers() {
  yield 1;
  yield 2;
  yield 3;
}

for (const num of generateNumbers()) {
  if (num > 2) break;
  console.log(num);
}
// Output: 1, 2

Asynchronous Iteration

ES2018 introduced asynchronous iterators and the for await...of syntax:

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);
  }
})();
// Output: 1, 2, 3

Destructuring Assignment with for...of

for...of can be combined with destructuring assignment:

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`);
}

Performance Considerations

In most modern JavaScript engines, the performance of for...of loops is close to that of traditional for loops. However, in performance-critical code, caution is still advised:

// Traditional for loop
for (let i = 0; i < array.length; i++) {
  // Potentially faster
}

// for...of loop
for (const item of array) {
  // More concise but slightly slower
}

Common Use Cases

Traversing Array-like Objects

function sum() {
  let total = 0;
  for (const num of arguments) {
    total += num;
  }
  return total;
}

console.log(sum(1, 2, 3)); // 6

Traversing Generator Functions

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);
}

Getting Both Index and Value

Using the entries() method:

const colors = ['red', 'green', 'blue'];

for (const [index, color] of colors.entries()) {
  console.log(`Color at position ${index} is ${color}`);
}

Comparison with Other Iteration Methods

Comparison with forEach

// forEach
[1, 2, 3].forEach((item) => {
  console.log(item);
});

// for...of
for (const item of [1, 2, 3]) {
  console.log(item);
}

The advantage of for...of is that it allows the use of break and continue, which forEach does not.

Comparison with for Loop

// Traditional for loop
for (let i = 0; i < array.length; i++) {
  if (array[i] === 'stop') break;
  console.log(array[i]);
}

// for...of loop
for (const item of array) {
  if (item === 'stop') break;
  console.log(item);
}

for...of is more concise, but traditional for loops offer more flexible control over indices.

Browser Compatibility and Transpilation

Although modern browsers support for...of, older environments may require transpilation tools like Babel:

// Before transpilation
for (const item of iterable) {
  // ...
}

// After transpilation
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

上一篇:迭代器对象

下一篇:生成器函数语法

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.