Categories
JavaScript Best Practices

JavaScript Best Practices — Unary Operators, Configs, and Useless Expressions

To make code easy to read and maintain, we should follow some best practices.

In this article, we’ll look at some best practices we should follow to make everyone’s lives easier.

Unary Operators ++ and --

++ and -- both do assignment and return the value.

Their placement can change the meaning of the code.

So, we should consider using alternatives.

For instance, instead of writing:

let i = 10;
i++;

We write:

let i = 10;
i += 1;

Use of process.env

We may use configurations instead of setting environment variables.

They’re easy to replace.

For instance, instead of writing:

if (process.env.NODE_ENV === "development") {
  //...
}

We write:

const config = require("./config");

if (config.env === "development") {
  //...
}

No process.exit()

process.exit() ends the program immediately.

This means that we don’t have a chance to handle errors gracefully.

So instead of writing:

if (error) {
  process.exit(1);
}

Instead, we write:

if (error) {
  throw new Error("error happened");
}

No Use of __proto__

__proto__ is a hidden property of an object which has its prototype.

We should use Object.setPrototypeOf and Object.getPrototypeOf to set and get the prototype of an object respectively.

For instance, instead of writing:

const a = obj.__proto__;

obj.__proto__ = b;

We write:

const a = Object.getPrototypeOf(obj);

Object.setPrototypeOf(obj, b);

No Use of Object.prototypes Built-in Properties Directly

We shouldn’t use Objetyc.prptotype built-in properties directly since they may be overridden by anyone.

For instance, foo.hasOwnProperty can be overridden by our own method.

Instead of writing;

const hasBazProperty = foo.hasOwnProperty("baz");

We write:

const hasBazProperty = Object.prototype.hasOwnProperty.call(foo, "baz");

No Variable Redeclaration

We shouldn’t redeclare variables, even though we can with var .

For instance, we shouldn’t write:

var a = 3;
var a = 123;

Instead, we write:

var a = 3;
a = 123;

Multiple Spaces in Regular Expression Literals

We shouldn’t make our regex literals more complex with extra spaces.

They’re hard to read, and we can replace them with curly braces.

For instance, instead of writing:

const re = /foo     bar/;

We write:

const re = /foo {5}bar/;

No Assignment in return Statement

We shouldn’t assign values ina returnm statement.

It’s probably a mistake if we do.

We probably want to do a comparison.

For instance, instead of writing:

function doWork() {
  return foo = bar + 2;
}

We write:

function doWork() {
  return foo === bar + 2;
}

No Unnecessary return await

We don’t need to use return await together.

Using await with return just add an extra task to the microtask queue.

async functions always return a promise, so we can just return the promise directly instead of awaiting it before doing so.

For instance, instead of writing:

async function foo() {
  return await bar();
}

We write:

async function foo() {
  return bar();
}

No Script URLs

Script URLs are like eval . They can run JavaScript code that’s in a string.

So instead of writing:

location.href = "javascript:void(0)";

we remove that.

No Self Assignment

We shouldn’t do self-assignment in our code since they’re useless.

For instance, instead of writing:

foo = foo;

or:

[bar, baz] = [bar, abc];

Instead, we write:

foo = bar;
[a, b] = [b, a];

No Self Compare

Self-comparisons are useless, so we shouldn’t write to them.

For instead of writing:

if (x === x) {
  x = 20;
}

We write:

if (x === y) {
  x = 20;
}

No Use of the Comma Operator

We shouldn’t use the comma operator since they always return the last item in the list.

For instance, instead of writing:

const a = (3, 5);

We just write:

const a = 5;

No Returning Values from Setters

We shouldn’t return values from setters since they can’t be used.

For instance, we shouldn’t write:

conat foo = {
  set b(value) {
    this.val = value;
    return value;
  }
};

class Foo {
  set b(value) {
    this.val = value * 2;
    return this.val;
  }
}

Instead, we write:

conat foo = {
  set b(value) {
    this.val = value;
  }
};

