Introduction
In computer science, an object or any data source that can be sequentially iterated is referred to as iterable. Iterable objects are those that can use for-loop and return some value. Consider the array items, which can be iterated over by utilising their indices. You've undoubtedly heard of iterations in computer science, which are related to iterable objects. Iterations are only possible when there is anything on which to iterate.
Source: meme
In this article, We'll go through each aspect of Iterables in Javascript.
See, Javascript hasOwnProperty
What are the Iterable objects?
An iterable javascript object can return its members one at a time, allowing it to be iterated over in a for-loop. Javascript includes a protocol that will enable control structures such as for...of and the spread operator... to cycle through data sequentially using objects such as arrays. This is known as the iterable, and the data structures that support it are known as iterables.
Now, what is the spread operator?
The spread operator is a new addition to the JavaScript ES6 operator set. It takes an iterable (for example, an array) and divides it into individual elements. To produce shallow copies of JS objects, the spread operator is widely employed. Using this operation shortens the code and improves its readability.
Three dots ... represent the spread operator.
To do so, you must understand how Javascript protocols work.
The Iterator protocol
The iterator protocol defines a standard for producing a sequence of values and a possible return value after all created values.
An object is an iterator when it implements the next() method with the following semantics:
next() method: A function with either zero or one argument returns an object with at least the two properties listed below.
- value (the next value)
- done (true or false)
value
Any value returned by the iterator.
(Can be omitted if done is true)
done
Has the value false if the iterator has more values to be produced.
Has the value true if the iterator has no other values to be produced.
For example:-
// Create a function rangeIterator, provide the default values for its parameters and iterate over the range
function rangeIterator(start = 0, end = Infinity, step = 1) {
// Assign the start to nextIndex
let nextIndex = start;;
// rangeIterator object
const rangeIterator = {
next: function() {
let result;
// To check if next Index is less or equals to the end
if (nextIndex <= end) {
// If the condition is satisfied, update the result
result = {
value: nextIndex,
done: false
}
// Increment the next Index
nextIndex += step;
// return the result
return result;
}
// If not return undefined in the value and true for done
return {
value: undefined,
done: true
}
}
};
// return the object
return rangeIterator;
}
// calling statement
const obj = rangeIterator(1, 10, 2);
// calls to the next() to get the values and done statement
console.log(obj.next());
console.log(obj.next());
console.log(obj.next());
console.log(obj.next());
console.log(obj.next());
console.log(obj.next());
Output
{ value: 1, done: false }
{ value: 3, done: false }
{ value: 5, done: false }
{ value: 7, done: false }
{ value: 9, done: false }
{ value: undefined, done: true }
At this time, the best explanation for the iterator property is that it is a property that understands how to retrieve elements from a collection one by one and offers a logical rule to cease doing so (e.g., if there are no more elements in the array).
Many people are misled into thinking that if an object is an iterator, it must also be iterable, which is incorrect. We won't obtain the same result if we use the for..of or spread operator on the object obj in the preceding example.
const obj = rangeIterator(1, 10, 2);
for (let i of obj){
console.log(obj.next());
}
Output
for (let i of obj){
^
TypeError: obj is not iterable
Let’s discuss it below with the help of Iterable protocol:-
The Iterable protocol
The iterable protocol enables JavaScript objects to define or customize iteration behavior, such as looping values in a for...of construct. Some built-in types, such as Array or Map, are built-in iterables with default iteration behavior, although others (such as Object) are not.
The iterable protocol notion is divided into two parts: the iterable (the data structure itself) and the iterator (sort of a pointer that moves over the iterable).
For example:-
const arr = [1,2,3,4];
// The right side of the for..of must be an iterable
for (let val of arr){
// arr is an iterable
console.log(val);
}
Output
1
2
3
4
Let’s do this for the non-iterable object:-
const obj = {
'fname':'Coding',
'lname':'Ninjas'
}
for (let val of obj){
// obj is a non-iterable object
console.log(val);
}
Output:-
for (let val of obj){
^
TypeError: obj is not iterable
at Object.<anonymous> (/tmp/cL1QJ0QeF1.js:9:17)
at Module._compile (internal/modules/cjs/loader.js:778:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:789:10)
at Module.load (internal/modules/cjs/loader.js:653:32)
at tryModuleLoad (internal/modules/cjs/loader.js:593:12)
at Function.Module._load (internal/modules/cjs/loader.js:585:3)
at Function.Module.runMain (internal/modules/cjs/loader.js:831:12)
at startup (internal/bootstrap/node.js:283:19)
at bootstrapNodeJSCore (internal/bootstrap/node.js:623:3)
Now, the question is, why are the objects not iterable in javascript?
The JavaScript exception "is not iterable" occurs when the value supplied as the right-hand side of for...of is not an iterable object.
Objects in JavaScript are not iterable unless they implement the iterable protocol. As a result, you cannot use for...of to iterate through an object's attributes.
To iterate through an object's properties or entries, you must use Object.keys or Object.entries.
const obj = {
'fname':'Coding',
'lname':'Ninjas'
}
// Iterate over the property names:
for(let key of Object.keys(obj)){
let value = obj[key];
console.log(key, value);
}
Output
fname Coding
lname Ninjas
An object must implement the @@iterator method to be iterable, which means that the object (or one of the objects in its prototype chain) must have a property with a @@iterator key accessible through constant [Symbol.iterator], it further has the next() method for every object. In some cases, if the object is an iterator, i.e., it implements the next() method, the @@iterator method will not be considered iterable.
Let us understand this by utilizing spread operator and for..of:-
// Maps are iterable as they implement both iterator and iterable protocol
const map = new Map();
map.set('Villa1', 'CodersVilla');
map.set('Villa2', 'NinjasVilla');
map.set('Villa3', 'ProgrammersVilla');
// Right side of the for..of loop must be an iterable
for(let it of map){
console.log(it);
}
Output
[ 'Villa1', 'CodersVilla' ]
[ 'Villa2', 'NinjasVilla' ]
[ 'Villa3', 'ProgrammersVilla' ]
// Maps are iterable as they implement both iterator and iterable protocol
const map = new Map();
map.set('Villa1', 'CodersVilla');
map.set('Villa2', 'NinjasVilla');
map.set('Villa3', 'ProgrammersVilla');
// By utilizing the spread operator
console.log(...map);
Output
[ 'Villa1', 'CodersVilla' ] [ 'Villa2', 'NinjasVilla' ] [ 'Villa3', 'ProgrammersVilla' ]
As we can see, maps in javascript are iterable since they can be executed using the for..of and spread operators. Let's test it with a non-iterable object.
const schools = {
name: 'Yorktown',
name: 'Stratford',
name: 'Washington & Lee',
name: 'Wakefield'
}
// using the spread operator with a non-iterable object
console.log(...schools);
Output
console.log(...schools);
^
TypeError: Found non-callable @@iterator
at Object.<anonymous> (/tmp/majE7xy8VH.js:9:9)
at Module._compile (internal/modules/cjs/loader.js:778:30)
Consider an array: when it is used in a for...of loop, the iterable property is used, which returns an iterator. This iterable property is namespaced as Symbol.iterator, and the object returned by it can be utilised on a common interface shared by all looping control structures.
In some ways, the Symbol.iterator is analogous to an iterator factory, which generates an iterator whenever the data structure is placed in a loop.
Let’s discuss it:-
Symbol.iterator
Symbol.iterator allows developers to reconfigure navigation patterns without copying or altering the object's order in place.
These patterns can then be defined once and used wherever by storing them in a function, class, or module. That is, you can loop through an Array in a modular way forwards, backward, randomly, or eternally.
The Symbol.iterator protocol itself is pretty straightforward. It includes the following:
Iterable: a function-containing object with the key Symbol.iterator.
Iterator: the function mentioned above that is utilised to acquire the values to be iterated.
For example:-
The example below defines an array, marks, then obtains an iterator object. The [Symbol.iterator]() function can be used to get an iterator object. The iterator's next() function returns an object with the attributes 'value' and 'done'. 'done' is a Boolean function that returns true after reading all of the items in the collection.
let marks = [50,100,39]
let it = marks[Symbol.iterator]();
console.log(it.next())
console.log(it.next())
console.log(it.next())
console.log(it.next())
Output
{ value: 50, done: false }
{ value: 100, done: false }
{ value: 39, done: false }
{ value: undefined, done: true }
This informs the Javascript runtime that an Object is iterable. It offers a method for obtaining the next value and determining whether the iteration is complete.
Now that we have gone through each method by which we can determine if the object is iterable or not let us now have a look at some use cases of iterable in javascript:-
Use cases of Symbol.iterator
-
Going Backwards
The two most frequent methods for iterating an array in reverse are indices (from array.length — 1 to 0) and Array.reverse:
let numbers = [1, 2, 3, 4, 5]
for (let i = numbers.length - 1; i >= 0; i--) {
// print in reverse order
}
for (let num of numbers.reverse()) {
// print in reverse order
}
console.log(numbers) // [5, 4, 3, 2, 1]
OR
However, both techniques raise questions. When working with indices, it's easy to overlook the -1, resulting in the terrible off-by-one error.
However, both techniques raise questions. When working with indices, it's easy to overlook the -1, resulting in the terrible off-by-one error.
Using Arrays.reverse the array changes it in place, which isn't usually the intent.
Let us do the same thing with the help of Symbol.iterable:-
// Customized iterable for printing the array in reverse order
const reverse = arr => ({
[Symbol.iterator]() {
// take the length
let i = arr.length;
return {
next: () => ({
// started from the last index
value: arr[--i],
// until the i becomes 0
done: i < 0
})
}
}
})
//
let numbers = [1,3,6,8,9,10]
for (let num of reverse(numbers)) {
console.log(num)
}
Output:
10
9
8
6
3
1
Rather than iterating backward on a specific array, we've defined the "moving backward" iteration behaviour for any array, without changing the order or producing a clone.
-
Another use case is a music player:-
Iteration behaviour can be seen in a music player. The majority of us do not listen to songs in chronological sequence. Instead, we use shuffle and repeat to manage the playback.
As a programmer, this entails creating a system that can play songs endlessly (repeat), randomly (shuffle), or both.
Again, Symbol.iterator is used to implement the logic that underpins various playback choices.
Now, what’s next?
Since we’ve covered a lot about Iterables in javascript and try by yourself on an online js compiler. it’s time to warp up the session with some faqs.