Categories
JavaScript Best Practices

JavaScript Best Practices — Source Files and Modules

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 name our files and proper encoding for our files.

Also, we look at the right way to deal with modules.

File Names

File names should be all lower case and may include underscores or dashes.

We shouldn’t put any additional punctuation in our file names.

The file names should end in .js so that we know that it’s a JavaScript file.

For instance, foo.js is a good file name.

File Encoding

The encoding of the file should be UTF-8. This is standard across all platforms so that it won’t cause issues when we run them anywhere.

White Space Characters

The ASCII horizontal whitespace character should be the only one in source files.

Other whitespace characters should be escaped.

Tabs shouldn’t be used for indentation since they may be interpreted differently in different platforms.

However, tabs can be converted to spaces automatically.

Special Escape Sequences

Special escape sequences should have a corresponding numeric escape.

So \' is the same as \x27 , for example.

Non-ASCII Characters

We should use Unicode equivalent for non-ASCII characters in our code files.

Source File Structure

We may want to enforce some structure in our source files.

For instance, we may have JSDoc comments for documentation.

We may put them in folders so that there’s a clear hierarchy in our project structure.

ES Modules

Modules are used in most new JavaScript projects now.

Therefore, we should be mindful of some rules regarding modules.

Import Length

Length of import statements should be 100 characters or less so they won’t overflow the screen.

This way, no one has to scroll horizontally to read the whole line.

Import Paths

We should use the import sattement to import ES modules.

For instance, we can write:

import './foo';

or:

import * as foo from './foo.js';

or:

import { name } from './foo.js';

We don’t need the extension when importing files.

Importing the Same File Multiple Times

We shouldn’t import multiple members of the same file in different import statements.

For instance, instead of writing:

import { bar } from './foo';
import { baz } from './foo';

We write:

import { bar, baz } from './foo';

Naming Imports

We can name imports with the as keyword.

The name should be camelCase.

For instance, we write:

import * as fooBar from './fooBar';

Naming Default Imports

We can name default imports with camelCase also.

For instance, we can write:

import fooBar from './fooBar';

Naming Named Imports

Also, we can change the name of named imports so that we can use the name that we want to use.

For instance, we can write:

import { Cat as FatCat } from './animal';

for constructor imports or:

import { cat as fatCat } from './animal';

for other imports.

Named vs Default Exports

We can have named and default exports just like imports.

For instance, we can create a default export by writing:

export class Foo { ... }

and a named export by writing:

export { Foo }

Export Container Classes and Objects

We shouldn’t export container classes and objects

Therefore, instead of writing the following:

export class Container {
  static foo() {
    return 1;
  }
}

We write:

export function foo() {
  return 1;
}

export const FOO = 1;

Mutability of Exports

Don’t export variables that are mutable.

This means anything declared with let shouldn’t be exported.

For instance, instead of writing:

export let foo = 1;

We write:

export const foo = 1;

export from Statements

We should wrap export from statements so that it stays within 100 characters per line.

So we write:

export * from './foo';

or:

export { bar } from './another.js';

Circular Dependencies in ES modules

We shouldn’t create circular dependencies between ES6 modules, directly or indirectly.

For instance, we shouldn’t write:

b.js :

import './a';

and:

a.js

import './b';

Conclusion

We should be careful when working with file names and modules.

File names should be named in lowercase with underscore or dashes,

We should use modules and they shouldn’t have circular dependencies.

Code files should be UTF-8 encoded to avoid issues running them.

Categories
JavaScript Best Practices

JavaScript Best Practices — Modules, Arrays, and Objects

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 the best ways to work with modules, arrays, and objects.

If we have a Single Export, then we Should Use Default Export

If we have only one export from our module, then we should use a default export instead of named export.

It’s more readable and maintainable.

For instance, instead of writing:

export const foo = () => {};

We write:

const foo = () => {}
export default foo;

Put all imports Above Non-Import Statements

Putting all imports above non-import statements make them easier to read.

For example, we should write:

import { foo } from "./foo";

foo();

Multiline Imports Should be Indented Like Multiline Arrays and Object Literals

If we have lots of imports, we shouldn’t write:

import {foo, bar, baz, qux, longName} from 'foo';

Instead, we should write:

import {
  foo,
  bar,
  baz,
  qux,
  longName
} from "foo";

Don’t Use Webpack Loader Syntax in import Statements

If we use Webpack specific syntax for import things, then we’ll have issues when we try to switch to another module loader.