class Foo {
  set b(value) {
    this.val = value * 2;
  }
}

Conclusion

We may consider using config files instead of environment variables.

Useless expressions and statements like self compare, returning things in setters, etc. should be removed.

Categories
JavaScript Best Practices

JavaScript Best Practices — Modules and Identifiers

To make code easy to read and maintain, we should follow some best practices.

In this article, we’ll look at some best practices we should follow to make everyone’s lives easier.

Minimum and Maximum Identifier Lengths

We should make identifier descriptive so they definitely shouldn’t be too short.

It also shouldn’t be too long.

If it’s too short, it’s probably not descriptive enough.

If it’s too long, then it has redundant information and takes up too much space.

So we shouldn’t write:

const x = 5;

since we don’t know what x is.

But we can write something like const numApples = 1; .

Location of Arrow Function Bodies with Implicit Returns

We should put arrows in a logical location.

For instance, we can write:

(foo) => bar;

or

(foo) => (bar);

or

(foo) => bar => baz;

They are conventional and logical.

It’s better than these examples:

(foo) =>
  bar;

or:

(foo) =>
  (bar);

The first examples are more compact than these ones.

Default Imports

We’ve have a default export for a default import to work.

For instance, we should write:

foo.js

export default function () { return 100 }

Then we should import it by writing:

import foo from './foo';

For CommonJS modules, we can write:

foo.js

module.exports = function () { return 100 }

Then we can import it by writing:

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

Repeated Imports and Exports

We can only have one default export in each module.

For instance, we can’t write:

export default class Foo { /*...*/ }

export default class Bar { /*...*/ }

We have to remove one of them.

If we remove one:

export default class Foo { /*...*/ }

Then we can write:

import Foo from './foo';

Export Statements that Come Before Other Statements

We shouldn’t have export statements that come before other statements.

For instance, we don’t want to fix statements like:

const bool = true

export default bool

const str = 'foo'

Moving all exports to the bottom would make it easier to read.

Use of File Extension within the Import Path

When we write imports, we don’t need an extension for JavaScript files.

For instance, instead of writing:

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

We write:

import bar from './foo/bar';

However, we should put the extension for JSON import.

For instance, we can write:

import bar from './bar.json';

We may also do the same thing for JavaScript files that don’t have the .js extension.

For instance, we can write:

import Component from './Component.jsx';

for JSX files.

Putting Imports First

We should put import statements first.

For instance, we should put them at the top.

Instead of writing:

import foo from './foo'

doSomething(foo)

import bar from './bar'

We write:

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

doSomething(foo);

Grouping Exports

We can group exports together to make finding them easier.

For instance, we can write:

const first = 'foo';
const second = 'bar';

export {
  first,
  second,
}

With CommonJS modules, we can write:

const test = {};
test.foo = true
test.bar  = true

module.exports = test;

We also grouped the exports together.

Named Exports

We can import named module members in various ways.

Given that we have:

export const foo = "foo"

We can write:

import { foo } from './foo'

Also, we can write:

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

to export it as bar .

We can also import it as bar:

import { foo as bar } from './foo'

Newline After Import

After a group of imports, we can put a new line to separate the imports from the rest of the code.

For instance, we can write:

import defaultExport from './foo'
import { bar } from 'bar'

const foo = 'bar';

or:

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

const baz = 1;

for CommonJS modules.

No Absolute Paths

We shouldn’t use absolute paths for imports because it’ll only work on the current computer’s current folder strucutre.

If we move it anywhere, it won’t work.

So we shouldn’t write:

import f from '/foo';
import bar from '/some/path';

or:

const f = require('/foo');
const bar = require('/some/path');

Instead, we write:

import f from './foo';
import bar from './some/path';

or:

const f = require('./foo');
const bar = require('./some/path');

Conclusion

We should write our imports and exports properly.

Never import with absolute paths.

And we should group them together.

Identifier lengths shouldn’t be too long or short.

Categories
JavaScript Best Practices

JavaScript Best Practices — Spacing, Lines, and Call

