Sam Walpole
DevDoc

DevDoc

My Top 5 JavaScript Tips & Tricks for Writing Cleaner Code

My Top 5 JavaScript Tips & Tricks for Writing Cleaner Code

Everyday techniques you can use to tidy up your code & make it easier to read

Sam Walpole's photo
Sam Walpole

Published on Aug 8, 2021

8 min read

Subscribe to my newsletter and never miss my upcoming articles

1. Destructuring Assignment

Destructuring assignment allows one or more object properties to be assigned to variables in a single expression. The created variable will have the same name as the property.

let myObj = {
  id: 1,
  name: 'My Object'
};

// without destructuring assignment
let id = myObj.id;
let name = myObj.name; 
// id = 1, name = 'My Object'

// with destructuring assignment
let { id, name } = myObj;
// id = 1, name = 'My Object'

This is most useful when you know that you need to use multiple properties from an object, you need to use the same property multiple times, or the property the you wish to use is deeply nested in that object. In all these cases, using destructuring assignment saves you from all the clutter of getting the object properties through chaining and makes your code more concise and easier to read.

For example, I have recently been working a lot with Leaflet, a Javascript framework for building interactive maps. It is highly customisable and allows you to assign your own properties to different markers on the map. However, accessing these properties can get somewhat messy - we can clean this up with destructuring assignment.

// without destructuring assignment
function onEachFeature (feature, layer) {
  if (feature.properties.hasPopup) {
    let popupContent = `<a href="/feature/${feature.properties.id}">${feature.properties.name}</a>`;
    layer.bindPopup(popupContent);
  }
}

// with destructuring assignment
function onEachFeature (feature, layer) {
  let { hasPopup, id, name } = feature.properties;

  if (hasPopup) {
    let popupContent = `<a href="/feature/${id}">${name}</a>`;
    layer.bindPopup(popupContent);
  }
}

We may have added an additional line of code, but I believe this makes it much clearer and easier to read the intention of this function now.

It is also possible to destructure arrays, which allows you to assign one or more elements in that array to variables. However, I do not personally find myself using this syntax often so I wont cover it any further here. If you wish to learn more, please see the MDN reference.

Finally, if you are using a function that has an object as a parameter, it is possible to destructure that object within the parameter list. This saves you the effort of having to explicitly declare the variables yourself, and makes it clear which properties are required by the function.

function logPerson(person) {
  let { name, age } = options;

  console.log(`${name} is ${age} years old`);
}

function logPerson({ name, age }) {
  console.log(`${name} is ${age} years old`);
}

2. Short Circuit Evaluation & Assignment

The JavaScript logical operators, AND (&&) and OR (||) are known as short circuit operators because they only evaluate the expression as far as necessary in order to determine the result of the boolean expression.

For example, AND requires that both sides of the expression evaluate to true. Therefore, if the left-hand side of the expression evaluates to false, it does not bother to check the right-hand side as it would be a waste of time.

Similarly, OR requires that only one side off the expression evaluates to true. Therefore, if the left-hand side evaluates to true, it doesn't bother to check the right-hand side.

This short circuiting can be useful for adding some safety to expressions involving objects. For example, consider the following function:

function logIfAdult(person) {
  if(person.age >= 18) {
    console.log("Person is an adult");
  }
}

The problem with this implementation is that you cannot guarantee that the person object is not null. If you run this function with a null person, you will get the following error: Uncaught TypeError: Cannot read property 'age' of null.

Thanks to short circuit evaluation, we can add some safety like this:

function logIfAdult(person) {
  if(person && person.age >= 18) {
    console.log("Person is an adult");
  }
}

This is because, if person is null it will evaluate to false (this is because null is a "falsey" value, if this concept is new to you, please read this article too), and the whole expression will short circuit. Only if person is not null will the expression move on to check the right-hand side of the expression, at which point we know it is safe to check and we wont get any errors.

We can exploit this short circuiting when assigning variables too. For example, consider the following function:

function logName(person) {
  let name = person && person.name;
  console.log(name);
}

logName({ name: 'Sam' });
// logs 'Sam'

logName(null)
// logs 'null'

What is happening here? Well in the first example, we pass the function a valid person object. Because the person object is not null, the AND operator moves over to the right-hand side of the expression, and assigns the value of person.name to the name variable. In the second example, person is null so the expression short circuits and returns null to the name variable.