Instead, we should use standard syntax to avoid issues with Webpack specific syntax.

Instead of writing:

import barSass from 'css!sass!bar.scss';

We write:

import barCss from 'bar.css';

Don’t Add JavaScript File Extension to Imports

We don’t need to add the JavaScript file extension to imports.

It also creates problems when we change the extension,

Therefore, we shouldn’t include it.

For instance, we can write:

import { bar } from './foo.js';

Instead, we write:

import { bar } from './foo';

Iterators and Generators

Iterators and generators are JavaScript features new to ES6 that lets us return items sequentially.

Don’t Use Iterators to Manipulate Arrays

Instead of using loops, we should use array methods to manipulate arrays.

For instance, instead of writing:

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

We write:

const arr = [1, 2, 3];
const result = arr.map(a => a ** a);

It’s shorter and no loops.

Another reason is that we don’t want to mutate array entries.

For instance, if we write:

let arr = [1, 2, 3];
for (let i = 0; i < arr.length; i++) {
  arr[i] = arr[i] ** arr[i];
}

Then we mutate each entry of arr as we loop through it.

If we can avoid mutation, then we should do that.

The following are some array methods that we can use to manipulate arrays:

  • map — map entries from one to another and return an array with the mapped entries
  • every — checks if each array entry meets some condition
  • find — returns the first entry of something that meets some condition
  • findIndex — returns the index of the first entry of something that meets some condition
  • reduce — combine array entries into a single value and return it
  • some — check if some array entries meet a given condition

We can also get object keys and values with these methods:

  • Object.keys — gets own string keys of objects
  • Object.values — gets own values of objects
  • Object.entries — gets own key-value pairs of objects

Don’t use Generators

If we’re targeting ES5 in our builds, then we shouldn’t use generators since they don’t transpile well.

Space Generators Definitions Properly

We should space the function definition as follows:

function* foo() {
  // ...
}

Object Properties

There’re a few things to be aware of if we access object properties.

Use Dot Notation When Accessing Properties

Dot notation should be used when we access object properties that are valid JavaScript identifers.

For instance, instead of writing:

const bar = foo['bar'];

We write:

const bar = foo.bar;

Conclusion

We should have a default export if we only export one member of a module.

Instead of using loops, we should use array methods to manipulate arrays.

If we need to manipulate object keys and values, we can get them with some Object static methods.

Also, use the dot notation as much as possible for accessing object properties.

Categories
JavaScript Best Practices

JavaScript Best Practices — Spaces

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 properly space out the JavaScript code.

Use Indentation for Making Long Method Chains

If we have method chains, then we should indent the method calls so that we see the chain more easily.

For example, instead of writing:

$('#foo').find('.selected').highlight().end().find('.bar').count;

We instead write:

$('#foo')
  .find('.selected')
  .highlight()
  .end()
  .find('.bar')
  .count;

Leave a Blank Line After Blocks and Before the Next Statements

If we have blocks followed by other statements, then we should follow that with an empty line so that we have the next statement separated from the block.

For instance, instead of writing:

if (foo) {
  return foo;
}
return baz;

We write:

if (foo) {
  return foo;
}

return baz;

It’s much clearer to separate them with an empty line.

Don’t Pad Blocks with Blank Lines

We shouldn’t pad blocks with empty lines.

This is because they don’t help with readability and takes up space.

For instance, instead of writing:

function foo() {

  console.log('bar');

}

We write:

function bar() {
  console.log(foo);
}

Don’t Use Multiple Blank Lines

We shouldn’t use multiple blank lines since they take up space and don’t help with improving readability.

For example, instead of writing:

const fullName = 'joe';


const email = 'joe@joe.com';

We write:

const fullName = 'joe';
const email = 'joe@joe.com';

Don’t Add Spaces Inside Parentheses

Spaces inside parentheses aren’t needed.

For instance, instead of writing:

function baz( foo ) {
  return foo;
}

We write:

function baz(foo) {
  return foo;
}

Don’t Add Spaces Inside Brackets

Likewise, we shouldn’t add spaces inside parentheses.

For instance, instead of writing:

const foo = [ 1, 2, 3 ];

We write:

const foo = [1, 2, 3];

If we access array entries or object properties, instead of writing:

foo[ 1 ]

We write:

foo[1]

And instead of writing:

bar[ 'baz' ]

We write:

bar['baz']

Add Spaces Inside Curly Braces

We should have spaces inside curly braces to improve readability.

For instance, instead of writing:

