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

Solution to Chinese garbled characters when operating MySQL database in CMD

I searched on Baidu. . Some people say to use the...

HTML table markup tutorial (2): table border attributes BORDER

By default, the border of the table is 0, and we ...

Detailed tutorial on installing Mysql5.7.19 on Centos7 under Linux

1. Download MySQL URL: https://dev.mysql.com/down...

In-depth analysis of MySQL explain usage and results

Preface In daily work, we sometimes run slow quer...

MySQL's method of dealing with duplicate data (preventing and deleting)

Some MySQL tables may contain duplicate records. ...

React.js framework Redux basic case detailed explanation

react.js framework Redux https://github.com/react...

Vue realizes the function of uploading photos on PC

This article example shares the specific code of ...

Install docker offline by downloading rpm and related dependencies using yum

You can use yum to install all dependencies toget...

MySQL string splitting operation (string interception containing separators)

String extraction without delimiters Question Req...

Example of using the href attribute and onclick event of a tag

The a tag is mainly used to implement page jump, ...

Flex layout allows subitems to maintain their own height

When using Flex layout, you will find that when a...