Quickie Dev #1: About falsy and default values in Javascript

Because setting default values in Javascript can seem very simple… until you realize you can’t actually override them!

This article is also available on Medium.

When you write code, you want your variables to have clearly defined values, you want to know what they are equal to at any given point in your program. The easy case is when you explicitly set a variable: if you say that a = 2, then the value of a is 2 (obviously). But what about variables you haven’t explicitly set? (Or haven’t explicitly set yet?) Those variables still have their default value.

The problem

There are various ways of initializing a variable and plenty of articles on the net to give you some shorthands. In Javascript, a well-known technique is to use the logical “or” operator, ||, to override the default value when you initialize a variable. This is particularly neat when you have some default configuration and you want to override it with user-specific options.

For example, let’s say I’m writing a program to display geometric shapes. The idea is to show a square, a triangle and a circle on a row, either black or red, that are evenly spaced out. I have two settings I can tweak: the spacing between the shapes and a useBlack flag to tell whether to draw black or red shapes.

We can set the spacing between the shapes and the color used to draw them (black or red).

Now, most of the time, I want the shapes to be black and the spacing to be of 10 pixels. In order words, I can define my basic configuration as:

var config = {
  spacing: 10, // in pixels
  useBlack: true
}

And in my program, I’ll just use this configuration when calling my drawing logic:

var config = {
  spacing: 10, // in pixels
  useBlack: true
}

function run() {
  const spacing = config.spacing;
  const useBlack = config.useBlack;
  // the magic functions that draw my shapes
  setShapesSpacing(spacing);
  setPaintColor(useBlack);
  drawShapes();
}

run();

This works great! Now it’s time to add my user-custom variables: this time I want to draw shapes with a 2 pixels-spacing, so that they’re closer together. To do this, let’s create a second object to hold our user configuration and use the famous || operator that allows you to easily assign a value to a variable from a list of possible sources.

function run(options) {
  const spacing = options.spacing || config.spacing;
  const useBlack = options.useBlack || config.useBlack;
  ...
}

run({ spacing: 2 });

As you can see, you write the default value on the far right and then the possible override on the right, and we separate the two with the || operator.

When I run this code, shapes are indeed closer together, yay!

Problem: However, if I decide to set a 0 pixel-spacing, then shapes suddenly jump back to their original position. Similarly, if I try and use the same technique for my colors by adding a useBlack set to false, nothing changes!

The why

This is because the || operator is actually returning the “first non-falsy operant”, or in other words the first value in your series of possibilities that is not considered “false” by Javascript. And there actually are more falsy values than we might think…!

false The keyword false.
0 The Number zero (so, also 0.0, etc., and 0x0).
-0 The Number negative zero (so, also -0.0, etc., and -0x0).
0n, -0n The BigInt zero and negative zero (so, also 0x0n/-0x0n).
"", '', `` Empty string value.
null null — the absence of any value.
undefined undefined — the primitive value.
NaN NaN — not a number.

When you pass in “0” or “false” as I just did, then this value is actually a falsy, so our initial base-configuration default will naturally pop up and our user-custom config will be ignored.

Note: as a rule-of-thumb, you can remember that using a falsy value with the || operator never returns the falsy value, and using a falsy value with the && operator always returns it.

Some solutions

To fix our problem, we have several possibilities.

Object.assign()

This allows you to completely overwrite the key-value pairs of an object. You start from a source and you “add on” one or more targets that may or may not share the same keys as the source:

const result = Object.assign(targetC, targetB, targetA, source);

Every time the same key is encountered, the program will simply overwrite the value. Else if there is a new key, it will be added to the final object with its new value. If none of the targets contain a key that is present in the source, then this key-value pair will be left unchanged.

Similarly to our previous example with the || operator, when using Object.assign you need to read the overwrites from right to left:

Using the ?? nullish coalescing operator (ECMA 2020)

Warning: this operator is only available if you are using Javascript ECMA 2020 or later.

The big difference between the ?? and the || operator is that the ?? operator only ignores the left operant if its is equal to null or undefined, instead of any falsy value. So a user-custom value of 0, an empty string or even NaN will not be skipped with this operator:

const spacing = options.spacing ?? 10;
// -> if options.spacing = 2, spacing = 2
// -> if options.spacing = 0, spacing = 0
// -> if options.spacing = null, spacing = 10

Reverting to a basic condition

If you’re unsure of your data or you’re afraid it might do something unexpected, you can always revert to an explicit condition (either with an if/else statement or with a ternary condition):

let spacing = 10;
if (typeof options.spacing === 'number') {
  spacing = options.spacing;
}

let useBlack = options.useBlack === false ? false : true;

Note: be careful, you might be tempted to use !options.useBlack… but this will lead to the exact same issue as with the || operator since the ! operator checks for falsy values too.

To conclude

Javascript is famous for being a somewhat “implicit” language that often does quite weird things until you understand the underlying reasons. Since JS is not a typed language, using the logical or coalescing operators properly to set default values requires you to really know your data, the types of your variables and the values they can take. Still, when used correctly, they are both very powerful tools.

Leave a Reply

Your email address will not be published.