JavaScript Coercion
by Nicklas EnvallCoercion is a noun, and it means, "the action or practice of persuading someone to do something by using force". Section 7 in the EcmaScript Specification says the following "The ECMAScript language implicitly performs automatic type conversion as needed".
In this article, we will look at coercion in JavaScript, both explicit and implicit. But first, we will look at types and values in JavaScript so we can more easily understand coercion.
JavaScript types and values
JavaScript has 7 built-in primitive types according to MDN:
- boolean
- null
- undefined
- number
- bigint
- string
- symbol
In English, primitive is a noun, and it means, "an original or primary word; a word not derived from another, as opposed to derivative". In JavaScript, primitives have no methods and are immutable, meaning they cannot be changed. Furthermore, we also have object
, which is a non-primitive built-in type.
When thinking of types, most of us probably think of a strongly typed language such as Java or C#, where we would declare a variable with the type int
or string
like:
int x = 1; string y = "hello";
But that's not how we do it in JavaScript. In JavaScript, our variables themselves do not have types, but our values do. This allows us to store different types of values with the same variable. We may use the typeof
operator to inspect which type a value has:
// new value with a different type var x = 1; x = "hello"; // typeof typeof "I'm a string"; // string typeof 1; // number typeof true; // boolean typeof undefined; // undefined typeof null; // object (this is a very old bug...) typeof {}; // object
Object Wrapper
Now, you've probably done something like this before:
var myString = "hello world"; myString.length;
But, let's ask ourselves, how is this actually possible? Because when you think about it, it shouldn't work because the primitive value "hello world"
does not have a property length
(remember, primitives do not have methods).
Well, this is where boxing/wrapping comes in. JavaScript, in this case, will coerce the primitive string value to an object by simply doing new String(myString)
and then call .length
from that object. The newly created object would later be abandoned after calling the method. These kinds of objects are what we refer to as wrapper objects.
The values null
and undefined
does not have wrapper objects, and therefore, we cannot do null.property
or undefined.property
. However, let's investigate a string created with the keyword new:
var s = new String("hello"); Object.getPrototypeOf(s) === String.prototype; // true
Yes, this might be pretty obvious. But it makes the point clear that with s
we can access all the methods that String.prototype
has. So, why not just use new String()
, new Number()
or new Boolean()
? Well, most information out there vaguely says that it's rarely useful, necessary or that there is anything to gain from it. It's better to just allow JavaScript to wrap the values when needed.
Type Coercion in JavaScript
Coercion can be explicit or implicit. Explicit coercion is when it's very clear that coercion is taking place, for example, coercing 1
to "1"
, would be done with String(1)
. Implicit coercion, on the other hand, is not that obvious, because it's often done when we use operators with our values. Continuing our example with coercing 1
to "1"
, an implicit coercion would be 1 + ""
.
Boolean("hello") // explicit if ("hello") { ... } // implicit String(10) // explicit 10 + '' // implicit Number("2") // explicit "2" < 1 // implicit
There are certain rules for how different types of values get coerced. Luckily the EcmaScript Specification has written them down for us. In this section, we'll look at how different primitive value types can get conversed to strings, booleans, and numbers.
1. ToBoolean (argument)
// 1. Undefined or Null: return false // 2. Boolean: return argument // 3. Number: if argument is +0, -0, or NaN, return false; otherwise return true // 4. String: if the argument is the empty String (its length is zero), // return false; otherwise return true. Boolean(undefined); // false Boolean(null); // false Boolean(true); // true Boolean(false); // false Boolean(0); // false Boolean(+0); // false Boolean(-0); // false Boolean(""/0); // false Boolean(1); // true Boolean(11); // true Boolean(22); // true Boolean(33); // true Boolean(""); // false Boolean("hello"); // true
2. ToString (argument)
// 1. Undefined: return "undefined" // 2. Null: return "null" // 3. Boolean: "true" if true, "false" if false // 4. Number: (see section 7.1.12.1) // 5. String: return argument String(undefined); // "undefined" String(null); // "null" String(true); // "true" String(false); // "false" String(1); // "1" String(-1); // "-1" String(-0); // "0" String(+0); // "0" String(0); // "0" String("hello"); // "hello"
3. ToNumber (argument)
// 1. Undefined: return NaN // 2. Null: return +0 // 3. Boolean: if true, return 1. If false, return +0 // 4. Number: return argument // 5. String: (see section 7.1.3.1) Number(undefined); // NaN Number(null); // 0 Number(true); // 1 Number(false); // 0 Number(-4); // -4 Number(11); // 11 Number(33); // 33 Number("2"); // 2 Number("hello"); // NaN
Implicit Coercion with Operators
We briefly covered ToBoolean, ToString, and ToNumber which showed us the result of trying to coerce a certain value type to another value type. But implicit coercion isn't this obvious, so now we will look at how coercion works with the commonly used operators ==
, +
, -
, *
, /
, and %
.
I have written down a simplified version of the rules of coercion with these operators, based on the ECMAScript specification. It only covers the data-types boolean
, null
, undefined
, number
, and string
.
Equal Operator (==)
Just to be clear, ==
allows coercion while ===
does not. Many JS-developers advocate using the strict ===
to avoid unexcepted behaviour that may occur. Anyway, here are the simplified rules for the equality operator:
- If
x
andy
has the same type,return x === y
- If
x
isnull
andy
isundefined
,return true
- If
x
isundefined
andy
isnull
,return true
- If
x
isnumber
and andy
isstring
,return x == ToNumber(y)
- If
x
isstring
and andy
isnumber
,return ToNumber(x) == y
- If
x
isboolean
,return ToNumber(x) == y
- If
y
isboolean
,return x == ToNumber(y)
1 == 1 // 1 === 1 results in true null == undefined // true undefined == null // true 30 == "30" // Apply rule 4: 30 == ToNumber("30") // Apply rule 1: 30 === 30 // 30 === 30 "30" == 30 // Apply rule 5: ToNumber(“30”) == 30 // Apply rule 1: 30 == 30 // 30 === 30 true == 1 // Apply rule 6: ToNumber(true) == 1 // Apply rule 1: 1 === 1
The following can be confusing:
Boolean("30"); // true true == "30"; // false
How does this make any sense? Well, I won't argue if it makes sense or not, but here's how it works:
true == "30" // Apply rule 6: ToNumber(true) == "30" // Apply rule 4: 1 == ToNumber("30") // Apply rule 1: 1 === 30
Addition Operator (+)
With +
we either do numeric addition or string concatenation. The simplified rules are:
- If
x
ory
is a string,return concatenation of x and y
- If
x
andy
is not a string,return ToNumber(x) + ToNumber(y)
// Examples of rule 1 "hello " + true // "hello true" "hello " + null // "hello null" "hello " + undefined // "hello undefined" "hello " + 123 // "hello 123" // Examples of rule 2 true + true // 1 + 1 resulting in 2 false + false // 0 + 0 resulting in 0
The (-, *, /, %) operators
Simply do, return ToNumber(x) (- OR * OR / OR %) ToNumber(y)
:
true - true // 0 "20" * 2 // 40 12 / false // Infinity 3 % "9" // 3
Further Reading
As we just saw, coercion can be very powerful and useful, but one must master it first. Both implicit and explicit coercion can confuse if the developer is not aware of the rules.
If you want to read more about coercion, then I recommend the following: