Explicit Coercion
Coercion is the operation of converting a value from one type to another. In JavaScript, coercion always results in either a String
, a Number
, or a Boolean
.
The entire topic of coercion is all about three specific operations:
- Converting non-
String
values toString
values - Converting non-
Number
values toNumber
values - Converting non-
Boolean
values toBoolean
values
The underlying mechanism that makes coercion work in JavaScript is a set of Abstract Operations, specifically ToString
, which handles the conversion of a non-String
primitive to a String
representation, ToNumber
, which handles the conversion of a non-Number
primitive to a Number
representation, ToPrimitive
, which handles the conversion of a complex value (like an Object
) to a primitive representation (so that either ToString
or ToNumber
can further convert them), and ToBoolean
, which defines which value types are "falsy" (i.e., will coerce to false
) or "truthy" (i.e, will coerce to true
).
What is Explicit Coercion?
Explicit Coercion is a coercion operation where it's very clear from the code what value type is getting converted to another value type and why. This is contrasted with Implicit Coercion, which is a coercion operation where the coercion seems to occur as a side-effect of some other operation (like a mathematical operation or string concatenation), and thereby it's less clear what value type is converted to another value type and why.
These are, of course, relative terms - a developer who is very experienced with string concatenation may find that what's commonly called an "implicit coercion" is actually a very clear operation. What I will cover in this blog post (and later in an Implicit Coercion post) will fall in line with what the larger JavaScript community generally considers to be explicit and implicit.
Another way to break it down that may be helpful is to think of "Explicit Coercion" as something more like "Verbose Coercion", where the mechanism that triggers the type conversion operation is readable in the code. In my last post, Coercion: ToString, ToNumber, and ToBoolean, the majority of my examples looked something like the following:
String(420);// expected result: "420"
Number("100");// expected result: 100
Boolean(0);// expected result: false
Boolean(1);// expected result: true
The first thing to note here is the usage the built-in functions String()
, Number()
, and Boolean()
(these are sometimes referred to as "native constructors", although in this case we specifically are not also using a new
keyword). The function name corresponds to the type that we want to convert the value to. The function then accepts as its sole argument the value that we are converting.
So in this example, String()
gets a non-String
value, the Number
420
; Number()
gets the non-Number
value, the String
"100"
; and Boolean()
gets non-Boolean
values, the Number
s 0
and 1
. The functions then go about converting the value they are given based on the rules of the Abstract Operations covered in the last blog post, ToString
, ToNumber
, (sometimes) ToPrimitive
, and ToBoolean
.
The pattern here is the important thing: code that uses these built-in functions is generally considered to be "Explicit", since the function spells out quite plainly the expected output type, and the argument it's given also makes it plain to see the type and value of the data that we are converting. That said, there are a few other ways to make these conversions, and we will explore those now.
Explicitly Coercing to a String
There are two main ways to explicitly coerce a value to a String
representation:
- Using the global
String()
function - Using the
.toString()
object method
The Global String()
Function
As we have seen previously, String()
typically coerces primitive values as you might expect.
null
coerces to "null"
:
String(null);// expected result: "null"
undefined
coerces to "undefined"
:
String(undefined);// expected result: "undefined"
true
coerces to "true"
and false
coerces to "false"
:
String(true);// expected result: "true"
String(false);// expected result: "false"
Number
s coerce to their String
equivalents unless they are really big or really small, in which case they are represented exponentially:
String(420);// expected result: "420"
String(1230000000000000000000);// expected result: "1.23e+21"
String(0.00000000000000123);// expected result: "1.23e-15"
-0
will coerce to a positive 0
representation:
String(-0);// expected result: "0"
When coercing an Object
to a String
, the conversion first looks for a [Symbol.toPrimitive]
method on the Object
, and if found uses that:
var obj = { a: 100, b: "Joey Reyes rules", c: true, d: [1, 2, 3], [Symbol.toPrimitive](hint) { console.log(`hint: ${hint}`); // let's see what the hint is return hint === "string" ? `{ b: "${this.b}" }` : this.a; },};
String(obj);/* * expected output: * hint: string * '{ b: "Joey Reyes rules" }' */
The .toString()
Object Method
If a [Symbol.toPrimitive]
method is not found, then ToPrimitive
looks for a .toString()
method on the Object
, and if found uses that:
var obj = { a: 100, b: "Joey Reyes rules", c: true, d: [1, 2, 3], toString() { return this.b; },};
String(obj);// expected result: "Joey Reyes rules"
If a defined .toString()
method is not found, it uses the generic .toString()
method from Object.prototype.toString()
, which returns the [[Class]]
property, ultimately outputting the String
"[object Object]"
:
var obj = { a: 100, b: "Joey Reyes rules", c: true, d: [1, 2, 3],};
String(obj);// expected result: "[object Object]"
We can also explicitly call .toString()
on some of the primitives, but there are some differences to note. The Number.prototype
and Boolean.prototype
objects have their own defined .toString()
methods that override the Object.prototype.toString()
method. Because of this, when you call .toString()
on a Number
or a Boolean
, the value is "boxed" in its prototype Object
wrapper and it uses its specific type's .toString()
method:
var num = 420;num.toString();// expected result: "420"
var bool1 = true;bool1.toString();// expected result: "true"
var bool2 = false;bool2.toString();// expected result: "false"
On a note related to Number.prototype.toString()
, Number.prototype
also has .toExponential()
, .toFixed()
, and .toPrecision()
methods that also may be used with a Number
to return a String
representation of that Number
. These methods will have their own specific parameters and work within their own sets of rules to return a String
representation according to the proper exponential, fixed, or specified precision expectations:
var num = 1234567890;num.toExponential();// expected result: "1.23456789e+9"
num.toFixed(2);// expected result: "1234567890.00"
num.toPrecision(4);// expected result: "1.235e+9"
Whether you would consider these methods to be clear enough that they return a String
from a non-String
value is really up to you. I include them here because they seem related to Number.prototype.toString()
(which is widely considered to be rather explicit), but it just starts to show how fuzzy the terms "explicit" and "implicit" really are.
Array.prototype
also has its own .toString()
method that can be used in a similar manner. This method internally calls the .join()
method, returning a single String
containing each element within the Array
, separated by commas:
var arr = ["Joey", "Reyes", "rules"];arr.toString();// expected result: "Joey,Reyes,rules"
An empty Array
will return an empty String
:
var arr = [];arr.toString();// expected result: ""
Function.prototype
also has its own .toString()
method, which generally returns a String
representing the source code of the function:
function doSomething(a) { return a * a;}
doSomething.toString();// expected result: "function doSomething(a) {\n return a * a;\n}"
This produces the same behavior we see if we pass a Function
to the global String()
function:
function doSomething(a) { return a * a;}
String(doSomething);// expected result: "function doSomething(a) {\n return a * a;\n}"
Symbol.prototype
similarly has its own .toString()
method, which returns a String
representing the source code of the specified Symbol
:
var sym = Symbol("howdy");
sym.toString();// expected result: "Symbol(howdy)"
As you might expect, the output is the same as if we passed the Symbol
to the global String()
function:
var sym = Symbol("howdy");
String(sym);// expected result: "Symbol(howdy)"
null
and undefined
on the other hand do not have .toString()
methods, nor do they inherit the method from Object.prototype.toString()
. Because of this, if you call .toString()
on null
or undefined
, you get an error:
var nullThing = null;nullThing.toString();// expected result: TypeError
var undefinedThing = undefined;undefinedThing.toString();// expected result: TypeError
Explicitly Coercing to a Number
There are three main ways to explicitly coerce a value to a Number
representation:
- Using the global
Number()
function - Using the
parseInt()
andparseFloat()
functions - Using the
+
or-
unary operators
The Global Number()
Function
Number()
also typically coerces primitive values as you might expect.
null
coerces to 0
:
Number(null);// expected result: 0
undefined
coerces to NaN
:
Number(undefined);// expected result: NaN
true
coerces to 1
and false
coerces to 0
, which are "falsy" values (more on that later):
Number(true);// expected result: 1
Number(false);// expected result: 0
String
s will coerce to their Number
equivalents if possible...
Number("420");// expected result: 420
...otherwise they will coerce to NaN
:
Number("four hundred twenty");// expected result: NaN
Unlike going from Number
to a String
, where -0
coerces to a positive representation, "0"
, going from a String
to a Number
preserves the negative representation:
Number("-0");// expected result: -0
As with coercing an Object
to a String
, when coercing an Object
to a Number
, the conversion first looks for a [Symbol.toPrimitive]
method on the Object
, and if found uses that:
var obj = { a: 100, b: "Joey Reyes rules", c: true, d: [1, 2, 3], [Symbol.toPrimitive](hint) { console.log(`hint: ${hint}`); // let's see what the hint is return hint === "string" ? `{ b: "${this.b}" }` : this.a; },};
Number(obj);/* * expected output: * hint: number * 100 */
If that's not found, then it looks for a .valueOf()
method on the Object
, and if found uses that.
var obj = { a: 100, b: "Joey Reyes rules", c: true, d: [1, 2, 3], valueOf() { return this.a; },};
Number(obj);// expected result: 100
Otherwise it uses the generic .valueOf()
method from Object.prototype.valueOf()
, which returns the Object
itself, a value that gets ignored by the ToPrimitive
Abstract Operation, which ultimately returns NaN
.
var obj = { a: 100, b: "Joey Reyes rules", c: true, d: [1, 2, 3],};
Number(obj);// expected result: NaN
As you might expect, Boolean.prototype
and String.prototype
have their own .valueOf()
methods that override Object.prototype.valueOf()
, but these don't come into play in coercing these values to Number
representations as much as .toString()
comes into play with String
coercions.
Passing a Function
to the Number()
global function will produce NaN
:
function doSomething(a) { return a * a;}
Number(doSomething);// expected result: NaN
I believe this is because, once again, ToNumber
tags in ToPrimitive
, which looks for a [Symbol.toPrimitive]
, doesn't find one, then uses the result of either .valueOf()
(which returns the function itself), or .toString()
(which returns a String
representation of the function itself). In either case, ToPrimitive
passes an unrecognized value (the Function
itself) or a String
up to ToNumber
, and either of those values coerces to NaN
.
Symbol.prototype
has its own .valueOf()
method, which returns the primitive value of the Symbol
object as a Symbol
data type:
var sym = Symbol("howdy");
sym.valueOf();// expected result: Symbol(howdy)
However, this doesn't really come into play in this discussion, as passing a Symbol
to the global Number()
function throws a TypeError
:
var sym = Symbol("howdy");
Number(sym);// Uncaught TypeError: Cannot convert a Symbol value to a number
The parseInt()
and parseFloat()
Functions
An operation that's related to coercing String
s values to Number
representations is the parseInt()
function, which is specifically used to parse a String
argument to a Number
. The main difference between Number(String)
and parseInt(String)
is that parseInt()
is more tolerant of non-Number
values being passed to it:
var str1 = "420";var str2 = "420px";var str3 = "px420";
Number(str1);// expected result: 420Number(str2);// expected result: NaNNumber(str3);// expected result: NaN
parseInt(str1);// expected result: 420parseInt(str2);// expected result: 420parseInt(str3);// expected result: NaN
Where the global Number()
function will always return NaN
if any non-numeric value is found in the String
that it is converting, parseInt()
works from left to right, parsing each individual character within the String
into the Number
that it returns, but stops when it reaches the first non-numeric value. An exception here is leading space characters:
var str4 = " 420 123";parseInt(str4);// expected result: 420
There is a lot more to parseInt()
than just this behavior, though, including passing a specified radix
argument. This topic is beyond the scope of this blog post (as well as my understanding at this time), so I will refer you to MDN's parseInt()
article for more details there.
Another similar function is the parseFloat()
function, which accepts a String
(or converts its argument to a String
) and returns a floating point number:
var str1 = "42.0123";var str2 = "42.0123px";var str3 = "px42.0123";var str4 = " 42.0 123";
Number(str1);// expected result: 42.0123Number(str2);// expected result: NaNNumber(str3);// expected result: NaNNumber(str4);// expected result: NaN
parseFloat(str1);// expected result: 42.0123parseFloat(str2);// expected result: 42.0123parseFloat(str3);// expected result: NaNparseFloat(str4);// expected result: 42
Something to note about parseInt()
and parseFloat()
is that they are really designed to convert String
values to Number
(or floating point) representations. They can accept non-String
values, but they will first run that value through the ToString
abstract operation. On an academic level, this additional step might actually fall into the definition of "implicit" coercion since it's a hidden coercion that's the side effect of another operation. More practically speaking, ToString
can just produce some unexpected behavior, so it's an area where caution should be taken.
Another thing to note is that because parseInt()
and parseFloat()
are tolerant of non-numeric characters and takes steps to parse out a Number
from the start of the String
, these two functions are a bit slower and less performant than Number()
.
The +
or -
Unary Operators
The third method for converting a non-Number
value to a Number
representation is with either the +
or -
Unary Operators. There's debate over whether this method is truly explicit or implicit, but I'm including it here because using the operator in a certain manner is intended for Number
coercion.
The way that this method works is that you prepend the operand that you want to coerce to a Number
with a +
sign. This pattern is the same as passing the value you want to convert to Number()
:
+"420";// expected result: 420
+"four hundred twenty";// expected result: NaN
-
works in a similar way, but it flips the sign of the Number
that it returns:
-"420";// expected result: -420
-"four hundred twenty";// expected result: NaN
The problem with this approach is that +
and -
are rather overloaded symbols in JavaScript. Not only can they be used in this manner for Number
coercion, they can also be used to concatenate String
s...
var str = "Joey";str += " rules ";str += 420;
console.log(str);// expected result = "Joey rules 420"
...or they can be used to increment/decrement Number
values...
var coolNumber = 420;
console.log(++coolNumber);// expected result: 421
console.log(--coolNumber);// expected result: 420
...or (of course) they can be used in addition and subtraction operations:
var num = 210 + 210;console.log(num);// expected result: 420
num -= 100;console.log(num);// expected result: 320
This can all get especially confusing when you start mixing and matching these operations:
var numericString = "320";var superCoolNumber = 100 + +numericString;
console.log(superCoolNumber);// expected result: 420
There are all kinds of nightmarish ways that these operators can get combined to make what is otherwise a straightforward explicit conversion operation feel anything but straightforward. Personally I prefer to avoid this method for Number
coercion, but large parts of the JavaScript community feel that it falls within the "explicit" category, so I'm including it here (along with all of these qualifiers about how its usage may be confusing).
Explicitly Coercing to a Boolean
There are two main ways to explicitly coerce a value to a Boolean
representation:
- Using the global
Boolean()
function - Using the
!
unary operator
Both of these approaches will return a Boolean
value according to the rules set out in the ToBoolean
Abstract Operation.
The following values will coerce to false
, making them "falsy":
undefined
null
+0
-0
NaN
""
(an emptyString
)
All other values and types will return true
, making them "truthy".
The Global Boolean()
Function
This approach should be fairly straightforward by now. We just pass the value we want to convert to a Boolean
to the global Boolean()
function.
null
coerces to false
:
Boolean(null);// expected result: false
undefined
coerces to false
:
Boolean(undefined);// expected result: false
+0
and -0
coerce to false
:
Boolean(0);// expected result: false
Boolean(+0);// expected result: false
Boolean(-0);// expected result: false
NaN
coerces to false
:
Boolean(NaN);// expected result: false
""
(an empty String
) coerces to false
:
Boolean("");// expected result: false
Everything else coerces to true
:
Boolean("Joey Rules");// expected result: true
Boolean(100);// expected result: true
Boolean({});// expected result: true
Boolean({ a: 420 });// expected result: true
Boolean([]);// expected result: true
Boolean(["Joey", "Reyes", "rules"]);// expected result: true
function doSomething(a) { return a * a;}Boolean(doSomething);// expected result: true
var sym = Symbol("howdy");Boolean(sym);// expected result: true
Pretty straightforward stuff.
The !
Unary Operator
The other route for coercing to a Boolean
(that really is more commonly encountered than the global Boolean()
function) is the !
unary negate operator. Prepending the value you want to convert with !
will coerce it to a Boolean
...
!null;// expected result: true
!undefined;// expected result: true
!0;// expected result: true
!"Joey rules";// expected result: false
![];// expected result: false
!["Joey", "Reyes", "rules"];// expected result: false
var sym = Symbol("howdy");!sym;// expected result: false
...however, we see here that this approach actually flips the value from "truthy" to "falsy" or vice versa, hence why it's called the "negate" operator.
Because of this, what will commonly be done is to use two !
operators, !!
- sometimes called the double-negate operator - to produce the value's actual Boolean
representation:
!!null;// expected result: false
!!undefined;// expected result: false
!!0;// expected result: false
!!"Joey rules";// expected result: true
!![];// expected result: true
!!["Joey", "Reyes", "rules"];// expected result: true
var sym = Symbol("howdy");!!sym;// expected result: true
Summary
Explicit Coercion is a coercion operation where it's very clear from the code what value type is getting converted to another value type and why.
We can explicity coerce non-String
values to String
representations using the global String()
function and the .toString()
method.
We can explicitly coerce non-Number
values to Number
representations using the global Number()
function, the parseInt()
and parseFloat()
functions, and the +
and -
unary operators.
We can explicitly coerce non-Boolean
values to Boolean
representations using the global Boolean()
function and the !
unary operator.
Glossary
- Coercion: The operation of converting a value from one type to another. In JavaScript, coercion always results in a scalar primitive value, namely a
String
, aNumber
, or aBoolean
. - Explicit Coercion: A coercion operation where it's very clear from the code what value type is getting converted to another value type and why.
- Implicit Coercion: A coercion operation where the coercion seems to occur as a side-effect of some other operation (like a mathematical operation), and thereby it's less clear what value type is converted to another value type and why.
- Scalar Primitive Value:
- Primitive refers to a data type that is a basic building block of other data types, not one composed of other data types. The six primitive data types in JavaScript are
undefined
,Boolean
,Number
,String
,BigInt
,Symbol
, andnull
. - Scalar refers to a data type that has a single value or unit of data. The scalar data types in JavaScript are
Number
,Boolean
, andString
. Contrast this withnull
orundefined
, whose label is both its type and its value. - A third category would be a Complex, Compound, or Structural value, such as an
Object
(and thereby alsoArray
) or aFunction
. These types are made up of other types.
- Primitive refers to a data type that is a basic building block of other data types, not one composed of other data types. The six primitive data types in JavaScript are
- Abstract Operations: Descriptions of various semantics within JavaScript that help establish norms for operations available across disparate JavaScript implementations.
ToString
: The Abstract Operation that handles conversions of non-String
values toString
representations.ToNumber
: The Abstract Operation that handles conversions of non-Number
values toNumber
representations.ToPrimitive
: The Abstract Operation that handles conversions of complex values to primitive values.ToBoolean
: The Abstract Operation that handles conversions of non-Boolean
values toBoolean
representations.
Sources / Further Reading
- You Don't Know JS: Types & Grammar by Kyle Simpson
- Abstract Operations in ECMAScript 2023 Language Specification by tc39
- Type Conversion Abstract Operations in ECMAScript 2023 Language Specification by tc39
- ToString Abstract Operation in ECMAScript 2023 Language Specification by tc39
- ToNumber Abstract Operation in ECMAScript 2023 Language Specification by tc39
- ToPrimitive Abstract Operation in ECMAScript 2023 Language Specification by tc39
- ToBoolean Abstract Operation in ECMAScript 2023 Language Specification by tc39
- Object to primitive conversion on javascript.info
- Explicit
String
Coercion Operations:- String() Constructor on MDN
- Object.prototype.toString() on MDN
- Number.prototype.toString() on MDN
- Number.prototype.toExponential() on MDN
- Number.prototype.toFixed() on MDN
- Number.prototype.toPrecision() on MDN
- Boolean.prototype.toString() on MDN
- Array.prototype.toString() on MDN
- Function.prototype.toString() on MDN
- Symbol.prototype.toString() on MDN
- Explicit
Number
Coercion Operations: - Explicit
Boolean
Coercion Operations: