Categories
JavaScript JavaScript Basics

JavaScript Best Practice — Replacing Old with New

Like any other programming language, JavaScript has its own list of best practices to make programs easier to read and maintain. There are a lot of tricky parts to JavaScript, and we can follow some best practices to improve our code.

Since ES6 was introduced, new constructs are replacing older ones for good reasons. It’s much shorter, cleaner, and easier to understand.

In this article, we’ll look at which older constructs that can be replaced with new ones, including replacing then with async and await, replacing dictionary objects with Maps, replacing apply with the spread operator, and replacing function constructors with the class syntax.

Replace Then with Async / Await

When we chain promises, we used to do it by using the then method and then returning another promise in the callback function that we pass into then.

This means that we have code that looks something like this:

Promise.resolve(1)  
  .then((val) => {  
    console.log(val);  
    return Promise.resolve(2);  
  })  
  .then((val) => {  
    console.log(val);  
    return Promise.resolve(3);  
  })  
  .then((val) => {  
    console.log(val);  
  })

With the introduction of the async and await syntax, which is just a shorthand for calling the then method repeatedly. We can write the code above like this:

(async () => {  
  const val1 = await Promise.resolve(1);  
  console.log(val1);  
  const val2 = await Promise.resolve(2);  
  console.log(val2);  
  const val3 = await Promise.resolve(3);  
  console.log(val3);  
})();

They both output 1, 2 and 3, but the second code snippet is so much shorter. It’s so much clearer that there’s no reason to go back to the old syntax.

Also, we can loop through promises and run them one after the other by using the for-await-of loop. We can loop through the promises we have above by rewriting the code like the following:

(async () => {  
  const promises = [  
    Promise.resolve(1),  
    Promise.resolve(2),  
    Promise.resolve(3),  
  ] 

  for await (let p of promises) {  
    const val = await p;  
    console.log(val);  
  }  
})();

Code like the one we have above is very handy for looping through many promises or promises that are created on the fly, which we can’t do in the earlier examples.

Replacing Dictionary Objects with Maps

ES6 also introduced the Map constructor, which lets us create hash maps or dictionaries without using JavaScript objects with string keys to do so.

Maps are also better because they have their own methods to get and manipulate the entries.

For example, instead of writing:

const dict = {  
  'foo': 1,  
  'bar': 2  
}

We write:

const dict = new Map([  
  ['foo', 1],  
  ['bar', 2]  
])

Then we can get an entry by using the get method as follows:

console.log(dict.get('foo'));

We can set the value of an existing entry by the key of the entry with the set method:

dict.set('foo', 2);

Also, we can check if an entry exists with the given key with the has method:

dict.has('baz');

There are also the keys and entries methods to get all the keys of the map and all the entries respectively.

For example, we can write:

console.log(dict.keys());

To get an iterator object with the keys of the Map . This means that we can loop through them with the for...of loop or convert it to an array with the spread operator.

Similarly, the entries method returns an iterator object with all the entries in the Map with each entry being an array of [key, value] .

There’s also the value method to get an iterator object with all the values in the Map .

We can also use other primitive values as keys. If we use objects, we can’t get the value back when we look them up since the lookup is done with the === operator which returns false is 2 objects that don’t have the same reference even if they have the same content.

These methods aren’t available in a regular object. Also, we might accidentally get or modify the object’s prototype’s properties instead of its own if we use the for...in loop.

Therefore, there aren’t many reasons to use a regular object as a dictionary anymore.

Replace Apply with Spread

If we don’t want to change the value of this inside the function, there isn’t much reason to use the apply method in functions.

If we only want to call a function with many arguments, we can use the spread operator when passing in an array for our arguments as follows:

const arr = [1, 2, 3, 4, 5];  
const add = (...args) => args.reduce((a, b) => a + b, 0);  
console.log(add(...arr));

All we have to do is to use the spread operator on our array, which is the 3 dots operator in the last line, and then the array will be separated into a comma-separated list of arguments.

Replace Constructor Functions with Classes

Another great ES6 feature is the class syntax for constructor functions. It’s simply syntactic sugar that makes the constructor function looks like a class.

The advantage of it is that it makes inheritance easy.

For example, if we want to inherit from a constructor function, we have to write something like this:

function Person(name) {  
  this.name = name;  
}

function Employee(name, employeeCode) {  
  Person.call(this, name);  
  Employee.prototype.constructor = Person;  
  this.employeeCode = employeeCode;  
}