const foo = {baz: 1};

We should write:

const foo = { baz: 1 };

With the extra spaces, the code is much easier to read.

Avoid Lines of Code that are Longer Than 100 Characters

Lines of code that are longer than 100 characters may overflow people’s screens.

If that happens, they need to scroll horizontally to read the whole line.

To avoid that, we should our lines of code to 100 characters or less.

Consistent Spacing Inside an Open Block and Next Token on the Same Line

We should have consistent spacing between the open block and the next token.

For instance, instead of writing:

function foo() {return 'bar';}

We should write:

function foo() {
  return 'bar';
}

The extra spaces make our function easier to read.

Avoid Spaces Before Comma and Require a Space After Commas

We should have spaces before a comma and have one space after a comma.

For instance, instead of writing:

const arr = [1 , 2];

We should write:

const arr = [1, 2];

It’s much more readable.

Enforce Spacing Inside a Computed Property Bracket

Spacing should be enforced inside a computed property bracket.

For instance, instead of writing:

obj[foo ]

We write:

obj[foo]

No Spaces Between Function and its Invocation

We don’t need a space between a function and its invocation.

For example, instead of writing:

foo ();

or:

foo
()

We write:

foo();

Having Spacing Between Keys and Values in Object Literal Properties

We should have some spaces between keys and values in object literal properties.

For example, instead of writing:

const obj = {foo:1};

We should write:

const obj = {
  foo: 1
};

No Trailing Spaces at the End of Lines

A trailing space at the end of a line is useless.

Therefore, we should remove them if they are there.

We should also configure our text editor to remove them automatically.

Conclusion

We should have spacing in places where they’re needed like in keys-value pairs of objects.

However, we shouldn’t have spaces where they are needed like the end of lines, extra blank lines, and other things like that.

Categories
JavaScript Best Practices

JavaScript Best Practices — Classes and Modules

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 the best ways to work with classes and modules.

Add a toString Method

We can add a toString method to our class if we want.

However, we should make sure that it doesn’t commit any side effects.

For instance, we can write:

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

  toString() {
    return `Person - ${this.getName()}`;
  }
}

We have a toString method to return the string representation of our class instance.

We can Skip the constructor Method if we Don’t Want to Put Anything in it

We don’t need to add an explicit constructor method to a class.

For instance, instead of writing:

class Person {
  constructor() {}
  //...
}

We write:

class Person {
  //...
}

They’re the same.

If we have a subclass, instead of writing:

class Waiter extends Person {
  constructor(...args) {
    super(...args);
  }
  //...
}

We write:

class Waiter extends Person {
  //...
}

We don’t need a call to the parent constructor if we don’t do anything in the constructor that’s unique to the subclass.

No Duplicate Class Members

We shouldn’t declare duplicate class members.

This is because the value of the method will always be the last one.

If we have:

class Foo {
  bar() {
    return 1;
  }
  bar() {
    return 3;
  }
}

Then bar returns 3.

So we should just stick with one:

class Foo {
  bar() {
    return 1;
  }
}

2 or more members of the same name are redundant.

Class Methods Should use this or be Made into a Static Method

If our method doesn’t reference this , then it should be static.

For instance, instead of writing:

class Foo {
  bar() {
    console.log('bar');
  }
}

We should write:

class Foo {
  static bar() {
    console.log('bar');
  }
}

Modules

Since ES6 has modules, it’s time to use them to organize our code.

Always Use Standard Modules

Before JavaScript has modules as a standard, there were various module systems.

However, now that JavaScript has modules as a standard feature, it’s time to ditch non-standard module systems.

For instance, instead of writing:

const foo = require('./foo');

and:

module.exports = {
  foo: 'bar'
};

We should write:

import foo from './foo';
//...
export default bar;

or:

import { foo } from './foo';
//...
export default bar;

No Wildcard Imports

We shouldn’t use wildcard imports to import everything.

For instance instead of writing:

import * as foo  from './foo';

We write:

import foo from './foo';

or:

import { bar } from './foo';

Don’t Export Directly from an Import

We shouldn’t use export in the same line as we import.

It’s better to make the imports and exports clear and consistent.

For instance, instead of writing:

export { bar as default } from './foo';

to export bar in the same line as we import bar from foo , we write:

import { bar } from './foo';
export default bar;

Import a Path only in one Place

We should have multiple lines that import members from the same module.

For instance, instead of writing:

import bar from 'foo';
import { baz, qux } from 'foo';

We should write:

