Skip to content

matthewburfield

moon indicating dark mode
sun indicating light mode

JavaScript deep dive - Coercion

May 02, 2020

sunrise Photo by Sadman Sakib on Unsplash

Introduction

In my option, the best resource there is for learning about coercion in JavaScript is the “You don’t know JS” (YDKJS) book series by Kyle Simpson. At the time of writing, Kyle is in the process of writing the second edition and hasn’t yet updated the chapter on “Types & Grammar” where you’ll find all the pages on coercion. However you can still view the same chapter in the first edition. It’s an excellent resource and goes super in depth on how coercion works in JavaScript. This blog post is basically going to be a re-hashing of that chapter but put into my own words to help with my own understanding.

Given the above, I’d like to start this blog post with a direct quote from the YDKJS series where Kyle introduces coercion. I think it applies really well to why I decided to write this Deep Dive Into JavaScript blog series in the first place.

In the same overall spirit of this series, rather than running away from coercion because everyone else does, or because you get bitten by some quirk, I think you should run toward that which you don’t understand and seek to get it more fully.

Our goal is to fully explore the pros and cons (yes, there are pros!) of coercion, so that you can make an informed decision on its appropriateness in your program.

With that out of the way, we can get started.

Coercion

It is common practice in programming to need to change a variable from one data type to another. This is called “type casting”, and many languages support it.

Here’s an example of type casting in the “c” programming language.

y = (double) 5;   // type casing an Integer to a Double

We can also type cast in JavaScript too, using some native functions:

const num = Number("42") // 42 (type casting a string to a number)
const str = String([1, 2, 3]) // 1, 2, 3 (type casting an array to a string)

In all the above cases, we say they are type casting, because each data type is converted to another type explicitly. We say explicit because the Number(..) function and the String(..) function make it super obvious that the data types are changing to a number and string respectively.

By contrast, Coercion, is changing a variable from one data type to another implicitly. Here we say implicit to mean that it is not super obvious that the data type has changed, but we are instead using the set of rules imposed by the programming language to change how different data types are represented in different situations.

We’ll deep dive into what I mean by “the set of rules imposed by the programming language” later in the article, but for now, let’s look at a couple of common examples of coercion in JavaScript. That is, an implicit conversion of data types.

const string = "This is a string"
if (string) {
  // The code will enter here because string has a value and is therefore
  // "coerced" into the boolean, `true`.
}

const newString = ""
if (string) {
  // The code will not enter here because newString is an empty string value,
  // and therefore will be "coerced" into the boolean, `false`.
}

You may have used something similar to the above in your code. It is a common pattern to evaluate if a string exists and has a value, but what’s actually happening here?

An if block cannot evaluate a string, it can only evaluate a boolean or an expression that returns a boolean. What’s happening is that the language is coercing the string to a boolean.

To put in another way, if we were to do the above coercions explicitly instead, it would look like this:

const string = "This is a string"
const stringHasValue = Boolean(string) // true
if (stringHasValue) {
  ...
}

const newString = ""
const newStringHasValue = Boolean(newString) // false
if (newStringHasValue) {
  ...
}

However we don’t need to do the explicit type casting, because the language already knows what we are trying to do in this situation. When we evaluate a string directly in an if block, it knows that a coercion of string to boolean is what we intend to do because of the set of rules imposed by the programming language. Therefore it implicitly changes the data type for us. That’s coercion.

As I said above, each programming language will have it’s own set of rules that it imposes for coercion. Languages with strong typing (like Java and Objective-C) typically do little implicit conversions, while languages with weak typing (like JavaScript) perform many implicit conversions between data types. Coercion can also happen at compilation time, or run time, depending on the coercion.

Up until now, I’ve tried to speak quite generally, because all of the above applies to many programming languages, not just JavaScript. However each programming language will have it’s own set of rules that it imposes for coercion, and since this is supposed to be a deep dive into JavaScript coercion, I’ll be focusing on only JavaScript moving forward.

Abstract Value Operations

Before we can deep dive into coercion, we need to first understand how JavaScript will convert types from one type to another. These same rules will apply to both explicit type castings, and implicit coercions.

In JavaScript, you can only convert values to primitive values. That means, if you are converting one type to another, the result can only be either a string, a number, or a boolean. These conversions are internal to JavaScript and aren’t called directly, although there are functions and methods that trigger them (which we’ll cover later).

The internal conversions are defined in the ECMAScript specification (more specifically section 7) which covers Abstract Operations. The ECMAScript specification says:

