Categories
JavaScript Best Practices

JavaScript Best Practices — Constructors and Classes

JavaScript lets us do a lot of things. It’s sometimes too forgiving in its syntax.

In this article, we’ll look at some best practices when developing in JavaScript, including object creation and constructors.

Prototypes

When we have methods in our constructor, we should add them to the prototype property of the constructor instead of putting it inside the constructor function.

For instance, instead of writing:

function Person(name) {
  this.name = name;
  this.greet = function() {
    return `hi ${this.name}`;
  }
}

We should write:

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

Person.prototype.greet = function() {
  return `hi ${this.name}`;
}

Even better, we use the class syntax by writing:

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

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

The last 2 pieces of code are the same.

We want to write our constructors like the last 2 examples because we only want to share one copy of the greet method between all the instances.

The greet method doesn’t change, only the value of this does.

Therefore, we don’t want to keep different copies of the same method as we would do in the first example.

Instead, we want to share it as we do in the last 2 examples.

We can create an instance of the constructor to find the difference:

const person = new Person('joe');
console.log(person)

If we log the person object, we can see that the method is in the __proto__ property, which is the prototype of the person .

It has the greet method.

If we create the same object with the version first version of the Person constructor, we’ll see that the greet method is in the person object itself.

Therefore, to save memory, the method should be in the constructor’s prototype property rather than inside the constructor itself.

Constructor’s Return Values

Constructors don’t have to return something explicitly.

We usually shouldn’t return anything explicitly because it’ll return the instance of the constructor automatically.

However, it does let us return something different if we want.

But we usually shouldn’t do that unless we have a reason to.

If we want to do that we can write:

function Person(name) {
  this.name = name;
  return {
    foo: name
  }
}

Then when we instantiate it as follows:

const person = new Person('joe');
console.log(person)

We get {foo: “joe”} instead of Person {name: “joe”} according to the console log output.

Patterns for Enforcing new

To create a new instance of an object, we need to use new .

The best way to do that is to use the class syntax.

This way we can’t call it without new .

If we call a constructor without new , then this would be the global object.

Naming Convention for Constructors/Classes

Constructors or classes are usually named in PascalCase.

This distinguishes regular functions from constructors and classes.

Using that

If we want to make sure that our constructor always behaves as a constructor, we can also create an object and return that.

For instance, we can write:

function Person(name) {
  const that = {};
  that.name = name;
  return that;
}

Now if we call our Person constructor with our without new , we still get an object returned.

Self-Invoking Constructor

We may also use the instanceof operator to check if a constructor’s this is the instance of the constructor.

Then we’ll know if we called it with new or not.

If we did, then the constructor’s this should be an instance of the constructor.

For instance, we can check as follows:

function Person(name) {
  this.name = name;
  if (!(this instanceof Person)) {
    return new Person(name);
  }
}

Now whether we called the Person constructor with new or not, we get the same result.

const person = new Person('joe');

and:

const person = Person('joe');

should get us the same result.

Conclusion

We should keep our methods in the prototype of the constructor rather than inside the constructor itself.

This way, one copy of the method is shared among all the instances.

This works because the method is the same across all instances.

The class syntax does this for us automatically.

Also, we can check if this is an instance of the constructor to check if our constructor has been called properly.

The class syntax also takes care of that for us since we can’t invoke a class without the new keyword.

Categories
JavaScript Best Practices

JavaScript Best Practices — Spacing and Variables

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 format file source code for readability.

We also look at how variables should be declared.

Indentation of Continuation Lines

If our line continues from the previous line, then we indent at least 4 spaces so that we can distinguish the line from the previous line.

Spacing

We should think about spacing when we’re writing our code.

Some spaces are required and some are not. Even though they aren’t required, we may want to have them.

Vertical Whitespace

We should have a single blank line between consecutive methods in a class or object literal.

Method bodies should have line breaks between logic groups.

However, we shouldn’t have blank lines at the start or end of the function.

We may have an empty line before the first and last method in a class or object literal.

Horizontal Whitespace

Horizontal whitespace usage depends on location.

We should never have trailing whitespaces.

We should have whitespace to separate reserved words like if , for , or catch .

function and super don’t need a space between them and the opening parentheses.

