ES6 loop and iterable object examples

ES6 loop and iterable object examples

This article will examine the ES6 for ... of loop.

Old method

In the past, there were two ways to traverse JavaScript.

First up is the classic for i loop, which lets you iterate over an array or any object that is indexable and has a length property.

for(i=0;i<things.length;i++) {
 var thing = things[i]
 /* ... */
}

The second is the for ... in loop, which is used to loop over the key/value pairs of an object.

for(key in things) {
 if(!thing.hasOwnProperty(key)) { continue; }
 var thing = things[key]
 /* ... */
}

The for ... in loop is often considered an aside because it loops over every enumerable property of an object[1]. This includes properties of parent objects in the prototype chain, as well as any properties that are assigned as methods. In other words, it goes through some things that people might not expect. Using for ... in usually means a lot of guard clauses in the loop block to avoid unwanted properties.

Early JavaScript solved this problem through libraries. Many JavaScript libraries (eg Prototype.js, jQuery, lodash, etc.) have utility methods or functions like each or foreach that allow you to iterate over objects and arrays without for i or for ... in loops.

The for ... of loop is ES6's way of trying to solve some of these problems without the need for third-party libraries.

for … of

for ... of loop

for(const thing of things) {
 /* ... */
}

It will iterate over an iterable object.

An iterable is an object that defines an @@iterator method that returns an object that implements the iterator protocol, or that is a generator function.

There are a lot of things you need to understand in this sentence:

  • Iterable objects
  • @@iterator method (what does @@ mean?)
  • Iterator Protocol (What does protocol mean here?)
  • Wait, iterable and iterator are not the same thing?
  • Also, what the heck are generator functions?

Let’s address these questions one by one.

Built-in Iterable

First of all, some built-in objects in JavaScript are naturally iterable, such as the array object. You can use arrays in a for ... of loop as shown in the following code:

const foo = [
'apples','oranges','pears'
]

for(const thing of foo) {
 console.log(thing)
}

The output is all the elements in the array.

apples
oranges
pears

There is also an entries method on arrays, which returns an iterable object. This iterable returns the key and value on each pass through the loop. For example, the following code:

const foo = [
'apples','oranges','pears'
]

for(const thing of foo.entries()) {
 console.log(thing)
}

Will output the following

[ 0, 'apples' ]
[ 1, 'oranges' ]
[ 2, 'pears' ]

The entries method is more useful when using the following syntax:

const foo = [
 'apples','oranges','pears'
]

for(const [key, value] of foo.entries()) {
 console.log(key,':',value)
}

Two variables are declared in the for loop: one for the first item in the returned array (the key or index of the value) and another for the second item (the actual value that index corresponds to).

A plain javascript object is not iterable. If you execute the following code:

// Cannot execute normally const foo = {
 'apples':'oranges',
 'pears':'prunes'
}

for(const [key, value] of foo) {
 console.log(key,':',value)
}

You will get an error

