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.

Categories
JavaScript Best Practices

JavaScript Best Practices — Comments and 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 the best ways to work with comments and spaces.

Comments

We should be careful when writing JavaScript. We want them to be readable like anything else.

Use /** ... */ for Multiline Comments

The /** ... */ block denotes that a multiline comment inside it.

It’s better than // since we can write our comments in one block.

For example, instead of writing:

// foo
// bar

We can write:

/**
 * foo
 * bar
 */

Use // for Single Line Comments

// is great for single-line comments. We should put a single line above the subject of the comment.

And an empty line should be added before the comment.

For instance, we should write:

function getType() {
  // returns type
}

or:

function getType() {
  console.log('get type');

// returns type
}

Prefix FIXME and TODO to Label Code that Needs Changing Later

We can change FIXME and TODO to label code that needs changing later.

This way, we won’t forget about them.

We just have to remember to remove comments after we’re done.

For instance, we can write:

// TODO - clean up code

If our code has Problems, then use // FIXME to Mark Them

For instance, we can write:

// FIXME - remove global variable

so that we can fix that.

Use // TODO to Mark Solutions to a Problem

If we have a solution that we don’t have time to implement yet but we have an idea of how to do it, we should use // TODO to mark that.

For instance, we can write:

// TODO - make total configurable

to mark the solution to some problem that hasn’t been implemented yet.

Whitespace

Spaces make things easier to read, so we should add them consistently.

Use Soft Tabs Set to 2 Spaces

Spaces are consistent among operating systems, so they’re better than tabs.

2 spaces make things readable without too much typing.

So instead of writing:

function foo (){
 let x;
}

We write:

function foo (){
  let x;
}

We can also configure our text editor to convert 1 tab to 2 spaces automatically to save typing.

Place 1 Space Before Leading Brace

We should place 1 space before a leading brace so that we can read our functions easier.

For instance, instead of writing:

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

We write:

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

Likewise, with arrow functions, instead of writing:

const foo = () =>{
  console.log('foo');
}

We write:

const foo = () => {
  console.log('foo');
}

With function calls, instead of writing:

person.set('attrs',{
  age: 1,
  gender: 'male'
});

We write:

person.set('attrs', {
  age: 1,
  gender: 'male'
});

Just one space makes things more readable.

Place 1 Space Before the Opening Parenthesis in Control Statements

If we’re writing conditional statements or loops, we should have 1 space before the opening parenthesis.

And we should have no pace between the argument list and the function names in function calls and declarations.

For instance, instead of writing:

if(foo) {
  bar ();
}

We should write:

if (foo) {
  bar();
}

Likewise, for loops, instead of writing:

while(foo) {
  bar();
}

We write:

while (foo) {
  bar();
}

For function definitions, instead of writing:

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

We write:

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

The spacing makes our blocks more readable.

Set Off Operators with Spaces

We should have spaces between operands and operators to improve readability.

For instance, instead of writing:

const x=y+1;

We write:

const x = y + 1;

End File with a Single New Line Character

We should always end a file with a single newline character.

This way, they can always be concatenated properly by programs that do that if needed.

For instance, instead of writing:

index.js

const x = 1;

We write:

index.js

const x = 1;


Conclusion

Spacing is important. Therefore, we should have them in places where they make sense.

If we write comments, we should denote multiline comments in a different way than single-line comments.

We may also use comments to denote todo items and things that need fixing.

Newline character should be at the end of file so that it can be concatenated properly with other files.

Categories
JavaScript Best Practices

JavaScript Best Practices —Names, jQuery, 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 name things and deal with objects, names, and jQuery code.

Use PascalCase for Constructor, Class, Singleton or Library Exports

We stick with PascalCase as we do without export for constructors, classes, singleton objects, and libraries.

For instance, we write:

const StudentUser = {
  //...
};

export default StudentUser;

This also applies to constructors and constructor functions:

class StudentUser {
  //...
}

function StudentUser {
  //...
}

Acronyms Should be All Upper Case or Lower Case

Having acronyms all in the same case makes them more readable.

For instance, we can write:

const HTTPResponses = [
  // ...
];

or:

const httpResponses = [
  // ...
];

When Should we Make Constant Names Upper Case?

We can make constant name supper case when the constant never changes.

Having them upper case means that we can trust to not change.

We should also make constant names upper case if they’re exported.

However, that should only apply to the top level.

For instance, we can write:

export const API_KEY = 'foo';

or:

export const FOO = {
  key: 'value'
};

Accessors

We don’t need accessor functions for properties.

Also, we shouldn’t use JavaScript getters or setters as they have unexpected side effects.

They also make our code harder to understand.

Instead, we make functions to get and set our values.

For instance, instead of writing:

class Person {
  get age() {
    // ...
  }

  set age(value) {
    // ...
  }
}

We write:

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

  setAge(value) {
    // ...
  }
}

Normal methods are easier to understand.

Is a Property or Method returns a Boolean, then we Prefix it with is or has

For instance, we write:

person.hasAge()

instead of:

person.age()

Now we know what the function does just by the name.

Creating get and set Functions

We can create get and set functions for getting and setting values.

For instance, we can write:

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

  set(key, val) {
    this[key] = val;
  }

  get(key) {
    return this[key];
  }
}

This way, we can get and set the values dynamically by their key and value.

Triggering Events

If we trigger events, we should pass in an object instead of a raw value.

For instance, we write:

const event = new CustomEvent("eventName", {
  "detail": "event"
});
document.dispatchEvent(event);

document.addEventListener("eventName", (e) => {
  console.log(e.detail);
});

Now we have an object as the value of detail instead of a primitive value.

If we always send an object with an event, then we won’t have to check if the object sent is a primitive value or an object.

jQuery

If we use jQuery , we should add a $ prefix so that we know it’s something returned by jQuery.

For instance, we can write:

const $main = $('.main');

Cache jQuery Lookups

If we look up something with jQuery, we should cache them so that we don’t have to look up the same thing repeatedly.

For instance, we can write:

const main = $('.main');
main.hide();
// ...

main.css({
  'background-color': 'green',
});

Use find with scoped jQuery Object Queries

We shouldn’t use find to make scoped queries.

Instead, we just use one CSS selector with one query:

$('.main ul').hide();

This way, we only do the lookup once rather than twice.

Arrow Functions

We should use arrow functions as much as we can for callbacks.

For instance, we write:

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

It’s much shorter than using traditional functions.

The value of this also doesn’t change inside it.

Skipping Braces

We can skip the braces and use implicit return if our function is one statement long.

The example above showed us that we can skip the braces and return something.

We wrote:

(x) => x + 1

which returns x plus 1.

Conclusion

To make things speedier when using jQuery, we should cache our queries.

Also, we shouldn’t use find to make scoped queries. Instead, use one CSS selector.

We should stick to standard JavaScript naming conventions.

And we should use the get and set keyword for accessors.

Categories
JavaScript Best Practices

JavaScript Best Practices — Documentation

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 practices for writing documentation with JSDoc.

Top/File-Level Comments

A file may name things like a copyright notice, author information in the comments.

We may also include the description of the file contents if it doesn’t repeat what’s in the code.

Class Comments

A class may have its own comments with the description and the parameters.

For instance, we can write:

/**
 * A class to store Fruit data
 */
class Fruit extends Food {
  /**
   * @param {string} name An argument for the name.
   * @param {Array<number>} dimensions List of numbers for the
   *    dimensions.
   */
  constructor(name, dimensions) {
    // ...
  }
};

We have a description of the class above the class.

And inside the class, we have a comment block to explain the items in the constructor.

Each parameter has its own explanation of what it holds and the type of them.

This way, we can use programs to help us create documentation if needed.

Enum Comments

We can write comments for an enum.

For instance, we can write:

/**
 * Types of weight units.
 * @enum {string}
 */
const WeightType = {
  GRAM: 'gram',
  POUND: 'type',
};

We added a comment for an enum that has a list of weight units.

The comment has an @enum tag to explain what each enum value’s type is.

In this example, they’re all strings.

Method and Function Comments

Like with classes and objects, we can add comments to methods.

For instance, we can write:

/** A class */
class FooClass extends BaseClass {
  /**
   * Operates on a FooClass instance and returns item.
   * @param {!FooClass} foo Some object.
   * @param {!OtherClass} other Another object.
   * @return {boolean} Whether something occurred.
   */
  doSomething(foo, other) { ... }

  /** @override */
  overriddenMethod(param) { ... }
}

In the code above, we have a comment for the clas at the top.

Then we have a comment block to describe what a method does.

In addition, we have an explosion of what each parameter can hold and the data type of them.

Also, we have the @override tag to explain that the method overrides a method of the parent class.

Likewise, we can have something similar in function.

For example, we can write:

/**
 * A function that makes an object.
 * @param {TYPE} arg
 * @return {!TYPE2}
 * @template TYPE
 */
function makeObject(arg) { ... }

We have a function that takes a given type of argument, so we write a comment to explain that.

Also, we have the @return tag to return some object of TYPE2 .

Property Comments

Also, we can add comments to properties.

For instance, we can write:

/** A class. */
class AClass {
  /** @param {string=} arg */
  constructor(arg = 'something') {
    /** @const {string} */
    this.arg = arg;
  }
}

We have a class that has a constructor with some comments.

We added a comment for the constructor’s argument.

