Categories
JavaScript Best Practices

Better JavaScript — Dictionaries

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 ways to improve our JavaScript code.

Build Dictionaries From Objects

Dictionaries are data structures with key-value pairs.

JavaScript objects can have key-value pairs added and removed dynamically.

So we can add items and remove them.

For instance, we can write:

const dict = {
  james: 33,
  bob: 22,
  mary: 41
};

Then we can loop through the entries in various ways.

We can use the for-in loop to loop through the items:

for (const name in dict) {
  console.log(name, dict[name]);
}

We can get the keys with Object.keys by writing:

for (const name of Object.keys(dict)) {
  console.log(name, dict[name]);
}

We switched in with of .

Also, we can get all the entries with Object.entries() :

for (const [name, age] of Object.entries(dict)) {
  console.log(name, age);
}

We destructured the key-value pairs in the loop heading.

Every object inherits from the Object.prototype , so we get methods like toString and other properties from it.

Object.keys , Object.entries only loop through own properties so this won’t be a problem.

The delete operator lets us delete properties from an object.

So we can write:

delete james

to remove the james property.

We shouldn’t use arrays to represent dictionaries.

Arrays can have properties since they’re also objects.

But that’s the wrong way to use arrays.

For instance, if we have:

const dict = new Array();
dict.james = 34;
dict.bob = 24;
dict.mary = 62;

then we shouldn’t use it as an object.

Some languages like PHP have associative arrays which are dictionaries.

Use null Prototypes to Provent Prototype Pollution

The prototype of an object is Object.prototype by default.

So if we have:

Object.getPrototypeOf({})

We get the Object.prototype returned.

To create an object with an null prototype, we can pass in an Object.create method with the null prototype.

For instance, we can write:

const obj = Object.create(null);

Then Object.getPrototypeOf(obj) is null .

We can also set the __proto__ property of an object by writing:

const obj = { __proto__: null };

This is a standard property since ES6 for setting the prototype so we can use it to set the prototype to null .

Use hasOwnPropety to Protect Against Prototype Pollution

The in operator can check if an object has a given property.

It also checks the prototype for such properties.

For instance, if we have:

const dict = {
  james: 33,
  bob: 22,
  mary: 41
};

Then if we have:

console.log('james' in dict);
console.log('toString' in dict);

both log true .

toString is inherited from Object.prototype .

We can use the hasOwnProperty to check only for non-inherited properties.

For instance, we can write:

dict.hasOwnProperty("james");

then that returns true .

But if we have:

dict.hasOwnProperty("toString");

that returns false .

Conclusion

The in operator checks if a property is in the object and its prototype.

hasOwnProperty only checks for non-inherited properties.

Objects are handy for creating dictionaries.

Categories
JavaScript Best Practices

Better JavaScript — Destructuring, Hoisting, and Closures

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 ways to improve our JavaScript code.

Use Destructuring to Extract Properties to Variables

If we want to extract properties to variables, we can use the destructuring assignment syntax.

For instance, we can write:

function foo(x, y) {
  const {
    min,
    round,
    sqrt
  } = Math;
  return min(round(x), sqrt(y));
}

to get the min , round , and sqrt methods from the Math object.

Then we can call the methods by using the variables instead of referencing Math repeatedly.

Getting Comfortable with Closures

Closures are something that we may not familiar with.

It may seem intimidating when we first work with them.

However, it’s actually simple.

Closures are just functions that are inside functions.

The inner functions can refer to variables in their parent’s scope.

For instance, we can write:

function makePie() {
  const ingredient = "peanut butter";

  function make(filling) {
    return `${ingredient} and ${filling}`;
  }
  return make("jelly");
}

We have a make function that takes the filling parameter and we return something after combining it with the ingredient from its parent function.

This means that we can keep private variables in functions and then access them from the inner function.

Also, we can return functions as variables in JavaScript.

For instance, we can write:

function makePie() {
  const ingredient = "peanut butter";

  function make(filling) {
    return `${ingredient} and ${filling}`;
  }
  return make;
}

and return the make function.

Then we can use it to create a new function by writing”

const f = makePie();
f('banana cream');

Then we get 'peanut butter and banana cream’ from the returned f function call.

We can shorten the makePie function more by writing:

function makePie() {
  const ingredient = "peanut butter";

  return function(filling) {
    return `${ingredient} and ${filling}`;
  }
}

We return an anonymous function instead.

JavaScript functions don’t have to have a name.

Closures can update the values of outer variables.

For instance, we can write:

function foo() {
  let val;
  return {
    set(newVal) {
      val = newVal;
    },
    get() {
      return val;
    },
    type() {
      return typeof val;
    }
  };
}

We created the foo function and returned an object with the set , get and type methods.

set sets the value of val .

