Categories
JavaScript Best Practices

JavaScript Best Practices — Destructuring and Strings

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 right way to return results in our function and some best practices for strings.

Use Object Destructuring for Multiple Return Values Rather Than Array Destructuring

We should return objects in our functions if we have multiple items that we want to return so that when the position of the changes, we don’t have to change the code when the returned value is destructured somewhere else.

For instance, if we have a function that returns several items in an array as follows:

const foo = () => {
  const a = 1;
  const b = 2;
  const c = 3;
  return [a, b, c];
}

const [a, b, c] = foo();

Then code would break if we swap the position of the items in the array. For example, if we have the following code:

const foo = () => {
  const a = 1;
  const b = 2;
  const c = 3;
  return [a, c, b];
}

const [a, b, c] = foo();

In the code above, we switched the position of b and b in the return value of foo but we didn’t change the order that they’re destructured. Therefore, we’ll get results that we didn’t expect.

If we return an object, then we don’t have to worry about the position that each entry is returned in.

For instance, if we have the following code:

const foo = () => {
  const a = 1;
  const b = 2;
  const c = 3;
  return {
    a,
    b,
    c
  };
}

const {
  a,
  b,
  c
} = foo();

Then we can swap b and c within the object returned in foo and get the same result as before because objects are destructured by matching their property name and their nesting level rather than just the position as in arrays.

Use Single Quotes for Strings

Using single quotes saves us some typing since we don’t have to press the shift key to type it.

It does the same thing as double quotes as they both create regular strings.

Therefore, instead of writing the following:

const foo = "foo";

We write:

const foo = 'foo';

Strings That Cause the Line to Go Over 100 Characters Should Not Be Written Across Multiple Lines Using String Concatenation

If our string is over 100 lines long and it’s all in one line, then we should write it across multiple lines so that we don’t have to scroll horizontally to read the whole string.

What we should do is to break them up into chunks that are less than 100 characters longs, put them into an array, and then call the array instance’s join method together to join them together.

For instance, we can write the following code to do that:

const arr = [
  'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
  'Vivamus tincidunt magna at turpis maximus.',
  'Duis porttitor imperdiet justo ac molestie.'
]

const str = arr.join(' ');

In the code above, we have sentences that are 100 characters long or less and then we called the join method to join them together.

This is easier to read and manipulate since we don’t have lots of + signs or “ signs as in the following code:

const str = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.' +
  'Vivamus tincidunt magna at turpis maximus.' +
  'Duis porttitor imperdiet justo ac molestie.'

or:

const str = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.
  Vivamus tincidunt magna at turpis maximus.
  Duis porttitor imperdiet justo ac molestie'

Conclusion

When we return something in a function that can be destructured, then we should do that by returning objects. This way, the destructuring is done by matching the property name and the nesting level rather than the position if we return the result as an array.

Therefore, the destructuring code wouldn’t break if we move the object properties around as long as the property names don’t change.

If we’re defining regular strings, then we should use single quotes to save us from using the shift key.

Also, we should put multiple strings together into an array and use the array’s join method to join multiple strings together, if we have long strings.

Categories
JavaScript Best Practices

JavaScript Best Practices — Defining Functions and Calling Them

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 how the best way to define functions in our JavaScript code and call functions with a variable number of parameters.

Never Use the Function Constructor to Create a New function

In JavaScript, the Function constructor lets us define a function with strings for parameters and the function body.

All arguments but the last arguments are parameters, which are passed in as strings. The last argument is the function body, which is also passed in as strings.

The Function constructor returns a function that runs in the global scope only.

For instance, we can define a function as follows:

const add = new Function('a', 'b', 'return a + b');

In the code above, we called the Function constructor with the parameters a and b , which are defined as strings, and the function body return a + b .

This is bad because everything is defined as strings, which means that they’re hard to trace and debug. Also, there’s a chance that someone may pass in malicious strings into the Function constructor so that malicious code may run.

All returned functions also run in the global scope, which is probably not what we want.