To make code easy to read and maintain, we should follow some best practices.

In this article, we’ll look at some best practices we should follow to make everyone’s lives easier.

Named Function Expressions

Names in function expressions aren’t very useful.

If we set the function to a variable, we can’t use it with that name.

We also don’t need it much in IIFEs.

So we can write:

Foo.prototype.bar = function() {};

instead of:

Foo.prototype.bar = function bar() {};

Also, we can write:

(function() {
    // ...
}())

instead of:

(function bar() {
    // ...
}())

Unless we need the name inside the function, it’s not that useful.

Consistent Use of Either Function Declarations or Expressions

We can keep to one function style for consistency.

So either we stick with:

function doWork() {
  // ...
}

or:

const doWork = function() {
  // ...
};

Arrow functions can only use the second style.

Line Breaks Between Arguments of a Function Call

We only need line breaks for arguments and function calls are only needed for long expressions or argument lists.

For instance, we can write:

foo(
  "foo",
  "bar",
  "three"
);

or:

foo("one")

We just keep line length around 100 characters or less.

Consistent Line Breaks Inside Function Parentheses

We should have consistent line breaks inside function parentheses.

For instance, we can write:

function foo(
  bar,
  baz
) {}

or:

const foo = function(
  bar, baz
) {};

For short functions, we can keep the signature all in one line.

Other than that, we break it into multiple lines.

Spacing Around the * in Generator Functions

We should keep the spacing around the * in generator functions.

For instance, we can write:

function* generator() {
  yield "foo";
  yield "bar";
}

or:

function * generator() {
  yield "foo";
  yield "bar";
}

or:

function *generator() {
  yield "foo";
  yield "bar";
}

We just have to stick with one.

Return Statement Should be Present in Property Getters

The whole point of getters is to return something.

Therefore, we should make sure that’s done in getters.

For instance, we should write:

const obj = {
  get name() {
    return "james";
  }
};

or:

Object.defineProperty(obj, "age", {
   get() {
     return 100;
   }
 });

instead of:

const obj = {
  get name() {
    return;
  }
};

or:

Object.defineProperty(obj, "age", {
   get() {

   }
 });

or:

class P {
  get name() {

  }
}

Put require() on the Top-Level Module Scope

We should put our require calls at the top level of the module scope.

For instance, we should write:

const fs = require("fs");

instead of:

function foo() {
  if (condition) {
    const fs = require("fs");
  }
}

Dynamically requiring things is confusing,

So we shouldn’t do it.

Require Grouped Accessor Pairs in Object Literals and Classes

Having setters that don’t have getters isn’t very useful since we can’t get the value that’s set.

Therefore, we should have getters for any setters.

For instance, we should write:

const obj = {
  get a() {
    return this.val;
  },
  set a(value) {
    this.val = value;
  },
  b: 1
};

instead of:

const obj = {
  set a(value) {
    this.val = value;
  },
  b: 1
};

We can get the value of a in obj with the getter.

Guarding for for-in

We should check for inherited properties if we loop with for-in since it enumerates inherited properties.

Instead of writing:

for (key in foo) {
  doWork(key);
}

We write:

for (key in foo) {
  if (Object.prototype.hasOwnProperty.call(foo, key)) {
    doWork(key);
  }
}

or:

for (key in foo) {
  if ({}.hasOwnProperty.call(foo, key)) {
    doSomething(key);
  }
}

We use Object.prototype.hasOwnProperty.call or {}.hasOwnProperty.call instead of foo.hasOwnProperty so that it still works if hasOwnProperty has been overwritten.

hasOwnProperty checks if a property is a noninherited property.

Callback Error Handling

We should check for errors in callbacks if they exist.

For instance, we can write:

function foo(err, data) {
  if (err) {
    console.log(err.stack);
  }
  doWork();
}

If it’s a Node style callback, err will be populated with the error, which we should handle in case it’s thrown.

Conclusion

We should handle errors in callback.

Naming function expressions is redundant.

Line breaks and spacing should be consistent.

Categories
JavaScript Best Practices

