Categories
JavaScript Best Practices

JavaScript Best Practices — Objects, Functions, and Arrays

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.

Getter and Setter Pairs in Objects and Classes

We should have getter and setter pairs in our objects.

Without getters, properties can’t be read, so it’s not used.

For instance, instead of writing:

const obj = {
  set a(value) {
    this.val = value;
  }
};

We write:

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

  get a() {
    return this.val;
  }
};

Line Breaks After Opening and Before Closing Array Brackets

We may put line breaks before the opening and closing brackets.

It’s clearer to have them for long arrays

For instance, instead of writing:

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

We write:

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

Spaces Inside of Brackets

We can line without spaces inside brackets.

So we can write:

const arr = ['foo', 'bar'];

and it’s clear to read.

Return Statements in Callbacks of Array Methods

We always need to return something in array methods.

Methods like reduce , Array.from , filter , map , some , every , etc. return things in methods.

For instance, we shouldn’t write things like:

const indexes = arr.reduce((foo, item, index) => {
  foo[item] = index;
}, {});

or:

const foo = Array.from(elements, (element) => {
  if (element.tagName === "DIV") {
    return true;
  }
});

Instead, we should write:

const sum = arr.reduce((a, b) => a + b, 0);

We return the result of the combination of the values with reduce .

Or we can write:

const foo = Array.from(elements, (element) => {
  if (element.tagName === "DIV") {
    return true;
  }
  return false;
});

Some methods like forEach don’t need to return something since we can’t do much with the return value.

But we can return early to move to the next iteration:

arr.forEach((item) => {
  if (item < 0) {
    return;
  }
  doSomething(item);
});

Line Breaks Between Array Elements

We may put long expressions in our array.

For instance, we can write:

const arr = [
  function foo() {
    doWork();
  },
  function bar() {
    doWork();
  }
];

We have 2 functions which are long, so we put them in their own line.

Braces in Arrow Function Body

Braces in the arrow function body can help with increasing clarity.

For instance, we can write:

const foo = () => {
  return 0;
};

instead of writing:

const foo = () => 0;

We know we’re returning something for sure with the return keyword.

Parentheses in Arrow Function Arguments

Parentheses are useful for delimiting parameters.

For instance, we can write:

(a) => {}

to make reading parameters easier.

A Space Before or After Arrow Function’s Arrow

We should definitely put spaces between the arrow of an arrow function.

For instance, instead of writing:

(a)=>{}

or:

a=> a;

We write:

(a) => {}

Treat var as Block Scoped

If we use var to declare variables, they should be used as block-scoped variables.

For instance, we shouldn’t write something like:

function doIf() {
  if (true) {
    var build = true;
  }

  console.log(build);
}

or:

function doIfElse() {
  if (true) {
    var build = true;
  } else {
    var build = false;
  }
}

Instead, we write:

const doIf = () => {
  var build;

  if (true) {
    build = true;
  }

  console.log(build);
}

or:

const doIfElse = () => {
  var build;

  if (true) {
    build = true;
  } else {
    build = false;
  }
}

We shouldn’t use the non-block-scope features of var like hoisting or accessing var variables outside a block in a function.

They just trick lots of people.

Spaces Inside of Blocks After Opening Block and Before Closing Block

It’s good to have spaces inside blocks to make them readable.

For instance, instead of writing:

function foo() {return false;}

We write:

function foo() { return false; }

It’s just much clearer to have the spaces at the opening and closing.

Conclusion

Single spaces and extra rows are good for readability.

We should use array methods in the way they’re intended.

Categories
JavaScript Best Practices

JavaScript Best Practices — Mutations and Security

Like any kind of apps, JavaScript apps also have to be written well.

Otherwise, we run into all kinds of issues later on.

In this article, we’ll look at why and how to avoid mutations and complexity.

Also, we look at security issues.

Don’t use Proxy

Proxy adds side effects to object operation, so we may want to avoid them to reduce complexity.

For instance, instead of writing:

const handler = {
  get(target, key) {
    return Math.max(target[key], Infinity);
  }
};
const object = new Proxy(variable, handler);
object.a;

Instead, we create a function:

const bigProperty = (target, key) => {
  return Math.min(target[key], Infinity);
}
positiveProperty(object, 'a');

Reduce the use of Rest Parameters

Rest parameters are handy because we can pass in any number of arguments into a function.

However, it’s better to have explicit parameters since they’re easier to deal with.