These operations are not part of the ECMAScript language; they are defined here to solely to aid the specification of the semantics of the ECMAScript language.

We’ll cover the most important abstract methods for converting data types; ToString, ToNumber and ToBoolean. These three are the most important because all coercion in JavaScript will end up as one of these three primitive types. (You can’t coerce a number type into an Object or Function etc).

ToString

Whenever any non-string value is converted into a string, the underlying conversion is defined by the ToString abstraction operation. Here is a table representing how common primitive values will be coerced to a string.

Argument TypeResult
undefined“undefined”
null“null”
true“true”
false“false”

As well as the above, all numbers will pretty much stringify to how one might expect:

Number valueString coercion result
NaN“NaN”
Infinity“Infinity”
0 and -0“0”
1“1”
1.0“1.0”
-1“-1”
1,000,000,000,000,000,000,000“1e+21”

Note that for really really big or really really small numbers, a conversion to string will result in the exponent form of the number. (i.e. "1e+21")

Objects, unless you specify your own, will use the default toString() method on the Object prototype which will return the internal [[Class]]. For example ”[object Object]

Arrays have a default toString() method that will convert each individual element in the array separately to a string, and then join each element with ”,“. For example:

const arr = [1, 2, 3];
arr.toString[]; // "1,2,3"

Functions also have a default toString() method.

If the function is a native JavaScript function, the function will get converted to:

"function () {\n    [native code]\n}"

If the function was created by the Function constructor, the function will get converted to:

const functionName = new Function()
String(functionName) // // "functionName anonymous(↵) {↵↵}

Otherwise, functions will get converted to a text representation of the function:

const functionName = (a, b) => {
  return a + b
}
String(functionName) // "(a, b) => {↵  return a + b;↵}"

ToBoolean

You may have come across the terms “falsy values” and “truthy values”. When we refer to these terms, what we actually mean is “when converted to a Boolean value, will the result be true or false“.

There is a very small list of values that will convert to false, and so it is much easier to just remember the list of “falsy” values. By definition, every other value will convert to true. The “falsy” values are:

Argument typeToBoolean conversion
undefinedfalse
nullfalse
+0, -0 or NaNfalse
"" (an empty string)false
falsefalse

If the value is not in the table above, then it will convert to true. Consider the following variables:

const a = "false"
const b = "0"
const c = "''"

What do you think the results will be if we were to convert the above variables into a boolean? That is, are they “truthy” or “falsy”?

They are all strings, and because none of them are the empty string value (""), they are all truthy!

There’s one last point about Boolean coercion that Kyle Simpson covers in YDKJS and I think is important.

JavaScript has actual keywords for true and false, and they behave as you’d expect for boolean values. It’s a common misconception that the values 1 and 0 are identical to true and false respectively. While that may be true in other languages, in JavaScript the numbers are numbers, and the booleans are booleans. You can coerce 1 to true (and vice versa), or 0 to false (and vice versa). But they are not the same.

ToNumber

If any non-number value is used in a way that requires it to be a number, such as a mathematical operation, the ECMAScript specification defines the ToNumber abstract operation for how that conversion should behave.

Argument typeToNumber conversion
undefinedNaN
null0
true1
false0

For converting a string to a number, it mostly works as the reverse of number to string, including the exponent form.

If the string would convert to an invalid number, for example if there are letters of the alphabet in the string that is not the e of an exponent form, then the result will be NaN.

Number("1") // 1
Number("1.1") // 1.1
Number("1e17") // 100000000000000000
Number("a string") // NaN

JavaScript will also trim white space in the string, if it exists, before doing the conversion.

Objects, (including Arrays and Functions) will first be converted to their primitive value equivalent by first using another abstract operation, ToPrimitive (ref). ToPrimitive will first convert the Object to a non-Object type (that is, a string, number or boolean), and the result of the ToPrimitive conversion will then be converted to a number according to the ToNumber rules above.

Conclusion

We’ve covered a lot so far in terms of coercion in programming, and specifically in JavaScript. But I’m not done yet! There is still so much more I want to learn about coercion in terms of application - how can I apply coercion in my code and what are the safe and meaningful ways in which I can use it to make my code better?

I’m going to split out this topic into two parts (a series within a series!?) so in part two, I can go into more detail about how to apply the coercion theory that we just learnt in real world situations!

Happy Coding!


Hi, I'm Matthew Burfield. I like writing about cool development stuff.