JavaScript Best Practices — 12 Useless Statements You Should Avoid

To make code easy to read and maintain, we should follow some best practices.

In this article, we’ll look at some best practices we should follow to make everyone’s lives easier.

No Dangling Underscores in Identifiers

Dangling underscores are used for identifying private variables whether they’re actually private or not.

It’s best not to use this since if they’re not private, people may think it’s private.

So instead of writing:

foo._bar();

We write:

foo.bar();

No Confusing Multiline Expressions

We should separate statements and expressions with semicolons when they need to be separated.

For instance, we shouldn’t code like:

const foo = bar
[1, 2, 3].forEach(log);

We don’t know if the 2 lines should be together or not.

Instead, we write:

const foo = bar;
[1, 2, 3].forEach(log);

to separate them clearly.

No Unmodified Conditions of Loops

We shouldn’ have unmodified conditions in loops.

This creates infinite loops, which probably don’t want.

For instance, we shouldn’t write:

while (item) {
  doWork(item);
}

Instead, we write:

for (let j = 0; j < items.length; j++) {
  doWork(items[j]);
}

No Ternary Operators when Simpler Alternatives Exist

We shouldn’t use ternary expressions when there’re simpler alternatives.

For instance, we shouldn’t write:

const isOne = answer === 1 ? true : false;

Instead, we write:

const isOne = answer === 1;

We don’t need to write true and false explicitly.

No Unreachable Code After return, throw, continue, and break Statements

We shouldn’t have unreachable code after return , throw , continue , and break statements since they’ll never be run.

For instance, instead of writing:

function foo() {
  return true;
  console.log("end");
}

We write:

function foo() {
  return true;
}

No Control Flow Statements in finally Blocks

We shouldn’t have control flow statements like return statements in finally blocks.

Instead, we should put them elsewhere.

Control flow statements are suspended until finally runs.

So the control flow statements in try or catch will be overwritten with the one in finally .

Therefore, instead of writing:

try {
  return 1;
} catch (err) {
  return 2;
} finally {
  return 3;
}

We write:

try {
  return 1;
} catch (err) {
  return 2;
} finally {
  console.log('finally');
}

No Negating the Left Operand of relational Operators

We shouldn’t negate the left operand of relational operators since they only negate the operand and not the whole expression.

For instance, instead of writing:

if (!key in obj) {
  // ...
}

We write:

if (!(key in obj)) {
  // ...
}

No Unused Expressions

If an expression isn’t used, then we should remove them.

For instance, we shouldn’t write:

{0}

or:

c = a, b;

instead, we write:

f()

or:

a = 0

etc.

No Unused Variables

If we have unused variables, we should remove them.

So if we have:

var x;

and it isn’t used anywhere else, then we should remove them.

No Early Use of Variables and Functions

We shouldn’t use variables or functions before they’re declared in our code.

For instance, we can write:

console.log(a);
var a = 10;

or:

f();
function f() {}

We can use function declarations and variables before they’re declared.

Function declarations can be called, but variables will be undefined if they’re used before they’re declared.

To remove confusion, we should write:

var a = 10;
console.log(a);

and:

function f() {}
f();

No Unnecessary .call() and .apply()

We shouldn’t use call and apply unnecessarily.

If we don’t change the value of this , then we shouldn’t use them.

Instead, we just call it directly.

So instead of writing:

foo.call(undefined, 1, 2, 3);
foo.apply(undefined, [1, 2, 3]);
obj.foo.call(obj, 1, 2, 3);
obj.foo.apply(obj, [1, 2, 3]);

We write:

foo(1, 2, 3)
obj.foo(1, 2, 3)

No Unnecessary catch Clauses

If our catch clause only rethrows an exception, then we don’t need them.

So we shouldn’t write:

try {
  doWork();
} catch (e) {
  throw e;
}

try {
  doWork();
} catch (e) {
  throw e;
} finally {
  cleanUp();
}

We write:

try {
  doWork();
} catch (e) {
  handleError(e);
}
try {
  doWork();
} catch (e) {
  handleError(e);
} finally {
  cleanUp();
}