Therefore, we shouldn’t run any code that uses the Function constructor.

Spacing in a Function Signature

Spacing in a function signature should be standardized and consistent. There should be no space after the function keyword or the function name. And there should be one closing parentheses and the opening curly brace.

For instance, we should write something like the following code:

function() {}
function add() {}

This way, we can read the name easily and the parentheses are easily readable as well.

Never Mutate Parameters

Mutating parameters is always bad. Since objects are passed by reference, they also mutate the arguments that are mutated outside.

For instance, if we write the following code:

const foo = (a) => a.foo = 2;
let a = {
  foo: 1
};
foo(a);
console.log(a);

Then in the code above, we have the a parameter in our foo function. Inside the function, we set a.foo to 2.

Then when we define the variable a with:

{
  foo: 1
}

Next when we call foo(a) , then we get that a is {foo: 2} because we mutate the parameter, which is propagated to the a variable itself because a is passed in by reference to the foo function.

What we should do instead is that we should assign the property’s value variable inside the function and then mutate that.

For instance, we can do the following:

const foo = (a) => {
  let bar = a.foo;
  bar = 2;
};

let a = {
  foo: 1
};
foo(a);
console.log(a);

This way, we get that a is:

{
  foo: 1
}

which is what we passed in originally. In the code above, we assigned a.foo to bar and set the value of bar to 2.

Prefer the Use of the Spread Operator ... to Call Functions with Variable Number of Parameters

With the introduction of the spread operator, we can use it to call functions with a variable number of parameters by calling it with an array that’s spread into arguments of the method by the spread operator.

This is much better than the old way, which is to call apply on the function to call a function with a variable number of parameters.

For instance, we can call the Math.max method to get the maximum number from an array of numbers as follows:

const max = Math.max(...[1, 2, 3]);

In the code above, we called Math.max with an array by using the spread operator. The spread operator spreads the array [1, 2, 3] into the arguments of the Math.max method,

Then we get 3 as the result of max .

This is much better than the old way, which is to use apply as follows:

const max = Math.max.apply(undefined, [1, 2, 3]);

In the code above, we called apply with 2 arguments, the first argument is the value of this , which can be anything since it’s a static function, but we chose undefined . The 2nd argument is the array of arguments that we want to pass into Math.max .

And then we get the same result. As we can see, it’s more a more complex way to get the same result.

Conclusion

We should never use the Function constructor to create a function. Also, we should use the spread operator to call functions that take a variable number of arguments rather than apply .

Categories
JavaScript Best Practices

JavaScript Best Practices — const and Destructuring

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 using const for declaring constants and using destructuring assignment syntax to assign property values and array entries to variables.

Using const to Declare Constants

The const keyword is one of the new JavaScript features that we should use often.

It lets us declare constants that can’t be reassigned to anything else its value has been set.

Also, we have to set it to a value when it’s defined so that it’s impossible for us to forget to assign it to something.

However, the object that it’s assigned to can still be mutated. For instance, we can still push on an array to add items to an array for example.

For instance, we can write the following code to declare a constant as follows:

const a = 1;

In the code above, we assigned a to 1. a is a constant, so we can’t assign it to any other value.

The is opposed to declaring variables with var or let , where we can assign the value to something else after it’s declared. For instance, we can write the following:

let a = 1;
a = 2;

or:

var b = 1;
b = 2;

If we reassign the value with something declared with const , we’ll get an error.

const can also be used with the destructuring assignment syntax, so we can declare multiple variables assign values to them all at once by using the syntax.

For instance, we can write the following:

const {
  a,
  b
} = {
  a: 1,
  b: 2
};

In the code above, we assigned a to 1 and b to 2 because the object on the right side has properties a and b with values 1 and 2 respectively.

a and b will be constants so they can’t be assigned to something else. If we do try to do that, we’ll get an error.

We can also do that with arrays as follows:

const [a, b] = [1, 2];

and get the same result as in the previous example.

Destructuring Arrays and Objects

The destructuring syntax in JavaScript is another syntax that we should use often.