const employee = new Employee('Joe', 1);  
console.log(employee)

This syntax looks strange coming from class-based object-oriented languages like Java.

However, the class syntax makes things look a lot familiar to developers that used other languages more than JavaScript. We can rewrite the code above to the following:

class Person {  
  constructor(name) {  
    this.name = name;  
  }  
}

class Employee extends Person {  
  constructor(name, employeecode) {  
    super(name);  
    this.employeecode = employeecode;  
  }  
}

const employee = new Employee('Joe', 1);  
console.log(employee)

The code does the same thing as what we have above. However, it’s clearer what we’re inheriting from since we have the extends keyword to indicate what we’re inheriting from.

With the constructor function, we have to worry about the value of this in the first argument of the call method, and what we pass into the subsequent arguments of call.

With the class syntax, we don’t have to worry about this. If we forgot to make the super call like the following code:

class Person {  
  constructor(name) {  
    this.name = name;  
  }  
}

class Employee extends Person {  
  constructor(name, employeecode) {     
    this.employeecode = employeecode;  
  }  
}

const employee = new Employee('Joe', 1);  
console.log(employee)

We’ll get the error ‘Uncaught ReferenceError: Must call super constructor in derived class before accessing ‘this’ or returning from derived constructor.’

It’s one less chance of making a mistake.

We don’t get any error if we omit the Person.call like in the Employee constructor function since the browser doesn’t know we want Employee to inherit from Person .

In addition, when we log the prototype of employee , we get that the prototype of employee is Person as expected with the class syntax.

However, we don’t get that unless we put Employee.prototype.constructor = Person; which is easily forgotten.

Conclusion

async and await and for-await-of are new constructs that make chaining promises much cleaner. It’s much better to use them instead of using then because of it.

for-await-of also lets us loop through promises that are generated dynamically which we can’t do with then or async and await alone.

Map s are much better than plain objects for hashes and dictionaries because it has its own methods to manipulate and get the entries. Also, we may accidentally be accessing the properties of prototype of plain objects.

If we don’t want to change the value of this inside a function, we can replace the apply method for calling functions with an array of arguments with the spread operator, which does the same thing.

Finally, the class syntax for constructors is much better than the original function syntax since we can inherit from parent classes easier than setting the prototype constructor of a constructor function.

Categories
JavaScript JavaScript Basics

Why Should We Be Careful When Using the const JavaScript Keyword?

Since ES6 was released, lots of new features have been added to the JavaScript. One of them is the const keyword for declaring constants. It lets is create constants that can’t be reassigned to something else.

However, we can still change it in other ways. In this article, we’ll look at the often-overlooked characteristics of const and how to deal with the pitfalls.

Characteristics of const

const is a block-scope keyword for declaring constants that’s available within the block that it’s defined in. It can’t be used before it’s declared. This means that there’s no hoisting.

They can’t be reassigned and can’t be declared.

For example, we can declare a constant as follows:

const foo = 1;

We can’t access it before declaration, so:

console.log(foo);  
const foo = 1;

will get us the error “Uncaught ReferenceError: Cannot access ‘foo’ before initialization”

The scope can be global or local. However, when it’s global, it’s not a property of the window object, so it can’t be accessed from other scripts within the app.

It’s a read-only reference to a value. This means that it’s important to note that the object may still be mutable. This is something that’s often overlooked when we declare constants. The properties of objects declared with const can still be changed, and for arrays, we can add and remove entries from them.

For example, we can write:

const foo = {  
  a: 1  
};  
foo.a = 1;  
console.log(foo.a);

Then we get 1 from the console.log .

We can also add properties to an existing object declared with const :

const foo = {  
  a: 1  
};  
foo.b = 1;  
console.log(foo.b);

Again, we get 1 from the console.log .

Another example would be array manipulation. We can still manipulate arrays declared with const , just like any other object:

const arr = [1, 2, 3];  
arr[1] = 5;  
console.log(arr)

The console.log will be us [1, 5, 3] .

We can also add entries to it by writing:

const arr = [1, 2, 3];  
arr[5] = 5;  
console.log(arr)

then we get:

[1, 2, 3, empty × 2, 5]

As we can see, objects declared with const are still mutable. Therefore, if we want to prevent accidentally changing objects declared with const , we have to make them immutable.

Making Objects Immutable