Also, we have another one for the instance variable itself.

We indicated that it’s constant with the @const tag.

Type Annotations

We add data type annotations after @param , @return , @this , and @type tags.

Also, we can add them optionally on @const , @export and any visibility tags.

Nullability

The modifiers ! and ? denote non-nullable and nullable respectively.

Also, we add type annotations for primitives, including string , numbner , boolean , symbol , undefined , and null .

Literal types of annotations include functions:

{function(...): ...}

and objects:

{{foo: string...}}

Reference types are UpperCamelCase.

Template Parameter Types

We should specific parameters in types.

For instance, we write:

const /** !Array<string> */ names = [];

if our array is a string array.

Conclusion

We can add comments for parameters, constructors, classes, and object properties.

Information like the type of them, if applicable, what a function returns, etc. can be added.

On top of a file, we can put information like a copyright notice or something like that.

There are also symbols for indicating nullability.

Categories
JavaScript Best Practices

JavaScript Best Practices — Comparisons

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 switch statements and other comparisons statements.

Use Braces to Create Blocks in switch and case Statements

If we create blocks in switch and case statements, then we can enclose block-scoped variables.

So instead of writing:

switch (foo) {  
  case 1:  
    let x = 1;  
    break;  
  case 2:  
    const y = 2;  
    break;  
    //...  
}

We can write:

switch (foo) {  
  case 1: {  
    let x = 1;  
    break;  
  }  
  case 2: {  
    let x = 2;  
    break;  
  }  
  //...  
}

We can use x twice since x isn’t available outside the case block.

Terneries Shouldn’t be Nested

Ternary expressions shouldn’t be nested since they’re hard to read.

They should also be short.

For instance, instead of writing:

const foo = val1 === val2 ?  
  "foo" :  
  val3 > val4 ? "baz" : null;

As we can see, that’s hard to understand.

Therefore, we should write the following instead:

const foo = val1 === val2 ?  
  "foo" : "baz";

Avoid Useless Ternary Expressions

We shouldn’t have useless ternary expressions.

For instance, the following isn’t needed:

const baz = c ? false : true;

If c is a boolean, we can just assign it directly to baz .

If it’s not, we can use the Boolean function to convert it to a boolean.

So we can write:

const baz = !Boolean(c);

instead.

Enclosed Expressions with Mixed Operators in Parentheses

We should enclose expressions with mixed operators in parentheses to make them easier to read.

For instance, we shouldn’t write:

const foo = a && b < 5 || c < 3 || d + 1 === 0;

Instead, we should write:

const foo = ((a && b < 5) || (c < 3) || (d + 1)) === 0;

Now we can see the expressions clearly since they’re separated by parentheses.

Blocks

There are some things to consider when we’re creating blocks.

Use Braces with Multiline Blocks

If we have blocks, then we should dd braces whether they’re required or not.

For instance, instead of writing:

if (test)  
  return true;

We write:

if (test) {  
  return true;  
}

Likewise, with functions, instead of writing:

function foo() { return true; }

We write:

function foo() {   
  return true;   
}

If we’re Using Multiline Blocks with if and else, put else on the Same Line with if Block’s Closing Brace

For instance, instead of writing:

if (test) {  
  foo();  
  bar();  
}  
else {  
  baz();  
}

We write:

if (test) {  
  foo();  
  bar();  
} else {  
  baz();  
}

It saves some space without impacting readability.

If an if Block Always Runs a return Statement, the Subsequent else Block isn’t Necessary

If we have an if block that always returns, then the else block will never be run.

Therefore, we should remove the else block and put the return outside it.

For instance, instead of writing:

const foo = () => {  
  if (x) {  
    return x;  
  } else {  
    return y;  
  }  
}

We write:

const foo = () => {  
  if (x) {  
    return x;  
  }  
  return y;  
}

Control Statements

There are better ways to deal with control statements than others in JavaScript.

If Our Control Statements are too Long, then Each Condition Should be in a New Line

For instance, if we have:

if ((foo === 123 || bar === 'abc') && suprSuperSuperLong() && isSuprSuperSuperLong()) {  
  doSomething();  
}

Then we should tidy it up by writing:

if (  
  (foo === 123 || bar === 'abc') &&  
  suprSuperSuperLong() &&  
  isSuprSuperSuperLong()  
) {  
  doSomething();  
}

This is much easier to read than the first example.

No Selection Operator in Place of Control Statements

Using if is easier to understand than using the && operator to run something if a condition is true .

For instance, instead of writing:

canRun && run();

We write:

if (canRun) {  
  run();  
}

Conclusion

We should surround swiotchj and case statements with braces so that we can use block-scoped variables with them.

If we long boolean expressions, then we should break them into their own line.