else or catch are used to separate reserved words from a closing curly brace preceding it.

We should put a space before an opening curly brace.

Also, we shouldn’t have any space in the opening of a template string expression.

So we shouldn’t have:

`a $ {y} b`

Instead, we should write:

`a ${y} b`

Both sides of any binary or ternary expression should also have a space between operands.

Commas and semicolons should also be added.

Colon in the object literal should also have one space after it.

Both sides of double slash that start an end of line comment should also have space before it.

Open-block comment character and both sides of those characters also have spaces.

No Horizontal Alignment

We shouldn’t have horizontal alignment in our code.

They’re hard to maintain.

For instance, we can just write:

{
  foo: 1,
  longer: 2,
};

instead of:

{
  foo:    1,
  longer: 2,
};

Function Arguments

We should wrap function arguments so that each line width is 100 characters or less.

For instance, we can write:

doSomething(
  fooArgumentOne,
  fooArgumentTwo,
  fooArgumentThree
)

Grouping Parentheses

We may have parentheses to group expressions that may be misinterpreted without them.

However, they’re unnecessary for expressions that follow delete , typeof , void , return , throw , case , in , of , or yield .

Comments

We may want to put comments in some of our code.

Block Comments

We can add comments as blocks with /* ... */ for multiline comments.

For single-line comments, we start with // .

Parameter Name Comments

We should comment on the naming of parameters if they don’t convey their meaning clearly enough.

For instance, we can write:

foo(obviousParam, /* greeting= */ 'hello');

Language Features

JavaScript has many dubious or dangerous features that we should avoid.

We should avoid the bad ones as much as possible.

Variable Declarations

There are much better ways to declare variables than others.

We should use them as much as possible.

Photo by Brooke Lark on Unsplash

Use let or const

We should use let or const to declare variables.

This way, they’re block-scoped and won’t be available outside the block.

Unless a variable needs to be reassigned, const should be used.

var keyword must never be used.

One Variable Per Declaration

We should never have more than one variable per declaration.

Therefore, something like let a = 3, b = 2; should never be used.

Declare Variables When Needed

We should declare variables right before they’re needed.

This way, we don’t have to follow the usage of one variable for too many lines.

Initialize as Soon as Possible

We should initialize variables as soon as possible.

This way, we won’t forget about them and get variable not initialized or undefined errors.

Conclusion

We should group or expressions with parentheses to clarify how they’re called.

Indentation should be added to blocks.

Vertical and horizontal whitespaces are both important.

Variables should be declared only when they’re needed. And they should be declared with let or const .

Categories
JavaScript Best Practices

JavaScript Best Practices — Object Creation

JavaScript lets us do a lot of things. It’s sometimes too forgiving in its syntax.

In this article, we’ll look at how to create objects, including namespacing members with global objects, destructuring dependencies, and private members.

Object Creation

We can create JavaScript objects in a few ways.

We can either use object literals or constructor functions/classes.

However, there’re some patterns that are useful for creating objects in an orderly manner.

Namespace Pattern

We can separate JavaScript code into namespaces so that we don’t have so many global variables.

Namespaces let us avoid name collisions and reduce the need to prefix names.

For instance, we can write:

const APP = {};
APP.Foo = class {}
APP.Bar = class {}
APP.c = 1;
//...

The code above creates a global APP object which has multiple classes inside it.

They’re in their own object so that name collision is minimized compared to having them all at the top level.

Now that we have JavaScript modules, we can just use that instead.

But if we’re using old-fashioned scripts, we can use this pattern.

It’s a bit more to type. Also, the state of the object is propagated everywhere since it’s all under one global object.

Nested names also mean longer property resolutions lookups.

General Purpose Namespace Function

We can’t assume the object that we have been in the script.

Therefore, we may want to check if it’s there first before we put things in there.

For instance, we can write:

APP = APP || {};

In case APP isn’t defined yet, we should check with the || operator to see if it’s defined.

We can also create a namespacing function.

For instance, we can write:

const namespace = (path) => {
  const parts = path.split('.');
  const obj = {};
  let nestedObj = obj;
  for (const p of parts) {
    nestedObj[p] = {};
    nestedObj = nestedObj[p];

}
  return obj;
}