get returns the value of val .

And type return the data type of val .

val is outside of the object, but it’s accessible by the object since it’s in its parent’s scope.

Each of these closures shares access to val .

Variable Hoisting

Variable hoisting is where a variable’s declaration can be accessed before the variable is defined.

This only applies to variables declared with var .

let and const variables are block-scoped and aren’t hoisted.

For instance, we can see a hoisted variable by writing:

console.log(foo);
var foo = 1;

foo is undefined but it can be accessed.

To avoid this, we should just use let and const variables.

Conclusion

We should use destructuring syntax to extract properties from objects.

Closures are something that we may use sometimes.

They’re functions that are in functions.

Variable hoisting is something that we may encounter but it’s less and less common.

Categories
JavaScript Best Practices

Better JavaScript — Defining 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 ways to improve our JavaScript code.

Use Immediately Invoked Function Expressions to Create Local Scope

Immediately Invoked Function Expressions or IIFEs for short, are functions that are created and called immediately.

For instance, we can create one by writing:

(function(){})();

We create the function and we can put whatever want to keep private inside.

It was used to get around JavaScript’s lack of block-scoped variables before ES6.

But now we need this less because we have modules to keep things private.

And we can define block-scoped variables with let and const .

If we use it in a block, then we may see some issues.

For instance, if we have a loop like:

function foo(a) {
  var result = [];
  for (var i = 0; i < a.length; i++) {
    (function(j) {
      result[i] = () => a[j]
    })(i);
  }
  return result;
}

Then we can’t use break or continue in the IIFE.

The this inside the IIFE would also change because of the traditional function used for the IIFE.

So we can do away with the IIFE, we change the var s th let and remove the IIFE:

function wrapElements(a) {
  let result = [];
  for (let i = 0; i < a.length; i++) {
    result[i] = () => a[i]
  }
  return result;
}

Since let is block-scoped the value of i will update and passed into the arrow function we set for result[i] in each iteration.

Unportable Scoping of Named Function Expressions

The meaning of the function can change depending on the context even though the function’s code looks the same.

For instance, if we have:

function times(x) {
  return x * 5;
}

then that’s a function declaration.

On the other hand, if we have:

const times = function(x) {
  return x * 5;
}

then that’s a function expression.

A function expression is where we define with the function keyword and didn’t assign that to a variable.

If we assign that to a variable, then it becomes a function expression.

We can also write:

const f = function times(x) {
  return x * 5;
}

to define a function expression

The difference between anonymous and named function expression is that the latter binds its name to the local variable within a function.

For instance, we can write:

const traverse = function find(tree, key) {
  if (!tree) {
    return null;
  }
  if (tree.key === key) {
    return tree.value;
  }
  return find(tree.left, key) ||
    find(tree.right, key);
};

We call find in the find function.

But when we call it outside, we call it with traverse .

Since we can just call traverse to do the same thing, having a name in the function’s scope isn’t very useful.

We can also use a function declaration like:

function find(tree, key) {
  if (!tree) {
    return null;
  }
  if (tree.key === key) {
    return tree.value;
  }
  return find(tree.left, key) ||
    find(tree.right, key);
}

However, named function expressions are useful for debugging.,

Most modern JavaScript environments produce stack traces with Error instances and the name will be used for the stack trace.

Conclusion

IIFEs have some uses but not as much as before.

Also, there’re several ways to define a function and they do slightly different things.

Categories
JavaScript Best Practices

Better JavaScript — Class Inheritance

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 ways to improve our JavaScript code.

Call Superclass Constructors from Subclass Constructors

To create a constructor that inherits from a parent constructor, then we need to call the superclass constructor within the subclass constructor.

For instance, we can write:

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

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

function Actor(name, role) {
  Person.call(this, name);
  this.role = role;
}

Actor.prototype = Object.create(Person.prototype);
Actor.prototype.constructor = Actor;

We create the Person constructor that servers the superclass constructor.

The instance methods are in the prototype property.

The Actor subclass is created by calling the Person constructor and then set its own state.

Then we create the prototype by using Object.create so that all the methods from Person are inherited.

Then we set the constructor to Actor so that if we use instanceof with an Actor instance like:

new Actor('james', 'main') instanceof Actor

Then that should return true .

We can call Person methods in an Actor instance because of the inheritance.

And we can override them if we wish.

So if we have:

const str = new Actor('james', 'main').toString()

that works and we get 'Person james’ as the value of str .

A less error-prone way to create superclasses and subclasses is using the class syntax.

For instance, we can write:

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

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

class Actor extends Person {
  constructor(name, role) {
    super(name);
    this.role = role;
  }
}

instead of what we had before.

They’re the same.

It’s just that the class version is shorter and way less chance of making mistakes.

super replaces the Person.call method call.

