Categories
JavaScript Best Practices

JavaScript Best Practices for Writing More Robust Code — Doing Checks

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 how to do checks in our app to only run things when the given condition is true or false.

Types of Checks We Should Do

If we want our JavaScript program to be robust. Then there’re a few kinds of checks that we must to in our app.

There’re 3 kinds of checks. They’re the following:

  • Existence check — checking if something is available
  • Type check — checking if the data type is correct or if a property exists
  • Value check — checking if something has the value that we’re looking for

Conditional Statements

We can check for values and run code accordingly with conditional statements. There’re a few types of conditional statements in JavaScript. They include if statements, switch statements, and ternary operators.

if statements are the most versatile kind of conditional statements. The code is run if the expression inside the if statement is truthy.

Truthy expressions are the expressions that evaluate to true when they’re coerced to a boolean. Falsy expressions are ones that evaluate to false when they’re coerced to a boolean.

Falsy values in JavaScript are 0, false , '' (empty string), undefined , null , and NaN . Everything else is truthy.

For instance, we can write the following if statement:

if (foo === 1) {  
  //...  
} else if (foo === 2) {  
  //...  
} else {  
  //...  
}

In the code above, we have several checks in our if statement. First, we check if foo is 1 with the === operator and run something if it’s true .

Otherwise, we check if foo is 2, then we run something if that’s true .

In all other cases, as indicated by the else block at the end, we run something if foo isn’t 1 or 2.

When checking for things, we should use the === operator to do the checks. If we want to check if something isn’t equal to something, then we use the !== operator.

This is because they don’t do type coercion before doing the comparison.

Switch Statements

The kind of comparison above can also be done with the switch statement.

For instance, we can rewrite our example with the switch statement as follows:

switch (foo) {  
  case 1: {  
    //...  
    break;  
  }  
  case 2: {  
    //...  
    break;  
  }  
  default: {  
    //...  
    break;  
  }  
}

In the code above, we have switch (foo) , which indicates that we check foo for the given value. In this case, we check for 1 and 2 as indicated by the case blocks. They are used for running code if foo is 1 or 2 respectively.

We enclose the case statements in blocks so that we don’t have to worry about block-scoped variables clashing with other block-scoped variables that have the same name if we want to reuse the name.

It’s also important that we the break; statement at the end of each block so that the code for the other cases won’t run and only the block for the case that matches will run.

The default block is for running code when foo isn’t 1 or 2, or doesn’t match the cases given in the case blocks in general.

This is like the else block that we have in the if statement example.

Ternary Operator

The ternary operator is useful for writing condition checks that only have 2 cases, and that we want to return something for one case and something else for another.

The operator is denoted by the ? , and we distinguish the cases with the : operator.

For instance, we can use it as follows:

const foo = 1;  
const x = (foo === 1 ? 'foo' : 'bar');

In the code above, we have the expression:

(foo === 1 ? 'foo' : 'bar')

which uses the ternary operator. This expression means that if foo is 1, then we return 'foo' . Otherwise, we return 'bar' .

Therefore, since foo is 1, 'foo' is returned, and we assign that to x .

Conclusion

We should do several kinds of checks in our app to make sure that our app runs correctly and only does things when we have the data we’re looking for.

To do the checks, we should use conditional statements like if statements, switch statements, or ternary expressions.

Categories
JavaScript Best Practices

JavaScript Best Practices — Dealing with Numbers

JavaScript is a very forgiving language. It’s easy to write code that runs but has issues in it.

In this article, we’ll look at how we should work with numbers in JavaScript.

Avoid Magic Numbers

Magic numbers are numbers that are used everywhere but their meaning isn’t explicitly specified.

To make them easier to understand and change, we should assign them to a named constant so that we can see what they mean.

Also, we only have to change one place since the constant name is referenced instead of numeric literals.

So instead of writing:

for (let i = 0; i < 10; i++) {
  //...
}

We write:

const MAX_DONUTS = 10;
for (let i = 0; i < MAX_DONUTS; i++) {
  //...
}

Use Hard-Coded 0s and 1s if we Need to

0’s and 1’s are OK for hard coding since they’re used for incrementing or setting as an initial number.

We often increment numbers by 1, so we can hard code 1.

0 are used for initial values of variables, so that’s also OK to hard code.

Other numbers should be replaced with something more descriptive.

Anticipate Divide-by-Zero Errors

We shouldn’t expect that the denominator a fraction will never be 0. Therefore, when we divide a number by another number, we should make sure that the divisor is never 0.