We can make objects immutable with the Object.freeze method. It takes one argument, which is the object that we want to freeze. Freezing means that rhe properties and values can’t be changed.

Property descriptors of each property also can’t be changed. This means the enumerability, configurability, and writability also can’t be changed, in addition to the value.

A property being configurable means that the properties descriptors above can be changed and the property can be deleted.

Enumerability means that we can loop through the property with the for...in loop.

Writability means that the property value can be assigned.

Object.freeze prevents all that from happening by setting everything except enumerable to false .

For example, we can use it as follows:

const obj = {  
  prop: 1  
};Object.freeze(obj);  
obj.prop = 3;

In strict mode, the code above will get us an error. Otherwise, obj will stay unchanged after the last line.

Note that Object.freeze only freeze the properties at the top level of an object, so if we run:

/* 'use strict'; */  
const obj = {  
  prop: 1,  
  foo: {  
    a: 2  
  }  
};  
Object.freeze(obj);  
obj.foo.a = 3;

We’ll get:

{  
  "prop": 1,  
  "foo": {  
    "a": 3  
  }  
}

As we can see, obj.foo.a still changed after we called Object.freeze on it.

Therefore, to make the whole object immutable, we have to recursively call Object.freeze on every level.

We can define a simple recursive function to do this:

const deepFreeze = (obj) => {  
  for (let prop of Object.keys(obj)) {  
    if (typeof obj[prop] === 'object') {  
      Object.freeze(obj[prop]);  
      deepFreeze(obj[prop]);  
    }  
  }  
}

Then when we call deepFreeze instead of Object.freeze as follows:

const obj = {  
  prop: 1,  
  foo: {  
    a: 2  
  }  
};  
deepFreeze(obj);  
obj.foo.a = 3;

We get that that obj stays unchanged:

{  
  "prop": 1,  
  "foo": {  
    "a": 2  
  }  
}

The deepFreeze function just loops through all the own properties of obj and then call Object.freeze on it, if the value of the property is an object, then it calls deepFreeze on deeper levels of the object.

Primitive values are always immutable including strings, so we don’t have to worry about them.

Another good thing about freezing an object with Object.freeze is that there’s no way to unfreeze it since the configurability of the properties is set to false , so no more changes can be made to the structure of the object.

To make an object mutable again, we’ve to make a copy of it and assign it to another object.

Now that we know that const doesn’t actually make everything constant, we’ve to be careful when we use const . We can’t assign new values to an existing constant. However, we can still change the property values of an existing object assigned to a constant.

Constants declared with const aren’t actually constant. To make it actually constant, or immutable, we’ve to call Object.freeze on each level of the object declared with const to make sure it’s actually constant.

This only applies to object-valued properties. Primitives are immutable by definition so we don’t have to worry about them.

Categories
JavaScript JavaScript Basics

Great New Features in ES2018

Ever since ES2015 was released, which was a great leap forward in itself, JavaScript has been improving at a fast pace. Every year since then, new features have been added to JavaScript specifications.

Features like new syntax and new methods for building in JavaScript have been added consistently. In ES2016 and 2017, The Object object had methods like Object.values and Object.entries added.

String methods like padStart and padEnd were added in ES2017. async and await, which is a shorthand syntax for chaining promises, were also added in ES2017.

The includes methods for arrays were added in ES2016. ES2018 was another release with lots of new features. With ES2018, the spread syntax is now available for object literals. Rest parameters were also added.

The for await...of loop, which is a loop syntax iterating through promises sequentially, was also added. The SharedArrayBuffer object was added for representing raw binary data that cannot become detached.

A finally function was also added to the Promise object.


Spread Operator in Objects

The spread syntax works by copying the values of the original array, and then inserting them into another array or putting them in the order they appeared in the array as the list of arguments in a function in the same order.

When the spread operator is used with objects, the key-value pairs appear in the same order they appeared in the original object.

With ES2018, the spread operator works with object literals. Then key-value pairs of an object can be inserted into another object with the spread operator.

If there are two objects with the same key that the spread operator is applied to in the same object, the one that’s inserted later will overwrite the one that’s inserted earlier.

For example, if we have the following:

let obj1 = {foo: 'bar', a: 1};  
let obj2 = {foo: 'baz', b: 1};  
let obj3 = {...obj1, ...obj2 }

