The for... ofAnd its use

As we know, the introduction of for… Of loop, often used instead of for… In and forEach(), and support for the new iteration protocol. for… Of allows you to iterate over arrays, strings, maps, sets,TypedArray, arguments, NodeList objects, Generators, and other iterable data structures. for… The OF statement creates an iterative loop over the iterable, calls the custom iteration hook, and executes the statement for each of the different property values.

for... ofGrammar:

for (variable of iterable) {
    // statement
}
// variable: Attribute values for each iteration are assigned to this variable.
Iterable: an object with enumerable properties that can be iterated over.
Copy the code

Common usage

{
  // Iterate over the string
  const iterable = 'ES6';
  for (const value of iterable) {
    console.log(value);
  }
  // Output:
  // "E"
  // "S"
  / / "6"
}
{
  // Iterate over the array
  const iterable = ['a'.'b'];
  for (const value of iterable) {
    console.log(value);
  }
  // Output:
  // a
  // b
}
{
  // Iterate over the Set
  const iterable = new Set([1.2.2.1]);
  for (const value of iterable) {
    console.log(value);
  }
  // Output:
  / / 1
  / / 2
}
{
  / / iterative Map
  const iterable = new Map([["a".1], ["b".2], ["c".3]]);
  for (const entry of iterable) {
    console.log(entry);
  }
  // Output:
  // ["a", 1]
  // ["b", 2]
  // ["c", 3]

  for (const [key, value] of iterable) {
    console.log(value);
  }
  // Output:
  / / 1
  / / 2
  / / 3
}
{
  // Iterate over Arguments Object
  function args() {
    for (const arg of arguments) {
      console.log(arg);
    }
  }
  args('a'.'b');
  // Output:
  // a
  // b
}
{
  // Iteration generator
  function* foo(){ 
    yield 1; 
    yield 2; 
    yield 3; 
  }; 

  for (let o of foo()) { 
    console.log(o); 
  }
  // Output:
  / / 1
  / / 2
  / / 3
}
Copy the code

Uncaught TypeError: obj is not iterable

// Normal objects
const obj = {
  foo: 'value1'.bar: 'value2'
}
for(const item of obj){
  console.log(item)
}
// Uncaught TypeError: obj is not iterable
Copy the code

As you can see, for of can iterate over most objects, even strings, but not normal objects.

How to usefor... ofIterating over ordinary objects

From the basic usage above, we know that for… “Of” can iterate over data structures such as arrays and maps. Along the same lines, we can use for… Of traverses ordinary objects.

  • Object.values(),Object.keys(),Object.entries()Usage and return value
const obj = {
  foo: 'value1'.bar: 'value2'
}
// Print an array of values
console.log(Object.values(obj)) // ["value1", "value2"]

// Print an array of keys
console.log(Object.keys(obj)) // ["foo", "bar"]

// Print a two-dimensional array consisting of [key, value]
// copy(object.entries (obj)) can copy the output directly to the clipboard and then paste it
console.log(Object.entries(obj)) // [["foo","value1"],["bar","value2"]]
Copy the code
  • becausefor... ofWe can iterate over arrays and maps, so we get the following method for traversing a normal object
const obj = {
  foo: 'value1'.bar: 'value2'
}
// Method 1: Use for of to iterate over the two-dimensional array formed by object.entries (obj), and use deconstructing to assign values
for(const [, value] of Object.entries(obj)){
  console.log(value) // value1, value2
}

// Method 2: Map
// Convert common objects to Map
// Map can take an array as an argument. The members of the array are arrays representing key-value pairs
console.log(new Map(Object.entries(obj)))

// Iterate over the Map generated by the normal object
for(const [, value] of new Map(Object.entries(obj))){
  console.log(value) // value1, value2
}

// Method 3: Continue to use for in
for(const key in obj){
  console.log(obj[key]) // value1, value2
}