import bar, { baz, qux } from 'foo';

Don’t Export Mutable Variables

We shouldn’t export anything that’s mutable. This is because the last value will be exported.

For instance, if we have:

foo.js

let bar = 3;
export { bar };
bar = 5;

Then when we import bar :

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

bar is 5.

Even though the export is done before bar is set to 5, we still get that it’s 5 after we import.

Therefore, just export const members:

foo.js

const bar = 3;
export { bar };

Conclusion

We should never export mutable variables. This way, they can’t change after we export them.

Also, we should separate import and export statements so that it’s clear what we’re doing.

If we don’t want to put anything in the constructor, we can skip it.

Categories
JavaScript Best Practices

JavaScript Best Practices — Arrow Functions and Classes

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 the best ways to work with arrow functions and classes.

Use Arrow Functions for Anonymous Functions

Arrow functions should be used for anonymous functions.

This way, we don’t have to deal with different values of this , and it’s shorter.

For instance, instead of writing:

[1, 2, 3].map(function (x) {
  return x + 1;
});

We write:

[1, 2, 3].map((x) => x + 1);

It’s much shorter and the value of this is the same as the outside.

Remove Curly Braces for Arrow Functions that are one Statement Long

We don’t need curly braces for arrow functions that are one statement long.

For instance, we can write:

[1, 2, 3].map((x) => x ** 2);

The return is implicit so we also don’t need to write return .

If an Expression Spans Multiple Lines, Wrap it in Parentheses

If we have an arrow function that’s one statement long, then we’ve to wrap it in parentheses so that we can return the whole expression.

For instance, we can write:

[1, 2, 3].map((x) => ({
  [x]: x
}));

This way, we map each array entry with the key and value set to the array entry’s value.

We can do that for any long expressions.

Include Parentheses Around Arguments for Clarity and Consistency

Adding parentheses around arguments make our arrow function signature clearer.

It’s also consistent with arrow functions that make no arguments or multiple arguments.

For instance, we write:

[1, 2, 3].map((x) => x ** x);

instead of:

[1, 2, 3].map(x => x ** x);

Avoid Confusing Arrow Function Syntax with Comparison Operators

We should put parentheses around comparison expressions so that we know that they’re comparison expressions.

For instance, instead of writing:

[1, 2, 3].map((x) => x <= 2);

We write:

[1, 2, 3].map((x) => (x <= 2));

As we can see omitting the parentheses make our arrow function much harder to read.

Classes & Constructors

We should embrace modern syntax instead of using old syntax.

Use class Syntax Instead of prototype

Using the class syntax makes things clearer than manipulating prototype properties.

Prototypes are something that many people are confused about.

To add instance methods to a constructor, instead of writing:

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

Person.prototype.getName = function() {
  return this.name;
}

We write:

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

  getName() {
    return this.name;
  }
}

Instead of adding methods to the prototype property, we should put methods inside a class instead.

Instance variables should be initialized in the constructor method instead of a constructor function.

After a class is declared with the given, we can’t declare another one with the same name.

That isn’t the case with constructors.

Use extends for Inheritance

extends with the class syntax makes inheritance much easier.

Also, instanceof will work properly.

For instance, instead of writing:

function Animal(name) {
  this.name = name;
}
Animal.prototype.getName = function() {
  return this.name;
}

function Dog(name) {
  Animal.call(this, name);
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Animal;

We should write:

class Animal {
  constructor(name) {
    this.name = name;
  }
  getName() {
    return this.name;
  }
}

class Dog extends Animal {
  constructor(name) {
    super(name);
  }
}

Now when we create a Dog instance, we can use instanceof to check if it’s an instance of Animal .

We can write:

const dog = new Dog();
console.log(dog instanceof Animal)

to see that the console log shows true so inheritance is done correctly.

The constructor function syntax gives us the same result but the process is more complex and error-prone.

Methods can Return this to Help with Method Chaining

In classes, we can have methods that return this to let us chain them.

For instance, we can write:

class Cube {
  setLength(val) {
    this.length = val;
    return this;
  }

  setWidth(val) {
    this.width = val;
    return this;
  }

  setHeight(val) {
    this.height = val;
    return this;
  }
}

Then we can chain methods of the Cube instance by writing:

const cube = new Cube()
  .setHeight(10)
  .setWidth(20)
  .setLength(30)

And cube is {height: 10, width: 20, length: 30}

Conclusion

The class syntax and arrow functions are 2 of the best features of JavaScript, so we should use them as much as possible.