Then we get {foo: “baz”, a: 1, b: 1} as the value of obj3 because obj1 is spread before obj2. They both have foo as a key in the object.

First foo: 'bar' is inserted by the spread operator to obj3. Then foo: 'baz' overwrites the value of foo after obj2 is merged in since it has the same key, foo, but was inserted later.

This is great for merging objects as we don’t have to loop through the keys and put in the values, which is much more than one line of code.

One thing to note is that we can’t mix the spread operator between regular objects and iterable objects. For example, we will get TypeError if we write the following:

let obj = {foo: 'bar'};  
let array = [...obj];

Rest Operator

The rest operator is a JavaScript operator where we can store an indefinite number of arguments in a function as an array. It looks exactly like the spread operator, which lets us spread entries of an array or a list of key-value pairs of an object into another object.

For example, if we have a function that has a long list of arguments, we can use the rest operator to shorten the list of parameters in a function.

The rest operator is used with the following syntax:

const fn = (a,b,..restOfTheArgs) => {...}

Where restOfTheArgs is an array with the list of arguments other than the first two.

For example, if we want to write a sum function that takes an indefinite list of numbers as arguments and sum up the numbers, we can write:

const sum = (a,b,...otherNums) => {  
  return a + b + otherNums.reduce((x,y)=>x+y, 0);  
}

As we can see, this is very handy for functions that have any list of arguments. Before we had this, we had to use the arguments object available in functions to get a list of arguments. This is not ideal as we allow them to pass in anything into the arguments.

With the rest operator, we have the best of both worlds. We can have some fixed parameters, while the rest stay flexible. This makes functions more flexible than functions with a fixed number of parameters, while having some flexibility of functions that take an indefinite number of arguments.

The arguments object has all the arguments of the function. Also, it’s not a real array, so array functions aren’t available to them. It’s just an object with indexes and keys to denote the argument’s positions.

Methods like sort, map, forEach, or pop cannot be run on the argument’s object. It also has other properties. This creates confusion for programmers. The arguments that are converted to an array with the rest operator do not have these issues, as they are real arrays.

To call the sum function we wrote, we can write:

const result = sum(1,2,3,4,5,6,7,8,9,10);

The result will be 55, since we summed up all the arguments together. otherNums is an array with all the numbers other than 1 and 2.

We can also use the rest operator to destructure a list of arguments into a list of variables. This means that we can convert a list of parameters into an array with the spread operator, and then decompose the array of arguments into a list of variables.

This is very useful as we can get the entries of the array that’s operated on by the rest operator and convert them to named variables. For example, we can write:

const sum = (a,b,...[c,d,e])=> a+b+c+d+e;

This way, we can use the rest operator, but limit the number of arguments that your function accepts. We take the function parameters a and b, and we also take c, d, and e as parameters.

However, it’s probably clearer without using the rest operator since all the parameters are fixed, we can just list the parameters directly.


for await...of

The for await...of loop allows us to create a loop that iterates over a list of promises as well as over the entries of a normal iterables.

It works with iterable objects like arrays, string, argument object, NodeList object, TypedArray, Map, Set, and user-defined synchronous and asynchronous iterable objects.

To use it, we write:

for await (let variable of iterable) {  
  // run code on variable
}

The variable may be declared with const, let, or var, and the iterable is the iterable objects that we are iterating over. We can iterate over asynchronous iterable objects like the following:

const asynNums = {  
  [Symbol.asyncIterator]() {  
    return {  
      i: 6,  
      next() {  
        if (this.i < 20) {  
          return Promise.resolve({  
            value: this.i++,  
            done: false  
          });  
        }
        return Promise.resolve({  
          done: true  
        });  
      }  
    };  
  }  
};

(async () => {  
  for await (let num of asynNums) {  
    console.log(num);  
  }  
})();

We should see 6 to 19 logged. It also works with async generators:

async function* asynNumGenerator() {  
  var i = 6;  
  while (i < 20) {  
    yield i++;  
  }  
}

(async () => {  
  for await (let num of asynNumGenerator()) {  
    console.log(num);  
  }  
})();

We should see the same thing logged. It also works great with promises:

const arr = Array.from({  
  length: 20  
}, (v, i) => i)let promises = [];

for (let num of arr) {  
  const promise = new Promise((resolve, reject) => {  
    setTimeout(() => {  
      resolve(num);  
    }, 100)  
  })  
  promises.push(promise);  
}