Conclusion

We shouldn’t write useless statements.

They make our code more complex and are redundant.

Categories
JavaScript Best Practices

JavaScript Best Practices — Shadowing Variables and Spacing

To make code easy to read and maintain, we should follow some best practices.

In this article, we’ll look at some best practices we should follow to make everyone’s lives easier.

No Variable Declarations from Shadowing Variables Declared in the Outer Scope

We shouldn’t have variable declarations from shadowing variables declared in the outer scope.

For instance, we shouldn’t write things like:

var a = 3;

function b() {
  var a = 10;
}

Instead, we write:

function b() {
  let a = 10;
}

We don’t want to declare them twice since the a outside the function is in the global scope, which means they cause confusion.

No Shadowing of Restricted Names

We shouldn’t assign anything to variables with restricted names.

For instance, we shouldn’t write statements like:

const undefined = "foo";

Instead, we write:

const foo = "foo";

No Spacing Between Function Identifiers and Their Applications

We shouldn’t have spacing between function identifiers and their invocation.

So we shouldn’t have things like:

fn ()

fn
()

Instead, we write:

fn()

No Sparse Arrays

Sparse arrays have empty slots.

These may be mistakes, so we may want to avoid them.

For instance, instead of writing:

const items = [,,];

We write:

const colors = [ "red", "green"];

Node Synchronous Methods

If we’re writing an app, we definitely shouldn’t be using synchronous methods.

They hold up the whole app with one operation since Node is single-threaded.

They’re still good for scripts.

So instead of writing:

function foo(path) {
  const contents = fs.readFileSync(path).toString();
}

We write;

async(function() {
  // ...
});

in apps.

No Tabs

Tabs aren’t as good as spaces for spacing since they may differ between text editors and platforms.

So instead of tabs, we write spaces.

We can replace tabs automatically with spaces.

No Template Literal Placeholder Syntax in Regular Strings

The template literal placeholder syntax in regular strings is useless.

Therefore, we should only use them in template strings.

For instance, instead of writing:

const greet = "Hello ${name}!";

We write:

const greet = `Hello ${name}!`;

Ternary Operators

Ternary operators are useful for conditionally returning data in line.

For instance, we can write:

const foo = isBar ? baz : bar;

It’s short and not too hard to read.

No Use of this/super Before Calling super() in Constructors

We should call super before assigning this properties in the subclass constructor.

For instance, instead of writing:

class A extends B {
  constructor() {
    this.a = 0;
    super();
  }
}

We write:

class A extends B {
  constructor() {
    super();
    this.a = 0;
  }
}

We’ll get an error with the first example.

Restrict What can be Thrown as an Exception

We show throw Error instances when using throw ,

For instance, instead of writing:

throw "error";

We write:

throw new Error("error");

The Error object has more useful things like the stack trace and message that isn’t in other objects or values.

Trailing Whitespace at the End of Lines

We shouldn’t have trailing whitespace at the end of the lines.

We should remove trailing whitespace if they’re there.

No Undeclared Variables

We shouldn’t use undeclared variables in our code.

Without strict mode, they’re treated as global variables.

If strict mode is on, it’ll be treated as an error.

So w shouldn’t write things like:

const bar = a + 1;

if a isn’t declared.

Instead, we write:

const a = 2;
const bar = a + 1;

No Initializing to undefined

Variables that are declared without a value are initialized to undefined by default, so we don’t need to set explicitly.

For instance, we shouldn’t need to write:

let foo = undefined;

Instead, we write:

let foo;

No Use of undefined Variable

We shouldn’t set a value to undefined .

It’ll throw an error under strict mode.

For instance, we shouldn’t write code like:

const undefined = "hi";

It makes no sense to assign undefined to a value.

Conclusion

We shouldn’t assign restricted identifiers with values.

Also, we should make sure that function calls and code are formatted in conventional ways to avoid confusion and other issues.

Node synchronous methods are good for scripts but not apps.