Categories
JavaScript Best Practices

JavaScript Best Practices — Classes and Modules

JavaScript is a very forgiving language. It’s easy to write code that runs but has mistakes in it.

In this article, we’ll look at what should be in our class methods, and also the best ways to use modules.

Class Methods Should Either Reference this Or Be Made Into a Static Method

A JavaScript class can have static or instance methods. If it’s an instance method , then it should reference this . Otherwise, it should be a static method.

For instance, we should write a class that’s like the following code:

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

  static greet() {
    return 'hi'
  }

  greetWithName() {
    return `hi ${this.name}`;
  }
}

In the code above, we have the static greet method which doesn’t reference this and the greetWithName method, which references this.name .

Static methods are shared with all instances of the class and instance methods are part of the instance, so it makes sense it references this .

Use ES6 Modules Import and Export Over Non-Standard Module Systems

With ES6, modules are a standard feature of JavaScript. We can divide our code into modules and only export code that we want to expose to the outside.

Also, we can selectively import members from another module.

Before ES6, there’re various module systems like RequireJS and CommonJS. They’re similar to what ES6 modules do today. However, ES6 modules can do what they do and they’re a JavaScript standard.

Therefore, we should just use ES6 modules over other kinds of modules so that we won’t have to worry about those kinds of modules going away or compatibility issues with other kinds of module systems.

For instance, instead of writing the following code to export and import modules:

module.js

module.exports = {
  foo: 1
};

index.js

const { foo } = require("./module");
console.log(foo);

We write:

module.js

export const foo = 1;

index.js

import { foo } from "./module";
console.log(foo);

In the first example, we used module.exports to export a member as a property of an object.

Then we imported the foo property with the require function. This is the old way with CommonJS module which is used before JavaScript has modules as a standard feature.

The second example does the same thing using standard JavaScript modules. We export the member foo in module.js and then import foo inindex.js .

JavaScript modules have been a standard for a long time and it’s supported in both browsers and Node.js natively since version 12, we can use regular JavaScript modules anywhere.

If not, we can use transpilers like Browserify, Webpack, or Parcel to transform module code into ES5 code.

Do Not Use Wildcard Imports

Wildcard imports import everything. It’s denoted by an asterisk symbol.

For instance, we do a wildcard import by writing the following code:

module.js

export const foo = 1;

index.js

import * as module from "./module";
console.log(module.foo);

In the code above, we imported the whole module.js module by using the * and the as keyword to name the module that we imported so that we can reference it in the line below.

This isn’t good because we usually don’t need all the members of a module, so it’s not efficient to import everything.

Instead, we should import only the members that we need. We should write:

import { foo } from "./module";
console.log(foo);

to import just the foo member which we need.

Photo by chuttersnap on Unsplash

Do Not Export Directly From an Import

We shouldn’t export directly from an import. This means that we shouldn’t use the as keyword to rename the export directly within the export to something else.

It’s clearer to separate them from their own line. For instance, we can write the following code to do that:

module.js

export const foo = 1;

bar.js

export { foo as bar } from "./module";

index.js

import { bar } from "./bar";
console.log(bar);

In the code above, we exported the foo member from module.js as bar by using the export statement.

We shouldn’t do that since it’s not clear that we’re both importing from module.js and exporting the member as bar at the same time.

Instead, we should write the following in bar.js :

import { foo } from "./module";
export { foo as bar } from "./module";

This way, it’s clear that we’re both importing and exporting in one module.

Conclusion

Class methods should reference this if it’s an instance method or be static if they don’t.

We should use ES6 modules since it’s a JavaScript standard. Also, we should separate imports and exports and shouldn’t import everything from a module.

Categories
JavaScript Best Practices

JavaScript Best Practices — Arrows, Const, and Duplicate Members

JavaScript is a very forgiving language. It’s easy to write code that runs but has mistakes in it.

In this article, we’ll look at confusing arrows, assigning to const constants, and duplicate members.

Arrow Functions That Can Be Confused with Comparisons

Arrow functions have the => fat arrow which may be confused with inequality comparison operators like <= or => for people that may not be completely familiar with JavaScript.

Therefore, we may want to make our code easier to understand for them by not using arrow functions that look like comparison expressions.

For instance, the following function may be confusing for some people:

const foo = a => 1;

We have the foo function that has a parameter a and returns 1.

However, some people may confuse this with:

const foo = a >= 1;