extends is the same as using Object.create and setting the constructor property.

So the class syntax should be used since it’s introduced, but we’ve to know that underneath, is still prototypes and constructors.

Never Reuse Superclass Property Names

We shouldn’t use superclass property names.

For instance, if we have:

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

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

function Actor(name, role) {
  Person.call(this, name);
  this.name = name;
  this.role = role;
}
Actor.prototype = Object.create(Person.prototype);
Actor.prototype.constructor = Actor;

then we used name in both Actor and Person .

This will create conflict between the 2.

Subclasses should be aware of all properties used by superclasses.

So we shouldn’t redefine properties that are in the superclass.

With the class syntax, we can just use the super object to refer to the instance properties of a superclass.

Conclusion

The class syntax lets us avoid many mistakes that we can create with subclass and superclasses.

Also, we shouldn’t have the same instance property names in a subclass and superclass.

Categories
JavaScript Best Practices

Adopting Better JavaScript Code Style — Error Handling and Comparisons

The way we write JavaScript can always be improved.

In this article, we’ll look at how to improve our code styles by improving the basic syntax we use in our code.

Error Handling

Error handling should be kept as simple as possible. If we encounter an error when running a function, we should throw an error.

For instance, we can throw an error if we encounter an invalid value as follows:

const greet = (name) => {
  if (typeof name !== 'string') {
    throw new Error('name not provided');
  }
  return `hi ${name}`;
}

In the code above, we check if name is a string. If it isn’t, then we throw an error.

Otherwise, we return the string with the greeting.

This is better than the alternative, which is returning error values:

const greet = (name) => {
  if (typeof name !== 'string') {
    return '';
  }
  return `hi ${name}`;
}

In the code above, we returned an empty string instead of throwing an error, so we hid the error from the developer using the function and the user.

It’s very easy to overlook error values since they aren’t immediately obvious. Unlike errors, which will show in the console immediately, we don’t see error values until we’re debugging.

This makes debugging harder since error values don’t show in the console.

Then if we need to handle errors, we can use the try...catch block as follows:

const greet = (name) => {
  if (typeof name !== 'string') {
    throw new Error('name not provided');
  }
  return `hi ${name}`;
}

try {
  greet();
} catch (ex) {
  console.log(ex);
}

In the code above, we have the following try...catch block:

try {
  greet();
} catch (ex) {
  console.log(ex);
}

Then since we didn’t pass in any argument into the greet function when we call it, we get an error in the console from the catch block.

To handle it better, we can choose to display an error message or something like that.

For async code, we should use promises as much as possible. Promises are rejected if errors are encountered, and like throwing errors in synchronous code, they also show up in the console.

For instance, if we have a promise that’s rejected as follows:

Promise.reject('error');

We can call catch on it to catch the error and handle it as follows:

Promise.reject('error')
  .catch(err => console.log(err));

In the code above, we called the catch method and in the catch method call, we passed in a callback to log the error.

Then we can get see errors and handle them in async code easily.

With the async and await syntax, we can catch errors from rejected promises as follows:

(async () => {
  try {
    await Promise.reject('error');
  } catch (err) {
    console.log(err);
  }
})();

As we can see, we just use try...catch like we did with synchronous code if we use the async and await syntax.

Comparison

=== and !== are always better than == and != .

This is because === and !== do not coerce the data types of their operands before doing the comparison.

On the other hand, == and != do data type coercion on their operands before doing any comparison.

By skipping typing one character, we introduced lots of headaches for ourselves because of the automatic type coercion.

Therefore, we should always just type the extra = sign and reduce the chances of bugs significantly.

To keep conditional expressions simple, we should use shortcuts as much as possible.

For instance, the following is bad:

if (condition === true) {
  // ...
}

This is bad because we don’t need to compare against true to check if something is true .

Instead, we should write:

if (condition) {
  // ...
}

Then we check if condition is truthy or not, which includes the value true .

If we use ternary expressions, we shouldn’t nest them. For instance, the following is good:

const foo = cond ? 'foo' : 'bar';

This is because we only have one condition cond and return 'foo' is cond is true and 'bar' otherwise.

However, we shouldn’t nest multiple ternary expressions together. So the following is bad:

const foo = cond1 ? cond2 ? 'foo' : 'bar' : 'baz';

Nesting makes the expression hard to understand. We’ve to put in the parentheses in our brains to compute the value in our heads so that we can understand what it’s doing.

Conclusion

Error handling is nest done by throwing errors and then handling them by catching them.

For async code, we can reject promises and then call catch or use the catch block to handle errors.

For conditionals, we should use the shortest way if we can. We also shouldn’t nest ternary expressions.

To do comparisons, we should use === and !== for comparisons so that we don’t have to worry about type coercions.