const obj = namespace('foo.bar');

The code above created a namespace function that takes an object path separated by dots.

It separates the path and creates the empty nested objects that we need.

Then we can put that in the function.

Declaring Dependencies

Most dependencies are namespaces, which we can use to reference global variables from dependencies easily.

For instance, we can write:

const {
  foo,
  bar
} = APP;

to destructure the APP namespace into its properties.

This way, we can access them individually in functions or any other piece of code.

Private Properties and Methods

JavaScript has no private properties and methods in objects and constructors.

To keep the private, we either keep them in modules or functions.

If we keep them in objects, anything can access them.

For instance, we can write:

const obj = {
  foo: 1,
  getFoo() {
    return this.foo;
  }
};

Then we can access the properties as follows:

obj.foo
obj.getFoo()

Nothing can stop us from accessing them.

Likewise, with constructors and classes, everything is public.

For instance, if we have:

class Foo {
  constructor() {
    this.foo = 1;
  }

  getFoo() {
    return this.foo;
  }
};

Then we can write:

(new Foo()).foo;

or:

(new Foo()).getFoo();

Private Members

If we want private members, then they can’t be a property of this .

So we can write:

class Foo {
  constructor() {
    const foo = 1;
  }

  getFoo() {
    return this.foo;
  }
};

We can only put them into functions and declare them with let and const to avoid exposing them to the public.

With the constructor function syntax, we can write:

function Foo() {
  const foo = 1;
};

Privileged Methods

Public members that have access to private members are called privileged methods.

We can define one as follows:

function Foo() {
  const foo = 1;
  this.getFoo = function() {
    return foo;
  }
};

getFoo is a privileged method as it can access foo .

This way, we can keep things private while being able to use them when needed.

Conclusion

We can use the namespace pattern to put members in a global object to avoid name collisions.

Also, there’re no private members in classes or constructors. So we’ve to define ordinary data with let or const to make them private.

Privileged methods are ones that can access private data.

Categories
JavaScript Best Practices

JavaScript Best Practices — Loops and Returns

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 practices for writing loops and functions like loop length, nesting, and returns.

Make Our Loops Short Enough to View all at Once

Our loops should be short so that the whole thing can be viewed on the page.

Otherwise, it’s too long, and we should break it up.

Limit Nesting to 3 Levels

Nesting usually makes our code harder to read. Therefore, we can limit it to 3 levels max.

Other than that, it’s way too much and we should eliminate them by putting some code into functions for example.

Move Loop Innards of Long Loops into Functions

If we have a long loop, that means that we’ve to break our code into smaller functions.

Small functions and loops are easier to read, so we should write those instead of putting everything into one big loop.

Make Long Loops Especially Clear

If we can’t avoid long loops, then we should make them as clear as possible.

Otherwise, we’ll have a hard time reading our own code.

Correspondence Between Loops and Arrays

Loops and arrays are often related. Many loops are used for manipulating arrays.

Loop counter variables so one to one with array indexes.

However, with JavaScript, we don’t always need to write loops to do array operations.

For instance, there’re many methods in the Array constructor that we can use to create loops with.

We can use map to map each array entry to new values by passing in a callback into it.

For instance, we can use it to square all numbers in an array as follows:

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

There’s also the filter method for returning an array with entries that meet some given condition.

We can find all the even numbers in an array as follows:

const arr = [1, 2, 3];
const result = arr.filter(a => a % 2 === 0);

Also, to search for an array item, we can use the find method as follows:

const arr = [1, 2, 3];
const result = arr.find(a => a % 2 === 0);

The code above will find the first even number in the array.

There’re many more methods that we can use instead of writing out loops.

We may want to consider those before using loops.

Multiple Returns from a Function

In JavaScript, we don’t have to exit a function at the end of a function.

We can use the return keyword to stop the function.

For instance, we can write something like:

const fn = () => {
  if (invalid) {
    return;
  }
  //...
  return {
    //...
  }
}

We can stop the function when we have invalid set to true in the example above.

This lets us clean up our code by reducing nesting.

Use a return When it Enhances Readability

We can add return statements to enhance readability.