or:

const foo = a <= 1;

which compares if a if bigger than or equal to 1 or if a is less than or equal to 1 respectively.

Therefore, we may want to make our arrow function less confusing by wrapping the function body with curly braces or wrapping the function signature with parentheses.

For instance, we can rewrite the the foo function in the following way:

const foo = a => {
  return 1
};

The code above makes our function clear by indicating that we want to return the value 1.

We can also rewrite it as follows:

const foo = (a) => 1;

The parentheses make our code’s reader clear than a is a parameter and it’s not a variable that we want to compare with 1.

No Modifying Variables That are Declared Using const

In JavaScript, constants that are declared with const can’t be reassigned to a new value.

If we write something like the following code, then we’ll get an error:

const a = 1;
a = 2;

When we run the code above, we’ll get the error ‘Uncaught TypeError: Assignment to constant variable.’ in the console and the code will stop running.

Therefore, we should be mindful of not doing that. If we want a to be able to be reassigned to a different value, then we should declare it with let instead.

For instance, we instead write the following:

let a = 1;
a = 2;

This way, a is declared as a variable instead of a constant and thus it can be reassigned to a new value.

Other operators that do assignment operation like += , -= , *= , /= , and %= also won’t work with const constants.

For instance, we’ll get the same error if we write the following:

const a = 1;
a += 2;

Loop variables that are declared with const also can’t be reassigned to a different value. For instance, we’ll get an error if we write:

for (const a in [1, 2, 3]) {
  a = 1;
}

In the code above, we tried to reassign a to 1, which also won’t work.

Duplicate Member Name in Classes

We don’t want duplicate member names in classes. This is because it’s confusing which one is actually the one that’s kept.

For instance, we shouldn’t be writing code like this:

class Foo {
  bar() {
    console.log("foo");
  }

  bar() {
    console.log("bar");
  }
}

In the code above, we have 2 bar instance methods. The 2nd would be kept, so the first one is useless.

Therefore, when we call the bar method as follows:

const foo = new Foo();
foo.bar();

We’ll see 'bar' logged in the console log output.

Therefore, we should just keep the one we want to keep or rename one of them if we need both.

We can write something like the following:

class Foo {
  foo() {
    console.log("foo");
  }

  bar() {
    console.log("bar");
  }
}

Then we can call both instance methods and see the logged value of both in the console.

Conclusion

We may want to rewrite arrow functions that may be confused with comparison expressions.

To do that, we can put our function signature in parentheses or add curly braces to the function body.

We shouldn’t reassign const constants to another value. That’s why it’s a constant.

Also, we shouldn’t have multiple members with the same name in a class. That’s just useless and confusing since the one that’s defined later just overwrites the one we have above.

Categories
JavaScript Best Practices

JavaScript Best Practices — Generator Functions and Class Assignment

JavaScript is a very forgiving language. It’s easy to write code that runs but has mistakes in it.

In this article, we’ll look at spacing around generator functions and assigning classes to other values.

Spacing Around the * in Generator Functions

Spacing around generator functions should be consistent. We usually define generator functions with the following spacing.

For instance, we can define one as follows:

function* foo() {
  yield 1;
}

A generator function is defined by the function* keyword. It denotes that the function is a generator function.

Then the rest of the first line has the same parts as for any other traditional function.

After the function* keyword, we have foo , which is the function name, then the parentheses, and then one space character, and then the opening curly brace.

The generation function returns a generator when it’s called, which we can use the spread operator or the for...of loop on.

For instance, we can use it as follows:

const arr = [...foo()];

for (const f of foo()) {
  console.log(f);
}

In the first line, we used the spread operator to spread the generated returned by foo into an array, so we get 1 .

In the loop, we looped through the entries returned by the generator returned by calling foo and then log the value in the loop.

The yield keyword returns the item that’s returned from the generator.

We can also define a generator within an object. We can do this 2 ways. The first is using same function* keyword as follows:

const obj = {
  foo: function*() {
    yield 1;
  }
}

In the code above, we have the obj object that has the foo property with the function* keyword to indicate that the function is a generator function.

The spacing is the same as in the standalone generator example we have previously except that our function doesn’t have a name.

We can also shorten this by replacing the function* keyword with * as follows:

const obj = {
  * foo() {
    yield 1;
  }
}

The code above is the short version of the previous example. The * symbol is separated by a space character from the property name foo .