A function with rest parameters doesn’t work well with currying since there is an indefinite number of parameters.

Therefore, instead of using:

const sum = (...nums) => {
  return nums.reduce((a, b) => a + b);
}

We write:

const sum = (nums) => {
  return nums.reduce((a, b) => a + b);
}

nums is an array in the 2nd example.

Having one fixed parameter is better than an indefinite number of arguments in an array.

Reduce the use of this

The use of this should be reduced since the use of this means we have a mutating internal state.

Also, this is confusing in JavaScript.

Therefore, it’ll be a mess if we use it. The value of this changes according to scope.

The use of throw

We shouldn’t use throw too much. It should be reserved for issues that we can’t resolve in our app.

It also should be used for the most critical errors so that everyone can see them right away.

In other cases, we may want to return data instead of in our function.

So instead of writing:

const throwAnError = () => {
  throw new Error('error');
}

We may write:

const returnAnError() {
  return new Error('error');
}

No Unused Expressions

We shouldn’t have any unused expressions in our code.

So things like:

1 + 2

shouldn’t be in our code since it’s useless.

Instead, we should assign expressions to a variable or pass them in as arguments of functions to use them.

So we can write:

const sum = 1 + 2;

No valueOf Fields

valueOf fields are useless since it just convert a value stored in an object into a primitive value.

This is a roundabout and confusing way to create primitive values.

Instead, we should just create primitive values instead.

So instead of writing:

const obj = {
  value: 200,
  valueOf() { return this.value; }
};

We just write:

const a = 200;

Always use new with Error

Error is a constructor, so we should use new with it.

In JavaScript, it’s easy to skip new if the class syntax isn’t used.

We’ll get unexpected results if we forgot it in other places.

So to be consistent, we should use new everywhere, including when we’re creating Error objects.

For instance, instead of writing:

throw Error('foo');

We write:

throw new Error('foo');

Security Risks

There are security risks with things like regular expressions.

We should be careful when using them so that they don’t break our apps.

Regular Expression DoS and Node.js

Node.js has issues with big regular expressions which are checked against long strings.

These big expressions block the event loop and so our app will be stalled until the code finishes running.

Since Node.js is mostly single-threaded, we’ll have issues with big regex checks.

Regex with some attributes like grouping with repetition creates more issues than others.

So if we have an email regex like:

/^([a-zA-Z0-9_.-])+@(([a-zA-Z0-9-])+.)+([a-zA-Z0-9]{2,4})+$/

which has many groups, then Node.js will be slow in checking strings against that regex.

Conclusion

We shouldn’t use proxies most of the time to simplify the code.

The use of this should always be reduced so as to reduce confusion and the chance of errors.

Also, we should check if a regex won’t stall our Node.js program before using it.

Categories
JavaScript Best Practices

JavaScript Best Practices — Functions and Mutations

Like any kind of apps, JavaScript apps also have to be written well. Otherwise, we run into all kinds of issues later on.

In this article, we’ll look at some best practices for working with arrow functions. Also, we look at why and how to avoid mutations.

Don’t Use Getters and Setters

Since getters and setters introduce side effects, we shouldn’t use them if we want to make functions that are easy to work with.

For instance, we shouldn’t write:

const person = {
  name: 'james',
  get age() {
    return this._age;
  },
  set age(n) {
    if (n < 0) {
      this._age = 0;
    } else if (n > 200) {
      this._age = 200;
    } else {
      this._age = n;
    }
  }
};

Also, we should call __defineGetter__ or __defineSetter__ :

person.__defineGetter__('name', () => {
  return this.name || 'james';
});

or:

person.__defineSetter__('name', (name) => {
  this.name = name.trim();
});

Instead, we create pure functions:

const restrictAge = (n, min, max) => {
  if (n <= min) {
    return min;
  }
  if (n >= max) {
    return max;
  }
  return n;
}

const setAge = (age, person) => {
  return Object.assign({}, person, { age: restrictAge(age, 0, 120 )});
}

This way, we have all pure functions, and it’s more versatile than our setter implementation since we can change the min and max .

Reduce the Use of Loops

Most loops in JavaScript code can be rewritten with array methods.

This is because there are already many methods like map , filter , and reduce methods that can let us work with arrays in a shorter way.

Therefore, instead of writing:

for (const a of arr) {
  result.push(a * 10);
}

We can write:

const result = arr.map(a => a  * 10);

It’s much easier to use map then a for-of loop.

Don’t Use Object.assign with a Variable as the First Argument

Object.assign mutates the first argument.

Therefore, to use it in a way that doesn’t mutate existing data, we should pass in an empty object as the first argument.

Instead of writing:

const a = { foo: 1, bar: 2 };
const b = { baz: 3 };

Object.assign(a, b);

we write:

const c = Object.assign({}, a, b);

Reduce the Use of Mutating Array Methods

Since we have the spread operator and alternative array methods, we can avoid the use of mutating array methods in our code.

For example, instead of writing:

arr.pop();

We can write:

const arr = arr.slice(0, arr.length - 1);

The only one that’s harder to replace is sort . In this case, we can make a copy of it with the spread operator and then call sort on that array.

So we can write:

const copy = [...arr];
copy.sort();

Instead of splice , we may consider using filter or slice .

If we want to remove one element with the given index, we can write:

const removed = arr.filter((a, i) => i !== index);

Then we remove the item at index index and assigned it to a new variable.

Reduce the Use of Mutating Operators

It’s easy to mutate data accidentally.

So we may want to avoid using mutating operators.

For instance, we may want to avoid using the += , /= , -= , %= or *= operators.

Instead, we can use non-mutating array methods or assigning values to a new variable.

We can write:

const b = a + 10;

instead of:

a += 10;

If we have to do update a variable with new values by adding repeatedly, we can use reduce :

const total = arr.reduce((total, a) => total + a, 0);

Before Careful with null or undefined

We should always be careful with null or undefined values since they are a source of many errors.

Therefore, we should make sure a variable or property isn’t null or undefined before working with them.

To check both, we can write:

if (val === null || val === undefined){
  //...
}

or:

if (val === null || typeof val === 'undefined'){
  //...
}

Conclusion

We should avoid mutating data so that we won’t accidentally change them.

It’s also easier to test function that doesn’t mutate data.

Also, we should be careful of null or undefined .

Categories
JavaScript Best Practices

JavaScript Best Practices — Arrow Functions

Like any kind of apps, JavaScript apps also have to be written well.

Otherwise, we run into all kinds of issues later on.

In this article, we’ll look at some best practices for working with arrow functions.

Placement of Arrow Functions

The placement of arrow functions is all about preferences.

We can put them at the top level.

Also, we can place them inside objects or functions.

This is all up to us.

So we can write:

const person = {
    getData: (id) => ajax(URL,{ id })
};

or:

export default id => person.getData(id)

or:

export const getPerson = id => person.getData(id)

or:

const getPerson = id => People.getData(id,onData)

We can place them at the top level, as exports or inside objects.

Returning Data

We should make sure that we write our return syntax properly.,

For instance, we can write:

const fn = prop => ( val => { return { [prop]: val }; } );

or:

const fn = (x, y) => {
  return (
    x > 3 ? x : y
  );
};

If we have an expression, we should put them in parentheses.

Also, we should add the return keyword unless our function only has one statement and only returns a short expression.

For instance, we can also write:

const cube = x => x ** 3;

This is also readable.

If we have arrow functions longer than that, then we should add parentheses, curly braces, and the return keyword.

The Use of this

We shouldn’t use this in callbacks that don’t need them.

For instance, if we have:

const squared = arr.map(a => a ** 2, this);

then we don’t need the this since it does nothing.

Arrow functions don’t have their own this , so this references whatever value of this is outside the function.

Therefore, this shouldn’t be at the top level since it’s going to be undefined under strict mode.

So having something like:

a => this.bar(a);

at the top level wouldn’t work.

Don’t Use the arguments Keyword

We should never use the arguments keyword.

It’s used to get all the arguments passed into a traditional function.

It doesn’t with arrow functions.

Also, it’s an array-like iterable object so it doesn’t have any array methods.

Therefore, instead of using:

function sum() {
  const numbers = [...arguments];
  return numbers.reduce((a, b) => a + b);
}

We can use the rest operator instead by writing:

const sum = (...numbers) => {
  return numbers.reduce((a, b) => a + b);
}

numbers would be an array, so we can call reduce on it.

The rest operator saves us typing and headaches.

Use of class

We can use class for constructors.

Ideally, we minimize the use of them since they hold internal state and have methods that commit side effects.

Therefore, whenever we can, we use pure functions.

So we can write:

const rectangle = (height, width) => {
  return {
    height: height,
    width: width
  };
}

Instead of:

class Rectangle{
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
}

Don’t Use the delete Keyword

The delete keyword removes a property from an object.

This means the object is mutated.

Therefore, it’s better to make a copy of an object without a property that we want.

Instead of writing:

delete foo.bar;

We can use something like Lodash to make a new object without a property:

const _ = require('lodash/fp');

const fooWithoutBar = _.omit('bar', foo);

Don’t Use the events Module

If we want to stick with functional programming, then we shouldn’t use the Node’s events module.

It lets us make side effects by emitting and listening to events.

Instead, we can create pure functions to pass around data.

Conclusion

Writing pure functions makes our lives easier since they don’t commit side effects.

Also, we should be careful about how we return data in arrow functions.

Categories
JavaScript Best Practices

JavaScript Best Practices — Arrays and Arrow Functions

Like any kind of apps, JavaScript apps also have to be written well. Otherwise, we run into all kinds of issues later on.

In this article, we’ll look at some best practices for working with arrays and arrow functions.

Use map to Map Items

The map method is great for mapping array entries from the original to something else.

This way, we don’t need to use loops to do it.

So instead of writing:

for (const a of arr) {
  cubed.push(a ** 3);
}

We write:

const cubed = arr.map(a => a ** 3);

This is much shorter and clean.

No Useless this Argument

We shouldn’t reference thsis if we don’t need to do it. For instance, we shouldn’t reference it in an arrow function when it’s used as a callback.

We should write something like:

const cubed = arr.map(a => a ** 3);

instead of:

const cubed = arr.map(a => a ** 3, this);

or:

const containsE = array.some((char) => char === 'e', this);

Avoid Reverse

We should avoid using reverse if we aren’t using it to reversing an array.

If we’re just trying to combine results from the end of the array to the left, we can use reduceRight instead.

For example, instead of writing:

const sum = array.reverse().reduce((total, c) => total + c, 0);

We should write:

const reverseSum = array.reduceRight((total, c) => total + c, 0);

We skipped the reverse operation and did the same thing.

This means we write less code and the code is faster because we skipped one operation.

Use Flat Map

The latest version of JavaScript has a flatMap method to map array items and reduce the nesting of the array by 1 level. This is better than using map and flat together.

For example, instead of writing:

const flattenedAndMapped = array.map((a) => a).flat();

We can write:

const oneAction = array.flatMap((b) => b);

If we don’t need to map, we can also use flat :

const flattened = array.flat();

Use Flat

To flatten an array, we don’t need to use reduce or concat anymore. This is because array instances now have the flat method to reduce nesting.

For instance, instead of writing:

const concatFlattened = [].concat(...array);

or:

const reduceFlattened = array.reduce((p, n) => p.concat(n), []);

We instead write:

const flattened = array.flat();

No Unused Parameters

We shouldn’t have unused parameters in our arrow functions.

Since they aren’t used, we should remove them.

For instance, we write:

const fn = (data, user) => request(user.id, data);

instead of writing:

const fn = (data, user) => request(user.id);

data wasn’t used in the example above, so we should take it out.

Number of Parameters

We shouldn’t have too many parameters. The more parameters we have, the harder for us to keep track of them.

We may pass arguments in the wrong place and it’s also easy to pass in the wrong type of data.

Ideally, we have 3 parameters or less in our functions.

For example, good functions are:

const fn0 = () => "";

or:

const fn3 = (one, two, three) => one * two * three;

But the following isn’t good:

const fn3 = (a, b, c, d) => a * b * c * d;

If we need more than that, then we can pass in an object or use the rest operator.

For example, we can write:

const fn = ({ a, b, c, d }) => a * b * c * d;

We used the destructuring syntax to destructure the properties in an object.

Then we can pass in as many properties as we want in an object.

Also, we can use the rest operator as follows:

const fn = (a, b, c, ...d) => a * b * c * d[0];

d would be a parameter that has arguments in the 4th position and beyond store in an array.

Naming Functions

If we need to use arrow functions in multiple places, then we should name them by assigning them to a variable.

For instance, we can write:

cosnt fn = (a, b) => a ** b;

Now we can call it by writing fn(1, 2) .

We can also put them into an object:

const obj = {
  fn: (a, b) => a ** b;
}

Then we can call it by writing obj.fn(1, 2) ;

Conclusion

There are many methods in array instances that we can use to manipulate them.

Also, there are better ways to define arrow functions and use them.