It’s useful for stopping a function when certain conditions are met as we have seen.

Use Guard Clauses to Simplify Complex Error Processing

As we can see from the example above, we can return early.

A group of code that lets us stop running a function early is called a guard clause.

For instance, in the function we have above:

const fn = () => {
  if (invalid) {
    return;
  }
  //...
  return {
    //...
  }
}

The following is the guard clause:

if (invalid) {
  return;
}

It’s much better than writing things like:

const fn = () => {
  if (!invalid) {
    //...
    return {
      //...
    }
  }
}

which does the same thing but has a lot more nesting.

Nesting is hard to read and the guard clause lets us avoid nesting.

Minimize the Number of returns in Each Function

Guard clauses are great, but we shouldn’t use them too frequently.

The more return statements we have in our code, the harder it is to understand.

Conclusion

Nesting is bad in loops or functions, so we should reduce them.

In functions, we can reduce nesting with return statements. We can return a function before the last line in some cases.

Instead of loops, we can also use array methods to do array operations.

Categories
JavaScript Best Practices

JavaScript Best Practices — Commenting Control Structures and Functions

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 practices for commenting on control structures and functions.

Commenting Control Structures

Space before the control structure is a good place for comments.

We may want to explain why we’re doing something with the loop or conditional statement.

Put a Comment Before Each if, case, loop, or Block of Statements

If we want to put in comments, we should put them before if , case , loops or blocks.

It’s a good place to clarify those structures.

Comment the End of Each Control Structure

We may also want to put them at the end of each control structure.

Treat End-of-Loop Comments as a Warning Indicating Complicated Code

Our loops usually don’t need much explanation.

If we feel like that we’ve to explain it with comments, then it’s probably too complex.

Therefore, we should simplify them in those cases.

Commenting Functions

We don’t need long comments before our functions.

If we have to write to them for every function, then people won’t be willing to create more functions, and rather make the existing ones long and hard to read.

Keep Comments Close to the Code they Describe

We should keep comments close to the code they describe so that they’ll more likely be maintained.

Also, we know what it’s actually commenting on if they’re close to the code that they’re describing.

Describe Each Function in One or Two Sentences at the Top of the Function

One or 2 sentences are enough to describe a function.

If we can’t describe our function in one or 2 sentences, then it’s probably too long.

Except for get and set accessor functions, the rest may use some description.

Document Parameters Where they are Declared

When parameters are declared, then we may want to describe them.

This way, we know what they’re storing if needed.

Comment on the Function’s Limitations

If there’re any limitations that we have to know about, then we should document them so that we know what we may run into when calling it.

Document the Function’s Global Effects

If a function changes global variables, then we should know about that.

It might be hard to find what we’re doing with global variables, so comments may help with that.

Document the Source of Algorithms that are Used

If we used an algorithm from some other source, then we may want to attribute it to that source so that people can look them up in that source.

Use Comments to Mark Parts of Our Program

We may also want to mark parts of our program with comments so we can find them later.

Describe the Design Approach to the Class

If we design a class, then we may want to explain our design approach by leaving some comments in the class.

The design philosophy, ap[approach, and alternatives that were discarded can all be explained.

Describe Limitations and Usage Assumptions

Any limitations and usage assumptions with our class can be explained with comments.

Issues with error handling, global effects, and uses of algorithms can all be explained.

Comments on Files

The top of code files is also good place to put comments.

Describe the Purpose and Contents of Each File

The purpose and content of each file may also use some explanation.

For instance, we can explain why there are multiple classes in a file.

Also, we may also want to explain why we divide our code into files in the way that we did.

Photo by Kon Karampelas on Unsplash

Put Name, Email Address, and Phone Number in Block Comments

Name of the author, his or her email address, and phone numbers may be in comments.

However, the name of the author may already be in the source control history, so we may not need it in the comments.

Include a Version-Control Tag

To identify the version of the program built, we may want to add a version control tag automatically to make that easier.

Include Legal Notices in the Block Comment

If a legal notice is required, we may also want to include in a comment in our code files.

Conclusion

There’re a lot of things that we may want to write comments for.

Legal notices, version numbers, design approaches and more can be outlined with comments.

We may also explain the limitations of our code in there.