With both, we can call it as follows:

const arr = [...obj.foo()];

for (const f of obj.foo()) {
  console.log(f);
}

And we get the same result as the other examples.

We can also define generator methods inside a class. For instance, we can define it as follows:

class Foo {
  * foo() {
    yield 1;
  }
}

In the code above, we have the foo generator function. The syntax of the function definition is the same as the object shorthand version that we have in the previous example.

Then we can call it as follows:

const arr = [...new Foo().foo()];

for (const f of new Foo().foo()) {
  console.log(f);
}

We can also define a static generator method in the Foo class instead of an instance method as follows:

class Foo {
  static * foo() {
    yield 1;
  }
}

In the code above, we have one before and after the * symbol.

Then we can use it as follows:

const arr = [...Foo.foo()];

for (const f of Foo.foo()) {
  console.log(f);
}

The spacing for the * is standard so we can just follow that to make our generator functions and methods readable.

Photo by Inspired Horizons Digital Marketing on Unsplash

Don’t Modify Variables of Class Declarations

In JavaScript, a class is nothing special. It’s just syntactic sugar for constructor functions.

Therefore, like any other function, it’s just a regular object. The class name is a regular variable name that can be assigned anything else.

It’s better that we don’t assign our class name to something else even though we can. This way, we keep our code easy to understand by nothing writing confusing code like assigning class names to another value, including classes.

For instance, we shouldn’t write code like the following:

class A { }
A = 0;

If we did that, then A would be 0 because we reassigned it to 0 on the 2nd line.

Instead, if we want to assign 0 to something, assign it to another variable. For example, we can write:

class A {}
const b = 0;

Conclusion

Generator functions can be defined in many ways. We should keep the spacing consistent and follow conventions to make them easy to read.

In JavaScript, classes are just functions, which are just objects assigned to a variable name. Therefore, we can assign the class name to another value, just like another variable. We should avoid doing that to reduce confusion in our code.

Categories
JavaScript Best Practices

JavaScript Best Practices for Writing More Robust Code — Removing Duplicates and Merging Arrays

JavaScript is an easy to learn programming language. It’s easy to write programs that run and does something. However, it’s hard to account for all the uses cases and write robust JavaScript code.

In this article, we’ll look at how to remove duplicate items from an array in a reliable way.

Sets

We can use the JavaScript Set constructor to create sets, which are objects that can’t have duplicate items included.

Duplicate items are determined in a similar way to the === operator, but we -0 and +0 are considered to be different values.

NaN is also considered to the be same as itself for the purpose of determining duplicate items for Set s.

We can create a set from an array as follows:

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

Then we have Set instance that has 1, 2 and 3 as the value of set .

Since a Set is an iterable object, we can use the spread operator to convert it back to an array as follows:

const noDup = [...set];

As we can see, it’s very easy to convert a Set back to an array.

Since the algorithm for determining duplicates is determined in a similar way to the === operator, it works well for removing duplicate primitive values.

However, it doesn’t work well for objects unless they reference the same item in memory.

If we have objects, then the most reliable way to remove duplicates is to convert them to strings and then parse them back to objects.

For instance, if we have the following array:

const arr = [{
  a: 1
}, {
  a: 1
}];

Then we can write the following code to map the array to a string, turn it to a Set , then we can parse the remaining items back to objects as follows:

const set = new Set(arr.map(a => JSON.stringify(a)));
const noDup = [...set].map(a => JSON.parse(a));

In the code above, we have a Set , which is created from array entries that are stringified with JSON.stringify .

Then we use the spread operator to spread the set back to an array and then map the stringified entries back to objects with JSON.parse .

This works well for plain objects that have no methods in them.

If our objects have methods, then we should make sure that each entry reference the same object.

Set s also have methods to make traversing them easier. There’s the entries method to get all the entries as an iterator that returned each entry as an array with the [key, value] structure.

forEach takes a callback to loop through them. The keys and values methods let us get the keys and values respectively.

The clear method removes all items from a set.

It also has the size property to get the size of the Set .

Using the Spread Operator

The spread operator is one of the most useful features that are added recently to JavaScript.

When it’s used with arrays, it can let us make copies of arrays or merge them without calling any methods. This makes our code short and easy to read.

We just put everything in an array with the spread operator and then we get a new array with new items.

Also, it lets us combine items from different kinds of iterable objects into one array.

For instance, we can use the spread operator with multiple arrays as follows:

const arr1 = [1, 2, 3];
const arr2 = [4, 5];
const arr = [...arr1, ...arr2];

Then we get that the value of arr is [1, 2, 3, 4, 5] .

As we can see, the items are added in order into the new array with the spread operator.

Since the spread operator works with different kinds of iterable object, we can spread Map s and Set s into arrays as well:

const arr1 = [1, 2, 3];
const set = new Set([4, 5]);
const map = new Map();
map.set('a', 1);
map.set('b', 2);
const arr = [...arr1, ...set, ...map];

Then we get that arr is:

[
  1,
  2,
  3,
  4,
  5,
  [
    "a",
    1
  ],
  [
    "b",
    2
  ]
]

The Map s are converted to an array with entries that are an array of key and value .

Conclusion

Set s are useful for removing duplicate items from arrays. They can also be converted or merged into arrays.

We can also merge multiple kinds of iterable objects into an array with the spread operator.

Categories
JavaScript Best Practices

JavaScript Best Practices — Arrays

JavaScript is a very forgiving language. It’s easy to write code that runs but has mistakes in it.

In this article, we’ll look at the best ways to work with JavaScript arrays

Don’t Use the Array Constructor Most of The Time

If we’re just defining an array with some entries in it, then we shouldn’t use the Array constructor to do it.

Using the Array constructor is longer, and there are also 2 versions of the constructor.

If we call the Array constructor with one argument, then it’ll create an array with the number of empty slots that are given by the number that we passed in.

If we call it with multiple arguments, then we get an array that has the entries that we passed into the arguments with it.

Also, it can be called with or without the new operator.

Therefore, we should just use an array literal to define an array whenever possible.

For instance, instead of writing:

const arr = new Array(1, 2, 3);

We should write:

const arr = [1, 2, 3];

As we can see, the 2nd example is much shorter and does the same thing.

The only exception for using the Array constructor is to fill an array with the same entries.

We can do that with the Array constructor as follows:

const arr = Array(5).fill(1);

Array(5) returns an array with 5 empty slots, and fill(1) fill those slots with 1’s.

Use Array.prototype.push Instead of Direct Assignment to Add Items to An Array

The array’s push instance method always adds an item to the end of the array.

This is much shorter than assigning the item to the last entry of the array with the bracket notation.

Therefore, instead of writing:

const arr = [1, 2];
arr[arr.length] = 3;

We should write:

const arr = [1, 2];
arr.push(3);

As we can see, it’s shorter and easier to use than using the bracket notation since we don’t have to think about array indexes at all when we call push .

Use the Spread Operator to Copy Arrays

The spread operator lets us copy arrays without using loops. It’s good for making a shallow copy of an array.

For instance, instead of using a loop to copy the array’s entries into a new one as follows:

const arr = [1, 2, 3];
const copy = [];
for (const a of arr) {
  copy.push(a);
}

We can write that all in one line as follows:

const arr = [1, 2, 3];
const copy = [...arr]

As we can see, we didn’t need a loop to make a shallow copy of an array. All we have to do is to use the spread operator.

Rather than using a loop to make a copy, we just use the ... operator to do the same thing in a much shorter way.

Use the Spread Operator to Convert Iterable Objects to Arrays Instead of Calling Array.from

The Array.from is good for converting iterable objects into an array. For instance, we can write the following code to convert a NodeList into an iterable object with it:

const ps = document.querySelectorAll('p');
const arr = Array.from(ps);

In the code above, we get all the p elements on a page with the querySelectorAll method and then convert the returned NodeList object to an array with Array.from .

However, we can do that in a shorter way with the spread operator. We can rewrite our code as follows:

const ps = document.querySelectorAll('p');
const arr = [...ps];

It does the same thing, just we type less to do it.

The only case that Array.from is good for is converting non-iterable array-like objects, which are objects with numerical keys and the length property with an integer as its value.

For instance, we can convert them to an array by calling Array.from as follows:

const obj = {
  0: 'a',
  1: 'b',
  length: 2
}
const arr = Array.from(obj);

In the code above, we passed in the obj object with the keys 0 and 1 and the length property with the value 2. Then when we pass it into Array.from , we have the following array returned:

["a", "b"]

Conclusion

We should use array literals to define arrays instead of using the Array constructor to define arrays most of the time.

The spread operator is useful for copying arrays and converting iterable objects to arrays.