$ node test.js
/path/to/test.js:6
for(const [key, value] of foo) {
TypeError: foo is not iterable

However, the static entries method of the global Object object accepts a plain object as an argument and returns an iterable object. A program like this:

const foo = {
 'apples':'oranges',
 'pears':'prunes'
}

for(const [key, value] of Object.entries(foo)) {
 console.log(key,':',value)
}

You can get the output you expect:

$ node test.js
apples : oranges
pears : prunes

Creating Your Own Iterable

If you want to create your own iterable objects, it takes a bit more time. You will remember that I said earlier:

An iterable is an object that defines an @@iterator method that returns an object that implements the iterator protocol, or that is a generator function.

The easiest way to understand this is to create iterable objects step by step. First, we need an object that implements the @@iterator method. The @@ notation is a bit misleading; what we are really doing is defining a method using the predefined Symbol.iterator symbol.

If you define an object with an iterator method and try to iterate over it:

const foo = {
 [Symbol.iterator]: function() {
 }
}

for(const [key, value] of foo) {
 console.log(key, value)
}

Got a new error:

for(const [key, value] of foo) {
^
TypeError: Result of the Symbol.iterator method is not an object

This is javascript telling us that it is trying to call the Symbol.iterator method, but the result of the call is not an object.

To eliminate this error, you need to use the iterator method to return an object that implements the iterator protocol. This means that the iterator method needs to return an object with a next key, which is a function.

const foo = {
 [Symbol.iterator]: function() {
 return {
 next: function() {
 }
 }
 }
}

for(const [key, value] of foo) {
 console.log(key, value)
}

If you run the code above, you will get a new error.

for(const [key, value] of foo) {
^
TypeError: Iterator result undefined is not an object

This time javascript tells us that it tried to call the Symbol.iterator method, and the object is indeed an object and implements the next method, but the return value of next is not the object that javascript expected.

The next function needs to return an object in a specific format - with two keys: value and done.

next: function() {
 //...
 return {
 done: false,
 value: 'next value'
 }
}

The done key is optional. If the value is true (indicating that the iterator has finished iterating), then the iteration has ended.

If done is false or not present, the value key is required. The value key is the value that should be returned by looping over this.

So put another program in your code with a simple iterator that returns the first ten even numbers.

class First20Evens {
 constructor() {
 this.currentValue = 0
 }

 [Symbol.iterator]("Symbol.iterator") {
 return {
 next: (function() {
 this.currentValue+=2
 if(this.currentValue > 20) {
  return {done:true}
 }
 return {
  value:this.currentValue
 }
 }).bind(this)
 }
 }
}

const foo = new First20Evens;
for(const value of foo) {
 console.log(value)
}

Generator

Manually constructing objects that implement the iterator protocol is not the only option. Generator objects (returned by generator functions) also implement the iterator protocol. The above example would look like this if constructed using a generator:

class First20Evens {
 constructor() {
 this.currentValue = 0
 }

 [Symbol.iterator]("Symbol.iterator") {
 return function*() {
 for(let i=1;i<=10;i++) {
 if(i % 2 === 0) {
  yield i
 }
 }
 }()
 }
}

const foo = new First20Evens;
for(const item of foo) {
 console.log(item)
}

This article won’t cover generators in detail, but if you need a primer you can read this article. The important takeaway for today is that we can make our Symbol.iterator method return a generator object, and that generator object will "just work" in a for ... of loop. “Works properly” means that the loop continues to call next on the generator until the generator stops yielding values.

$ node sample-program.js
2
4
6
8
10

References

Each enumerable property of the object: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...in

Summarize

This is the end of this article about ES6 loops and iterable objects. For more information about ES6 loops and iterable objects, please search for previous articles on 123WORDPRESS.COM or continue to browse the following related articles. I hope you will support 123WORDPRESS.COM in the future!

You may also be interested in:
  • Implementation of ES6 iterators and iterable objects
  • Detailed explanation of the difference between let and var in es6 for loop
  • ES6 tutorial for loop and Map, Set usage analysis
  • ES6 New Features 2: Iterator (traversal) and for-of loop detailed explanation
  • ES6 Iterator and for.of loop usage learning (summary)
  • Detailed explanation of the difference between CommonJS and ES6 module loop loading processing
  • ES6 introductory tutorial: Iterator and for...of loop detailed explanation
  • Analysis of ES6 Iterator interface and for...of loop usage

<<:  How to solve the problem of ping being successful but the port being unavailable in Linux

>>:  Detailed explanation of the implementation of regular backup of MySQL database tables

Recommend

Detailed explanation of MySQL transaction processing usage and example code

MySQL transaction support is not bound to the MyS...

How to operate json fields in MySQL

MySQL 5.7.8 introduced the json field. This type ...

How to understand JS function anti-shake and function throttling

Table of contents Overview 1. Function debounce 2...

Javascript to achieve the effect of closing advertisements

Here is a case study on how to close ads using Ja...

Four ways to compare JavaScript objects

Table of contents Preface Reference Comparison Ma...

Use Nginx to build a streaming media server to realize live broadcast function

Written in front In recent years, the live stream...

MySQL Daemon failed to start error solution

MySQL Daemon failed to start error solution A few...

How to uninstall MySQL 8.0 version under Linux

1. Shut down MySQL [root@localhost /]# service my...

How to use MySQL covering index and table return

Two major categories of indexes Storage engine us...

Detailed explanation of the practical application of centos7 esxi6.7 template

1. Create a centos7.6 system and optimize the sys...

Example of troubleshooting method to solve Nginx port conflict

Problem Description A Spring + Angular project wi...

Detailed explanation of how to use WeChat mini program map

This article example shares the specific implemen...