{
  // Convert an array-like object to an array
  // The object must have a length attribute and its elements must be indexable.
  const obj = {
    length: 3.// Length is required, otherwise nothing will be printed
    0: 'foo'.1: 'bar'.2: 'baz'.a: 12  // Non-digital properties are not printed
  };
  const array = Array.from(obj); // ["foo", "bar", "baz"]
  for (const value of array) { 
      console.log(value);
  }
  // Output: foo bar baz
}
{
  // Method 5: deploy the array's [Symbol. Iterator] method [invalid for normal string attribute objects]
  const iterable = {
    0: 'a'.1: 'b'.2: 'c'.length: 3[Symbol.iterator]: Array.prototype[Symbol.iterator]
  };
  for (let item of iterable) {
    console.log(item); // 'a', 'b', 'c'}}Copy the code

Matters needing attention

  • As distinct from non-terminating traversalforEach.for... ofThe cycle ofbreak.throw.continuereturnTerminate, in which case the iterator closes.
  const obj = {
    foo: 'value1'.bar: 'value2'.baz: 'value3'
  }
  for(const [, value] of Object.entries(obj)){
    if (value === 'value2') break // The next iteration will not be executed
    console.log(value) // value1
  };
  [1.2].forEach(item= > {
      if(item == 1) break // Uncaught SyntaxError: Illegal break statement
      console.log(item)
  });
  [1.2].forEach(item= > {
      if(item == 1) continue // Uncaught SyntaxError: Illegal continue statement: no surrounding iteration statement
      console.log(item)
  });
  [1.2].forEach(item= > {
      if(item == 1) return // Still continue the next loop, printing 2
      console.log(item) / / 2
  })
Copy the code
  • The For… Of with the For… In contrast to

    • for... inNot only does it enumerate array declarations, it also looks for inherited non-enumeration properties from the constructor’s prototype;
    • for... ofDo not consider non-enumerable properties on the constructor prototype (orfor... ofThe iterable defines the data to iterate over. ;
    • for... ofMore for specific collections (such as groups of objects), but not all objects can befor... ofIteration.
      Array.prototype.newArr = () = > {};
      Array.prototype.anotherNewArr = () = > {};
      const array = ['foo'.'bar'.'baz'];
      for (const value in array) { 
        console.log(value); // 0 1 2 newArr anotherNewArr
      }
      for (const value of array) { 
        console.log(value); // 'foo', 'bar', 'baz'
      }
    Copy the code

Why can’t ordinary objects befor ofThe iteration

We mentioned earlier the term “iterable” data structures, and when we iterate over ordinary objects with for of, we also report a “not iterable” error. In fact, any element with a Symbol. Iterator attribute is iterable. We can simply look at a few objects that can be iterated for of to see how they differ from normal objects:

As you can see, all of these objects that can be iterated for implement a Symbol(symbol.iterator) method, whereas normal objects do not.

In simple terms, the for of statement creates a loop to iterate over an iterable object. An iterable object internally implements the Symbol. Iterator method, while a normal object does not, so a normal object is not iterable.

Iterator (Iterator)

For more information on the concept of Iterator, see Ruan yifong’s introduction to ECMAScript 6:

In short, ES6 adds an iterator interface to unify the processing of collection type data structures. Of simplifies the processing of data with different structures. Iterators, on the other hand, iterate in a generator-like manner, calling the next method over and over, returning an object containing the value and done properties that indicate whether the iterator has finished.

How to implementSymbol.iteratorMethod to enable ordinary objects to befor ofThe iteration

Following the instructions above, let’s first look at the array’s Symbol. Iterator interface:

const arr = [1.2.3];
const iterator = arr[Symbol.iterator]();
console.log(iterator.next()); // {value: 1, done: false}
console.log(iterator.next()); // {value: 2, done: false}
console.log(iterator.next()); // {value: 3, done: false}
console.log(iterator.next()); // {value: undefined, done: true}
Copy the code

We can try implementing a Symbol. Iterator interface for a normal object:

// Normal objects
const obj = {
  foo: 'value1'.bar: 'value2'[Symbol.iterator]() {
    // In this case, the object. keys will not get the Symbol
    const keys = Object.keys(obj); 
    let index = 0;
    return {
      next: () = > {
        if (index < keys.length) {
          // The iteration result is not finished
          return {
            value: this[keys[index++]],
            done: false
          };
        } else {
          // The iteration result is finished
          return { value: undefined.done: true}; }}}; }}for (const value of obj) {
  console.log(value); // value1 value2
};
Copy the code

We implement Symbol. Iterator for obj, and we can even do something like this:

console.log([...obj]); // ["value1", "value2"]
console.log([...{}]); // console.log is not iterable (cannot read property Symbol(Symbol.iterator))
Copy the code

We have implemented a Symbol. Iterator interface for our obj Object. It is important to note that we do not have to worry about the [Symbol. Because the Symbol. The iterator such Symbol attribute, need through the Object. The getOwnPropertySymbols (obj) to earn, Object. GetOwnPropertySymbols () method returns a given Object itself all the attributes of the Symbol of the array.

There are a few cases where the Iterator interface is called by default (i.e. Symbol.

  • Extension operator.This provides a simple mechanism to convert any data structure with an Iterator interface deployed into an array. That is, as long as a data structure has an Iterator interface deployed, you can use the extension operator on it and turn it into an array (not surprisingly, code){} [...]You get an error, and'123'] [...It will print an array[' 1 ', '2', '3']).
  • Destructuring assignments to arrays and iterables (destructuring is the syntactic sugar provided by ES6, which is inherently designed forIterable objecttheThe Iterator interfaceThrough theTraverse deviceGet the corresponding values in order for assignment. whileThe internal mechanism for deconstructing and assigning values to ordinary objects is to find attributes of the same name and then assign them to the corresponding variable). ;
  • yield*:_yield*This is followed by a traversable structure that calls its traverser interface;
  • Since traversing an array calls the traverser interface, any situation that takes an array as an argument is called;
  • A string is an array-like object that also has a native Iterator interface, so it can also be used byfor ofIteration.

Iterator pattern

The iterator pattern provides a way to access the elements of an aggregate object sequentially without exposing the internal implementation of the object, thereby allowing external code to transparently access the data inside the collection without exposing the internal structure of the collection. The iterator pattern provides a unified interface for traversing different collection structures, enabling the same algorithm to operate on different collection structures.

It is easy to see that Symbol. Iterator implements an iterator pattern. The collection object internally implements the Symbol. Iterator interface for external invocation, and we do not need to pay much attention to the internal structure of the collection object. When we need to process the data inside the collection object, we can call the Symbol.

For example, if we implement the Symbol. Iterator interface for the normal object in the preceding section, if we adjust the data structure in obj as follows, then we only need to change the Symbol.

const obj = {
  // Data structure adjustment
  data: ['value1'.'value2'],
  [Symbol.iterator]() {
    let index = 0;
    return {
      next: () = > {
        if (index < this.data.length) {
          // The iteration result is not finished
          return {
            value: this.data[index++],
            done: false
          };
        } else {
          // The iteration result is finished
          return { value: undefined.done: true}; }}}; }}// External call
for (const value of obj) {
  console.log(value); // value1 value2
}
Copy the code

In practice, we can take the Symbol. Iterator above and wrap it separately so that we can iterate over a class of data structures. Of course, the code below is just the simplest example, and you can explore more practical tips from there.

const obj1 = {
  data: ['value1'.'value2']}const obj2 = {
  data: [1.2]}// Iterate over the method
consoleEachData = (obj) = > {
  obj[Symbol.iterator] = () = > {
    let index = 0;
    return {
      next: () = > {
        if (index < obj.data.length) {
          return {
            value: obj.data[index++],
            done: false
          };
        } else {
          return { value: undefined.done: true}; }}}; }for (const value of obj) {
    console.log(value);
  }
}
consoleEachData(obj1); // value1 value2
consoleEachData(obj2); / / 1. 2
Copy the code

Added a little

As I wrote this article, I had a problem: native objects do not deploy an Iterator interface by default, that is, objects are not iterable, so what is the mechanism for deconstructing assignment of native objects?

There is a claim that ES6 provides a Map data structure. In fact, when a native object is destructed, it is destructed as a Map. Do you have any different opinions on this? Feel free to explore in the comments section.

The resources

  • MDN: for… of
  • Understanding the JavaScript For… Of the Loop (【 原 文】)
  • The Iterator and for… Of circulation

This article was first published on my personal blog.