It helps us destructure arrays and objects into variables. As we can see, from the examples in the previous section, we can destructure array entries and object properties into variables.

It’s easy to use and works with any nesting levels. We can destructure a nested array as follows:

const [
  [a], b
] = [
  [1], 2
];

In the code above, we destructured a nested array by putting the a inside the array on the left side. Then on the right side, we have 1 inside the array. Therefore, 1 will be assigned to a by the position that it’s located in the nested array.

Likewise, we can do the same thing with nested objects as follows:

const {
  a,
  b: {
    c
  }
} = {
  a: 1,
  b: {
    c: 2
  }
};

In the code above, we have the nested property c in the b property. By checking the position of c , the destructuring will be done because c on both sides have the same nesting level.

Therefore, it’s value will be assigned to c .

There’re more ways we can use the destructuring syntax. For instance, it’s very useful for destructuring properties and array entries in object parameters of functions.

We can destructure array entries in parameters into variables as follows:

const foo = ([a, b]) => a + b

In the code, we set the first 2 entries of the array parameter as values of variables a and b .

Then when we call it as follows:

foo([1, 2]);

Then we get 3 because a is 1 and b is 2.

Likewise, we can do that with object parameters. For instance, we can write the following:

const foo = ({
  a,
  b
}) => a + b

and we can call it as follows:

foo({
  a: 1,
  b: 2
})

and get the same return value of 3 because of the destructuring syntax applied to the object.

We can also use it in the for...of loop as follows:

for (const [key, value] of new Map([
    ['a', 1],
    ['b', 2]
  ])) {
  console.log(`${key} - ${value}`);
}

In the code above, we looped through a Map instance with the for...of loop and destructured the entries using the destructuring syntax into the key and value constants.

Therefore, we will see the items’ key and value logged.

Since the destructuring syntax is so convenient, we should use it everywhere.

Conclusion

We should use const to declare constants so that they can’t be reassigned to a different value accidentally.

The destructuring syntax is a very convenient way to assign array entries and object property values to variables directly. Therefore, we should use it everywhere.

Categories
JavaScript Best Practices

Maintainable JavaScript — Browser Detection

Creating maintainable JavaScript code is important if want to keep using the code.

In this article, we’ll look at the basics of creating maintainable JavaScript code by looking at how to detect browsers.

Browser Detection

Browser detection is a tricky topic.

There’s no perfect way to detect the actual browser.

User-Agent Detection

One way to detect the browser is user-agent detection.

The client or server can look at the user-agent string to determine the browser that made the request.

The browser’s user-agent string can be found in the navigation.userAgent property.

So we may think about writing something like:

if (navigator.userAgent.includes("MSIE")) {
  // ...
} else {
  // ...
}

We check for Internet Explorer by using the user-agent string.

The problem is that parsing the user-agent string is hard.

Also, browsers have copied each other to maintain compatibility.

With every new browser release, we need to update the user-agent detection code.

This added to the maintenance burden that we have to do.

User-agent string checking should be the last approach to check for the correct course of action.

If we check for user-agent strings, then we should only check the older browsers.

This is because newer browsers probably have the new features we want.

New features won’t be added to old browsers.

Feature Detection

Instead of checking the user-agent string, we should check whether the feature we want exists in the browser.

To do that, we can check for properties.

For instance, we can write:

if (typeof document.getElementById === 'function') {
  // ...
}

to check that document.getElementById is a function.

This way, we know exactly what we’re looking for exists or not.

It doesn’t rely on knowledge of which browser is used and only which features are available.

Therefore, it’s easy to support new browsers.

We should provide a logical fallback is there’s no feature available in the browser to provide the functionality that we want.

If we want to use newer features, then we’ve to different kinds of checks for different browsers’ implementations.

For instance, we may write:

function setAnimation(callback) {
  if (window.requestAnimationFrame) {
    return requestAnimationFrame(callback);
  } else if (window.mozRequestAnimationFrame) {
    return mozRequestAnimationFrame(callback);
  } else if (window.webkitRequestAnimationFrame) {
    return webkitRequestAnimationFrame(callback);
  } else if (window.oRequestAnimationFrame) {
    return oRequestAnimationFrame(callback);
  } else if (window.msRequestAnimationFrame) {
    return msRequestAnimationFrame(callback);
  } else {
    return setTimeout(callback, 0);
  }
}

to check each browser’s implementation of the requestAnimation method.

If none of the variants available, then we use the setTimeout method.

Avoid Feature Inference

We should assume that if one feature is available, then the related features are available.

For instance, we shouldn’t assume that if document.getElementsByTagName() is present, then document.getElementById() is present.

This also applies to any other kind of object.

Avoid Browser Inference

Also, we shouldn’t infer the browser from a given feature.

So we shouldn’t write something like:

if (document.all) {
  //...
} else {
  //...
}

to infer that is document.all exists, then IE is being used.

The code above isn’t an equivalent to:

const isIE = navigator.userAgent.includes("MSIE");
if (isIE) {
  //...
} else {
  //...
}

We can’t assume that if one feature exists, then our code is running on a given browser.

This is because the feature can be present in other browsers.

Conclusion

Browser detection is a tricky topic.

User-agent string detection isn’t very accurate.

But we can check for features to see if they’re present before we use them.

Categories
JavaScript Best Practices

Maintainable JavaScript — Removing Methods and Inheritance

Creating maintainable JavaScript code is important if want to keep using the code.

In this article, we’ll look at the basics of creating maintainable JavaScript code by looking at avoiding changing objects we don’t own.

Don’t Remove Methods

Removing methods from objects we didn’t create is also easy.

For instance, we can write:

document.getElementById = null;

Then we made document.getElementById null .

Now getElementById is no longer a function.

So we can’t call it.

Also, we cal delete properties with the delete operator.

delete operator works on regular objects, so we can write:

let person = {
  name: "james"
};
delete person.name;

and we remove the name property, so person.name would be undefined .

However, this operator has no effect on built-in library methods.

So if we write:

delete document.getElementById

that has no effect.

Removing existing methods is definitely a bad practice.

Developers expect the object to have methods described in the library documentation.

And existing code may be using the methods since everyone expected them to be there.

If we want to remove a method, then we should deprecate them so that they won’t be used for new code and will be removed from the existing code.

Then once they’re all gone, then we can remove it.

Removing would be the very last step.

Better Solutions

Modifying objects is definitely a bad idea.

We should adopt some common design patterns to avoid modifying objects we don’t own.

Object-Based Inheritance

One way to extend existing objects is to create a new object with whatever object we want as the prototype.

For instance, we can write:

const person = {
  name: "jane",
  sayName() {
    console.log(this.name);
  }
};

const student = Object.create(person);

We called Object.create with the person object as the prototype of the student object.

So we can call sayName on the student object since it’s inherited from person :

student.sayName();

Then we get 'jane' logged.

We can then define our own sayName method on student by writing:

const person = {
  name: "jane",
  sayName() {
    console.log(this.name);
  }
};

const student = Object.create(person);
student.sayName = function() {
  console.log('joe');
}

student.sayName()

Then we’ll see 'joe' logged instead of 'jane' .

Once the object is created, then we own it.

So we can do whatever we want with it without affecting other pieces of code and confusing people.

Type-Based Inheritance

Type-based inheritance works like object-based inheritance.

We inherit properties from a prototype.

But we create child classes or constructors instead of objects.

With ES6, we can create child classes by using the extends keyword.

For instance, we can create a subclass of Error by writing:

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

class Author extends Person {}

We created the Author class which is a subclass of Person .

The extends keyword indicates that it’s a subclass.

This lets us create objects flexibly.

We can define any new properties and methods in the subclass to extend the Person class.

Conclusion

We shouldn’t remove methods from objects we don’t own so that no one will be confused.

Also, we don’t want to break existing code.

We can extend objects and classes to create the objects and classes we want.