(async () => {  
  for await (let num of promises) {  
    console.log(num);  
  }  
})();

As we can see, if we run the code above, we should see 0 to 19 logged sequentially, which means that promises were iterated through in sequence. This is very handy as we never have anything that can iterate through asynchronous code before this loop syntax was introduced.


Promise.finally()

The finally function is added to the Promise object which runs when the promise is settled. That means it runs whenever the promise is fulfilled or rejected.

It takes a callback function that runs whenever the promise is settled. This lets us run code regardless of how a promise ends. Also, this means that we no longer have to duplicate code in the callbacks for then and catch functions.

To use the finally function, we use it as the following:

promise  
.finally(()=>{  
  // run code that when promise is settled  
})

The finally is useful is we want to run code whenever a promise ends like cleanup code or processing after the code ends.

It’s very similar to calling .then(onFinally, onFinally). However, we do not have to declare it twice to make a variable for it and pass it in twice. The callback for the finally function doesn’t take any argument, as there’s no way to determine the status of the promise beforehand.

It’s used for cases when we don’t know what will happen to the promise, so there’s no need to provide any argument. If a promise is fulfilled then the resolved value will be intact after calling the finally function.

For example, if we have:

Promise.resolve(3).then(() => {}, () => {})

This will resolve to undefined, but if we have:

Promise.resolve(3).finally(() => {})

The code above will resolve to 3. Similarly, if the promise if rejected, we get:

Promise.reject(5).finally(() => {})

This will be rejected with the value 5.


SharedArrayBuffer

The SharedArrayBuffer is an object that’s used to represent a fixed-length raw binary data buffer.

It’s similar to the ArrayBuffer object, but we can use it to create views on shared memory. It can’t become detached, unlike an ArrayBuffer. The constructor of it takes the length of the buffer as an argument.

The length is the size in byte for the array buffer to create. It returns a SharedArrayBuffer of the specified size with contents initialized to 0.

Shared memory can be created and updated by multiple workers on the main thread. However, this may take a while until the changes are propagated to all contexts.


Conclusion

With ES2018, we have more handy features to help developers develop JavaScript apps.

The highlights include the spread operator for objects, which let us copy key-value pairs to other objects and the rest operator to let us pass optional arguments.

The for await...of loop lets us iterate through collections of asynchronous code which could never easily be done before.

Categories
JavaScript JavaScript Basics

How to Search and Replace Strings in JavaScript

Search

There are a few ways to search for strings in JavaScript. Strings has the startsWith , endsWith , indexOf , lastIndexOf , charAt , search and includes , and match functions.

startsWith

startsWith checks if a string starts with the substring you pass in.

For example:

const str = "Hello world.";  
const hasHello = str.startsWith("Hello"); // trueconst str2 = "Hello world.";  
const hasHello2 = str.startsWith("abc"); // false

endsWith

endsWith checks if a string ends with the substring you pass in.

For example:

const str = "Hello world.";  
const hasHello = str.endsWith("world."); // trueconst str2 = "Hello world.";  
const hasHello2 = str.endsWith("abc"); // false

indexOf

indexOf finds the index of the first occurrence of the substring. Returns -1 if not found.

For example:

const str = "Hello Hello.";  
const hasHello = str.indexOf("Hello"); // 0  
const hasHello2 = str.indexOf("abc"); // -1

lastIndexOf

lastIndexOf finds the index of the last occurrence of the substring. Returns -1 if not found.

For example:

const str = "Hello Hello.";  
const hasHello = str.lastIndexOf("Hello"); // 6  
const hasHello2 = str.lastIndexOf("abc"); // -1

charAt

charAt returns the character located at the index of the string.

For example:

const str = "Hello";  
const res = str.charAt(0); // 'H'

search

search get the position of the substring passed into the function. It returns -1 if the substring is not found in the string.

Example:

const str = "Hello";  
const res = str.search('H'); // 0

includes

includes checks if the substring passed in is in the string. Returns true if it is in the string, false otherwise.

const str = "Hello";  
const hasH = str.includes('H'); // true  
const hasW = str.includes('W'); // false

Replace

The replace() function included with strings are useful for replacing parts of strings with another. It returns a new string with the string after substring is replace.

Example:

const str = "Hello Bob";  
const res = str.replace("Bob", "James"); // 'Hello James'

replace() can also be used to replace all occurrences of a substring, for example:

const str = "There are 2 chickens in fridge. I will eat chickens today.";  
const res = str.replace(/chickens/g, "ducks"); // "There are 2 chickens in fridge. I will eat chickens today."

If the first argument is a regular expression which searches globally, then it will replace all occurrences of the substring.

Categories
JavaScript JavaScript Basics

Why Should We Use the JavaScript For-Of Loop?

Since ES6 we have the for...of loop. It’s a very useful loop that can do a lot more than regular for loops. In this article, we’ll look at why the for...of loop is useful for developers and whether the performance is good enough to replace the for loop.

The for...of loop lets us loop through arrays, array-like objects, and user-defined iterables. It lets us loop through all of them without using different kinds of methods and loops.

For example, we can create a for...of loop to loop through arrays as follows:

const arr = [1, 2, 3];

for (const a of arr) {  
  console.log(a);  
}

The loop will produce the following output:

1  
2  
3

Where the for...of loop shines is the ability to iterate through array-like objects like NodeLists and our own iterable objects. For example, if we have the following HTML:

<p>  
  foo  
</p>  
<p>  
  bar  
</p>  
<p>  
  baz  
</p>

then we can use the following JavaScript

const nodeList = document.querySelectorAll('p');

for (const element of nodeList) {  
  console.log(element.textContent.trim());  
}

Then we get the following logged:

foo  
bar  
baz

With the for...of loop we don’t have to write out the indexes like with a regular for loop to iterate through NodeList, which is the only alternative since it’s not an array, but an array-like object.

Also, we can iterate user-defined iterable objects like generators and iterable objects with it. For example, given the following generator:

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

We can use the for...of loop to loop through it as follows:

for (let a of generator()) {  
  console.log(a);  
}

Then we get:

1  
2  
3

Furthermore, we can define our own iterable object. As long as it has the Symbol.iterator method, we can loop through it with the for...of loop. Given that we have:

const iterableObj = {  
  *[Symbol.iterator]() {  
    yield 1;  
    yield 2;  
    yield 3;  
  }  
}

We can loop through it with:

for (let a of iterableObj) {  
  console.log(a);  
}

The for...of also has no problem looping through new objects like Set s and Map s:

const set = new Set([1, 1, 2, 2, 2, 3, 5, 4, 3]);

for (const value of set) {  
  console.log(value);  
}

Then we get:

1  
2  
3  
5  
4

Likewise, we can loop through maps:

const map = new Map([  
  ['a', 1],  
  ['b', 2],  
  ['c', 3]  
]);

for (const value of map) {  
  console.log(value);  
}

Also, we can loop through strings with it. For instance, given that we have the following string:

const str = 'foo';

We can loop through each character as follows:

for (let char of str) {  
  console.log(char);  
}

Then we get:

f  
o  
o

As we can see, it’s very useful for looping through anything that can be looped through.

Performance

We can test this by looping through a number array with 10000 entries. The array is constructed as follows:

let arr = [];  
for (let i = 1; i <= 10000; i++) {  
  arr.push(i);  
}

To test the performance of each loop, we use the console.time and console.timeEnd methods available in most modern browsers.

With the forEach method of an array, we get that it takes 840.8779296875ms to loop through each entry with the forEach method of an array.

With the for...of loop, it takes 843.972900390625ms to loop through each entry of the same array.

Finally, with a plain for loop, it takes 841.210205078125ms seconds to loop through 10000 numbers.

With length caching as we do below:

const len = arr.length;  
for (let i = 0; i < len; i++) {  
  console.log(arr[i]);  
}

It’s even faster at 593.16796875ms.

As we can see, the plain for loop is much faster. However, for looping through small amounts of data, we can live with the slightly slower performance. Also, we don’t have to write any extra code to loop through other kinds of iterable objects like Map s and Set s.

This means that for non-array iterable objects, the for...of loop is still better for looping through these kinds of things. Less code means less processing time is needed.

However, for arrays, if we loop through an array with lots of entries, then the plain old for loop is probably a better choice.

Should we use the for…of loop?

From what we looked at, the for...of loop is good for anything other than large arrays. It can loop through many more kinds of objects than other loops or the array’s forEach method out of the box.

It’s designed to be more flexible and clean than other loops.

Also, we don’t have to worry about what we loop through as long as they’re iterable, which isn’t the case with other loops.

The only thing that it’s not good at is looping through large arrays. In that case, use the for loop with the array length cached.