Javascript's implicit coercion simply refers to Javascript attempting to coerce an unexpected value type to the expected type. So you can pass a string where it expects a number, an object where it expects a string etc, and it will try to convert it to the right type. This is a Javascript feature that is best avoided.
3 * "3" //9
1 + "2" + 1 //121
true + true //2
10 - true //9
const foo = {
valueOf: () => 2
}
3 + foo // 5
4 * foo // 8
const bar = {
toString: () => " promise is a boy :)"
}
1 + bar // "1 promise is a boy :)"
4 * [] // 0
4 * [2] // 8
4 + [2] // "42"
4 + [1, 2] // "41,2"
4 * [1, 2] // NaN
"string" ? 4 : 1 // 4
undefined ? 4 : 1 // 1
Non-numeric values in numeric expressions
Strings
Whenever you pass a string as an operand in a numeric expression involving either of these operators: -, *, /, %
, the number's conversion process is similar to calling the in-built Number
function on the value. This is pretty straightforward, any string containing only numeric characters will be converted to it's number equivalent, but a string containing a non-numeric character returns NaN
. Illustrated below,
3 * "3" // 3 * 3
3 * Number("3") // 3 * 3
Number("5") // 5
Number("1.") // 1
Number("1.34") // 1.34
Number("0") // 0
Number("012") // 12
Number("1,") // NaN
Number("1+1") // NaN
Number("1a") // NaN
Number("one") // NaN
Number("text") // NaN
The case for the + operator
The + operator unlike other mathematical operators, performs two functions:
- Mathematical addition
- String concatenation
When a string is an operand of the + operator, Javascript instead of converting the string to a Number, converts the number to a string.
// concatenation
1 + "2" // "12"
1 + "js" // "1js"
// addition
1 + 2 // 3
1 + 2 + 1 // 4
//addition, then concatenation
1 + 2 + "1" // "31"
(1 + 2) + "1" // "31"
//concatenation all through
1 + "2" + 1 // "121"
(1 + "2") + 1 // "121"
Objects
Most Javascript Object conversions usually result in [object Object]
, For example
"name" + {} // "name[object Object]
Every javascript Object inherits a toString
method, that is called whenever an Object is to be converted to a string. The return value of the toString
method is used for such operations as string concatenation and mathematical expressions.
const foo = {}
foo.toString() // [object Object]
const baz = {
toString: () => "I'm object baz"
}
baz + "!" // "I'm object baz!"
When it's a mathematical expression, Javascript will attempt to convert the return value to a number, if it's not.
const foo = {
toString: () => 4
}
2 * foo // 8
2 / foo // 0.5
2 + foo // 6
"four" + foo // "four4"
const baz = {
toString: () => "four"
}
2 * baz // NaN
2 + baz // 2four
const bar = {
toString: () => "2"
}
2 + bar // "22"
2 * bar // 4
Array objects
The inherited toString
method of Arrays work abit differently, it works in a way similar to calling the join
method of an array without any arguments.
[1,2,3].toString() // "1,2,3"
[1,2,3].join() // "1,2,3"
[].toString() // ""
[].join() // ""
"me" + [1,2,3] // "me1,2,3"
4 + [1,2,3] // "41,2,3"
4 * [1,2,3] // NaN
So when you pass an array where it expects a string, Javascript concatenates the return value of the toString
method with the second operand. If it expects a number, it attempts to convert the return value to a number.
4 * [] // 0
4 / [2] // 2
//similar to
4 * Number([].toString())
4 * Number("")
4 * 0
//
4 / Number([2].toString())
4 / Number("2")
4 / 2
True, False and ""
Number(true) // 1
Number(false) // 0
Number("") // 0
4 + true // 5
3 * false // 0
3 * "" // 0
3 + "" // "3"
The valueOf
method
It is also possible to define a valueOf
method that will be used by Javascript whenever you pass an Object where it expects a string or numeric value.
const foo = {
valueOf: () => 3
}
3 + foo // 6
3 * foo // 9
When both the toString
and valueOf
methods are defined on an Object, Javascript uses the valueOf
method instead.
const bar = {
toString: () => 2,
valueOf: () => 5
}
"sa" + bar // "sa5"
3 * bar // 15
2 + bar // 7
The valueOf method is intended for Objects that are supposed to represent a numeric value.
const two = new Number(2)
two.valueOf() // 2
Falsy and Truthy
I really wanted to make that falsy and trusy
Every Javascript value can be coerced into either true or false. Coercion into boolean true
means the value is truthy. Coercion into boolean false
means the value is falsy.
There are a handful of values in Javascript that return falsy values, they are:
- false
- 0
- null
- undefined
- ""
- NaN
- -0
Everything else is truthy,
if (-1) // truthy
if ("0") // truthy
if ({}) // truthy
The above snippets are okay, but it is better practice to be explicit when trying to determine truthiness of a value. Basically, don't rely on Javascript's implicit coercion, even if you feel you know them perfectly.
Instead of the code snippet below,
const counter = 2
if (counter)
Any of the below is better practice depending on your requirements
if (counter === 2)
//or
if (typeof counter === "number")
This is because for example, you define a function that is supposed to work with numbers
const add = (number) => {
if (!number) new Error("Only accepts arguments of type: number")
//your code
}
So if I call the add function with 0, I will always get an unintended error
add(0) // Error: Only accepts arguments of type: number
//better check
const add = (number) => {
if (typeof number !== "number") new Error("Only accepts arguments of type: number")
//your code
}
add(0) // no error
NaN
NaN
is a special numeric value that is not equal to itself.
NaN === NaN // false
const notANumber = 3 * "a" // NaN
notANumber == notANumber // false
notANumber === notANumber // false
NaN
is the only Javascript value that is not equal to itself. So you can check for NaN
by comparing it to itself.
if (notANumber !== notANumber) // true
ECMAScript 6 introduced a method for checking NaN, Number.isNaN
Number.isNaN(NaN) // true
Number.isNaN("name") // false
Beware of the global isNaN
function, it attempts to coerce it's argument before checking if it's NaN
. For example,
isNaN("name") // true
isNaN("1") // false
The global isNaN
function should be avoided, the way it works is similar to the function below
const coerceThenCheckNaN = (val) => {
const coercedVal = Number(val)
return coercedVal !== coercedVal ? true : false
}
coerceThenCheckNaN("1a") // true
coerceThenCheckNaN("1") // false
coerceThenCheckNaN("as") // true
coerceThenCheckNaN(NaN) // true
coerceThenCheckNaN(10) // false
That's most of implicit coercion. If I missed something, please chime in, in the comments below, and thanks for reading all the way.
Top comments (6)
Only case where I like to use type coercion is with null.
There are only two values that
== null
:null
andundefined
. Same thing forundefined
. So it's very useful for when you want to check if your variable holds an actual value and want to avoid an ugly(variable === null || variable === undefined)
, just dovariable == null
.nice tip @elarcis
The last thing I recall biting me in JS and I didn't see it mentioned above:
The first one is false because
new
creates a Number object instance which is compared by reference. Value does not equal object reference.The second one is true because it is a function which returns a value type.
Type coercion is one of the worst parts of JS. I could not even count the number of hours I have wasted tracking down bugs because of this "feature". It is one of the main reasons I avoid the language.
Yeah, object wrappers. They almost never have an advantage over the normal primitives.
Those sort of bugs can truly be frustrating.
If you think it is best avoided, you aren't taking full advantage of what is possible in JS. JS is a weakly-typed language - embrace that fact, and stop trying to use it as if it were strongly typed. It's a pro, not a con.
I'm looking at you, #TypeScript !
I don't know if it actually fits into the implicit coercion stuff, since it is more about optimization, but since ES2015, the signed 64 bit float representation (52 bit of mantissa, 11 bit of binary exponent and 1 bit for the sign) of Number is now no longer the only available representation of numeric values (which was required in order to work with asm.js and Web Assembly).
Modern JS vms like v8 will therefore keep an extra internal flag if it knows that a number is an integer instead of a float throughout the scope and then run the faster non-floating-point calculations on them. To "coerce" a floating point number to such an integer for the rest of the scope, be sure to add
| 0;
to any calculation that may result in an odd result.