We can extend this further to log a default name instead of just null. This time we use the OR operator, so we will only use the default value if the person object is null.

function logName(person) {
  let name = person && person.name || 'Default Name';
  console.log(name);
}

logName({ name: 'Sam' });
// logs 'Sam'

logName(null)
// logs 'Default Name'

3. Optional Chaining & Nullish Coalescing Operator

Short circuit evaluation and assignment is so common that new more concise syntax is being added in to JavaScript to achieve the same aim. These are the optional chaining and nullish coalescing operators. I have decided to include both short circuiting and optional chaining/null coalescing since, at the time of writing, the latter are newer features and may not be fully compatible with older browsers.

The optional chaining operator (?.) allows you to dive into objects without explicitly having to check if the object is not null. If the object is null, then the expression will just return undefined instead of throwing an error. For example, with optional chaining, the logIfAdult function from above can be rewritten as:

function logIfAdult(person) {
  if(person?.age >= 18) {
    console.log("Person is an adult");
  }
}

The nullish coalescing operator (??) is used to return a default value if the value on the left-hand side of the expression is null. In this way, it replaces the functionality of the OR operator in the logName function above:

function logName(person) {
  let name = person?.name ?? 'Default Name';
  console.log(name);
}

4. Named Callback Functions

Anonymous functions can be really useful - they can be declared when and where you want, and are great if you only need the function as a one-off.

let people = [
  {
    id: 1,
    firstName: 'Sam',
    lastName: 'Walpole',
  },
  ...
];

let viewModels = people.map(p => ({
  id: p.id,
  name: `${p.firstName} ${p.lastName}`,
}));
// viewModels = [{ id: 1, name: 'Sam Walpole' }]

However, since the function has no name, you are leaving it up to future developers to work out what the code inside your callback function does - that's ok here, but in a longer, more complex functions it may waste unnecessary time. By declaring the function first as a named function, you instantly make the code more readable and give future developers some clues as to the intent of the function.

let people = [
  {
    id: 1,
    firstName: 'Sam',
    lastName: 'Walpole',
  },
  ...
];

let toViewModel = p => ({
  id: p.id,
  name: `${p.firstName} ${p.lastName}`,
});

let viewModels = people.map(toViewModel);
// viewModels = [{ id: 1, name: 'Sam Walpole' }]

5. Enums/Dictionaries

An enum is a way to store a set of constant values as a type. Most languages have in built support for enums, but in JavaScript we have to construct them ourselves using an object.

const Color = {
  RED: 'RED',
  GREEN: 'GREEN',
  BLUE: 'BLUE',
};

let redCar = {
  make: 'Ferrari',
  model: '812',
  color: Color.RED,
};

let greenCar = {
  make: 'Aston Martin',
  model: 'Vantage',
  color: Color.GREEN, 
};

Enums pair nicely with switch statements for flow control:

function getHexColor(car) {
  switch (car.color) {
    case Color.RED:
      return '#ff0000';
    case Color.GREEN:
      return '#00ff00';
    case Color.BLUE:
      return '#0000ff';
  }
}

However, sometimes this can be a bit verbose. Instead of using a switch statement here, we could use a dictionary. Dictionaries in JavaScript are declared in a very similar way to enums, but conceptually the have a different purpose. Where enums are a set of constant values, dictionaries are a collection of key/value pairs.

function getHexColor(car) {
  let hexColors= {
    [Color.RED]: '#ff0000',
    [Color.GREEN]: '#00ff00',
    [Color.BLUE]: '#0000ff',
  };

  return hexColors[car.color];
}

In the above example, we have got rid of the need for a switch statement, as we have created a dictionary with the enum values as the key, and the hex colors as the values. By removing all the clutter of the switch statement, I believe that this leads to much easier to read code.

Conclusion

In this article I have provided 5 tips that I regularly use in JavaScript to make my code more concise and easy to read. I hope that you have found them helpful and will find the opportunities to use them in your own code.

I post mostly about full stack .NET and Vue web development. To make sure that you don't miss out on any posts, please follow this blog and subscribe to my newsletter. If you found this post helpful, please like it and share it. You can also find me on Twitter.

Did you find this article valuable?

Support Sam Walpole by becoming a sponsor. Any amount is appreciated!

Learn more about Hashnode Sponsors
 
Share this