Make Type Conversions Obvious

In JavaScript, types may be converted explicitly.

This may cause unexpected results.

To make them obvious, we should convert the variables explicitly.

So, we should use functions like Number , Number.parseInt , or Number.parseFloat to convert non-numbers to numbers.

The + sign before a variable or a non-number can also convert them to numbers.

Also, when we compare things that may be numbers of equality, we should use === so that we’ve to convert the operands to numbers with those functions and operators.

Avoid Mixed-Type Comparisons

Mixed type comparisons often create unexpected results.

Therefore, we should avoid them as much as possible. We should use the === operator to compare for equality.

If we’re checking for inequality, then we should convert both operands to the same type before comparing them.

So we should convert them both to numbers with those methods and operations we looked at before.

Check for Integer Division

We should check that the result of any integer division is what we expect.

We should make sure that division is done last if we have an expression with multiple arithmetic operations.

For instance, instead of writing:

10 * 3 / 10

we write:

(10 * 3) / 10

to avoid anything unexpected.

Check for Integer Overflow

We should check if our number’s absolute value is bigger than Number.MAX_SAFE_INTEGER is we’re working with large integers.

If it is, then we need to use the bigInt type instead.

bigInt is a primitive data type that can let us store integers with magnitude bigger than Number.MAX_SAFE_INTEGER .

We can do arithmetic with other bigInts only and not numbers.

Photo by Austin Distel on Unsplash

Avoid Additions and Subtractions on Numbers that have Greatly Different Magnitudes

The result might not be what we expect if we do additions and subtractions on numbers that have big difference in magnitudes.

For instance, 1000000000.00 + 0.00000000001 returns 1000000000 since there aren’t enough significant digits to include all the digits of the result.

Avoid Equality Comparisons

Equality comparisons don’t work well with numbers.

For instance if we have:

let sum = 0;
for (let i = 0; i < 10; i++) {
  sum += 0.1;
}

We get 0.9999999999999999 as the value of sum instead of 0.1

Therefore, comparing both directly won’t work very well.

We may want to write our function to see if one number is close enough to what we want to be considered equal.

Anticipate Rounding Errors

As e can see, rounding errors happen frequently. If we add a number 10 times, it doesn’t equal to the number we expect, for example.

Conclusion

When we deal with numbers in JavaScript, we should keep a few things in mind.

Rounding errors are a problem that w should be aware of.

When we apply arithmetic operations to numbers, we don’t always get the same thing as we do the same thing on paper.

Therefore, we should be careful when checking for values.

Mixed-type comparisons are also bad. We should convert everything to numbers before comparing them.

Categories
JavaScript Best Practices

JavaScript Best Practices for Writing More Robust Code — Type Checks

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 how to do type checks properly in JavaScript so that we won’t run into data type errors when we’re running our apps.

typeof Operator

We can use the typeof operator to check for primitive data types and also some object types like functions.

These checks are needed so that we know that we’re working on the right type of data. For instance, we don’t want to use the + operator with strings when we actually want to add numbers together.

To use it, we add typeof before the value that we want to check. Possible values that are returned by the typeof operator are the following:

  • Undefined — "undefined"
  • Null — "object"
  • Boolean — "boolean"
  • Number — "number"
  • BigInt (new in ECMAScript 2020) — "bigint"
  • String — "string"
  • Symbol (new in ECMAScript 2015) — "symbol"
  • Function object (implements [[Call]] in ECMA-262 terms) — "function"
  • Any other object — "object"

These are all the possible type name strings that typeof can return. Most of these are primitive value types, except for 'object' and 'function' .

Note that typeof null returns 'object' . This is a bug, but since it breaks too much existing code for this to be corrected, it’s kept the way it is.

For instance, we can do the following checks with the typeof operator:

typeof 3 === 'number';
typeof 3.1 === 'number';
typeof(40) === 'number';
typeof Math.LN2 === 'number';
typeof Infinity === 'number';
typeof NaN === 'number';

The code above all return true. As we can see from the code above, numbers, Infinity and NaN all return the type 'number' when we use them with the typeof as operands.

An example for a BigInt check is the following:

typeof 41n === 'bigint';

BigInts have an n after the numeric literal to distinguish them from normal numbers. Also, they can only be integers and can only operate on other BigInts in most cases.

To check for strings we can write something like the following:

typeof '' === 'string';
typeof 'bla' === 'string';
typeof `template` === 'string';

This works for both normal and template strings.

To check for booleans, we can write:

typeof true === 'boolean';
typeof false === 'boolean';
typeof Boolean(0) === 'boolean';
typeof !!(1) === 'boolean';

Boolean() and !! do the same thing. They both convert the argument or operand to a boolean. The expressions above all return true .

To check for symbols, we can write:

typeof Symbol() === 'symbol'
typeof Symbol('foo') === 'symbol'
typeof Symbol.iterator === 'symbol'

The expressions above all return true .

And to check for functions, we can write:

typeof () => {} === 'function';
typeof class C {} === 'function';
typeof Math.sin === 'function';

Note that since classes are just syntactic sugar for constructor functions in JavaScript, classes when used with typeof returns 'function' . The expressions above all return true .

To check if something is an object, we can write something like:

typeof {a: 1} === 'object';
typeof [1, 2, 3] === 'object';
typeof new Date() === 'object';

The expressions above all return true .

We should use wrapper objects for primitive values, so the following are useless and confusing:

typeof new Boolean(true) === 'object';
typeof new Number(1) === 'object';
typeof new String('abc') === 'object';

All 3 expressions are true because they all have types 'object' . This isn’t a problem with object literals since they return the types we expect.

However, these don’t and they don’t do anything better than the literals, so we should avoid using constructors for defining primitive values.

The typeof operator doesn’t work with undeclared variables since ES2015.

For instance, if foo isn’t declared, then in ES2015 or later, then typeof foo will throw an error in our code.

Whereas before ES2015, typeof foo returns undefined if the variable is undeclared.

With block-scoped variables or constants, we can only do operations on them when the variable is declared. For instance, the following:

typeof foo === 'undefined';
let foo;

will give us a ReferenceError.

Therefore, we should be careful when using the typeof operator so we don’t cause more crashes during our checks.

Conclusion

The typeof operator lets us type checks mainly on primitive values. However, we can also use it to check if a function or class is a function.

Other kinds of objects will return 'object' with the typeof operator so it isn’t that useful.

Categories
JavaScript Best Practices

JavaScript Best Practices for Writing More Robust Code — New Features

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 how to add polyfills and language features to our app to add features that aren’t built into the browsers we’re targeting.

Polyfills

Polyfills are important for making our client-side JavaScript code robust. Polyfills are scripts that add features that browsers may not otherwise support.

It implements particular JavaScript APIs that browsers may not support yet. Part of the JavaScript standard library that isn’t available in the browser that we’re targeting also need polyfills.

They’re just libraries that we add just like any other library. For instance, some API like the Fetch API or the Network Information API may not be available yet.

However, we want to use them so that we can make HTTP requests adding an extra library or watching for network connection type changes respectively.

Then we need to add polyfills for them. If we need to add support for the Fetch API for browsers that don’t support them, then we need to install a Fetch polyfill by following the instructions at https://github.com/github/fetch.

We should only use the polyfill version if the native version isn’t available as the native version is faster since it’s built into the browser rather than added on the fly.

Likewise, there’s a Network Information API polyfill at https://www.npmjs.com/package/network-information-api-polyfill so that we can use the Network Information API in older browsers.

New features added to browser global objects like Array may also need a polyfill for them to work.

For instance, if we need to use the array instance’s find method in IE, then we need to add a polyfill for it. The script for it is available at https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find.

There’s also which is a one-stop-shop for all the polyfills we need to add new native features to browsers that may not be available.

Some features like audio and video stream access do not have polyfills as they require native hardware support. Therefore, those features would need to be disabled in our app if our app supports an old browser that doesn’t have this feature built-in.

The Babel Compiler

To add new JavaScript features that many browsers don’t support out of the box, we would need to add a compile step to our browser so that when we use new JavaScript language features in our app, that it would compile down into code that’s compatible with the browsers that we target.

We can use the Babel compiler to do that. It supports all current features in addition to ones that are experimental.

It’s better to stick with the current ones in a production app as the experimental ones are subject to breaking changes.

Changes that are to become standard are in Stage 4, which means that they’ll be added as a standard for sure into the JavaScript language.

Useful JavaScript features that are to become standard are ones like the optional chaining operator.

For instance, if we have a plain JavaScript that’s compiled with Parcel, which comes with Babel already, then we can add the @babel/plugin-proposal-optional-chaining Babel plugin by running:

npm i @babel/plugin-proposal-optional-chaining

Then in the .babelrc file of the project, we add:

{  
  "plugins": \[  
    "@babel/plugin-proposal-optional-chaining"  
  \]  
}

Then we can use it on our JavaScript code adding:

const foo = {};  
const x = foo?.bar?.baz;  
console.log(x);

Babel supports many more features than just the optional chaining operator. It also works with both Node.js and client-side JavaScript.

Pretty much any more JavaScript features are supported with Babel, mostly without plugins. Features like let , const , for...of , modules, maps, sets, symbols, rest and spread operators, destructuring, etc. are all supported by Babel.

We can also add Babel as a script for legacy apps or for prototyping by adding:

<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>

to our HTML.

Then if we have a script tag with the type attribute set to text/babel , then we can add modern Javascript features without worrying about breaking the app in old browsers.

Conclusion

Polyfills are scripts that add support for features that may not be available in old browsers.

To add native JavaScript features that may not be available in the browser that our app is targeting, we can use the Babel compiler to add support for them. We can also add Babel plugins to support more cutting edge features.

In addition, we can compile modern JavaScript code on the fly by using the script tag version of Babel and add our own scripts with the type attribute set to text/babel to add support for it without building.

Categories
JavaScript Best Practices

JavaScript Best Practices for Writing More Robust Code — More About Functions

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 good function features that JavaScript has that we should use.

Destructuring Arguments

Destructuring arguments should be used to take parts of an argument and reference that. This way, we can reference a part of an argument as a variable rather than having to use the path of the property or array to access it.

Also, there are much fewer chances for errors since some properties may be non-existent, null , or undefined , and they’ll throw errors if we access them with the path.

We don’t have to worry about that since we can set default values for properties with the destructuring syntax.

For instance, we can do that with the rest operator and arrays as follows:

const f = (a, b, ...[d, e]) => {
  console.log(d, e);
}

Then when we call it as follows:

f(1, 2, 3, 4, 5);

We get that d is 3 and e is 4.

Also, we can use the destructuring syntax on object arguments. For instance, we can write the following code to use that:

const f = ({
  foo: {
    bar
  }
}) => {
  console.log(bar);
}

In the code above, we have the following function signature:

({
  foo: {
    bar
  }
})

This code takes the foo.bar property’s value and assigns it to the bar variable.

Then when we call it as follows:

f({
  foo: {
    bar: 1
  }
});

We get that bar in f is 1.

The good thing about the destructuring syntax is that it won’t throw errors when we try to destructure a property that doesn’t exist if we set default values for them

For instance, if we have the following function:

const f = ({
  foo: {
    bar
  } = {}
}) => {
  console.log(bar);
}

Then when we call it as follows:

f({});

We’ll get undefined instead of an error since we set the foo property to an empty object.

Likewise, we can set default values for destructured array entries as we wish. For instance, we can write:

const f = (a, b, ...[d, e = 4]) => {
  console.log(d, e);
}

Then we call it as follows:

f(1, 25);

We get that d is undefined and e is 4 since we set e ‘s default value to 4.

As we can see, there’s less chance for committing errors and also it’s clearer since we can see the structure of the arguments.

Minimize the Use of Traditional Functions

The use of traditional functions should be minimized. Not only we need to type the function keyword every time.

Also, with traditional functions, we have to worry about the value of this and arguments . We also have to think if it’s hoisted or not.

Hoisted functions, which are function declarations, can be referenced anywhere in a script, but function expressions can’t be referenced until they’re defined.

Moreover, we don’t have to worry about how to call , bind , and apply if we don’t use traditional functions.

These things are always confusing.

With the class syntax for constructor functions, there’s also no reason to use them to create constructor functions.

This is because the class syntax tells us when we have errors in our class declarations.

For instance, if we have the following JavaScript class hierarchy:

class Foo {
  constructor(foo) {
    this.foo = foo;
  }
}
class Bar extends Foo {
  constructor(foo) {
    super(foo);
  }
}
const bar = new Bar();

Then when we forgot to call super in the constructor of Bar , we’ll get an error since we have constructor in the Foo class that takes an argument.

We want these errors to be shown so we can fix them before the code goes to production.

This is in contrast to what we have before we have the class syntax:

function Foo(foo){
  this.foo = foo;
}

function Bar(foo){
  Foo.call(this, foo);
}

Bar.prototype.constructor = Foo;
const bar = new Bar('foo')

This does the same thing as the class syntax, but we get no errors if we miss the call and constructor .

Conclusion

We should use the destructuring syntax so that we can see the structure of our arguments more clearly and selectively use parts of the arguments in our functions.

Also, we shouldn’t use traditional functions anymore in most cases since they are more confusing and complex and harder to understand.