Objects in JavaScript can be converted to primitives in three contexts:
- Numeric
- String
- Boolean
Understanding the way conversion works helps to evade possible pitfalls and write cleaner code.
String conversion
String conversion happens when a string representation of an object is required.
For example, in alert(obj)
does it to output obj
:
var obj = { name: 'John' } alert(obj) // [object Object]
The explicit conversion is also possible: String(obj)
.
The algorithm of Object to String conversion
- If
toString
method exists and returns a primitive, then return it.
Execution normally stops here, becausetoString
exists on all objects by default. - If
valueOf
method exists and returns a primitive, then return it. - Otherwise, throw an exception.
Again, normally all objects have toString
. Built-in objects have their own toString
implementations:
alert( {key: 'value'} ) // toString for Objects outputs: [object Object] alert( [1,2] ) // toString for Arrays lists elements "1,2" alert( new Date ) // toString for Dates outputs the date as a string
Custom toString
For our objects, we can implement a custom toString
:
var user = { firstName: 'John', *!*toString:*/!* function() { return 'User ' + this.firstName } } alert( user ) // User John
Numeric conversion
There is another conversion in JavaScript, not as wide known as toString
, but internally it is called much more often.
Numeric conversion is performed in two main cases:
- In functions which needs a number: for example
Math.sin(obj)
,isNaN(obj)
, including arithmetic operators:+obj
. - In comparisons, like
obj == 'John'
.
The exceptions are the string equality===
, because it doesn’t do any type conversion, and also non-strict equality when both arguments are objects, not primitives:obj1 == obj2
. It is true only if both arguments reference the same object.
The explicit conversion can also be done with Number(obj)
.
The algorithm of numeric conversion:
- If
valueOf
method exists and returns a primitive, then return it. - Otherwise, if
toString
method exists and returns a primitive, then return it. - Otherwise, throw an exception.
Among built-in objects, Date
supports both numeric and string conversion:
alert( new Date() ) // The date in human-readable form alert( +new Date() ) // Microseconds till 1 Jan 1970
But most objects do not have valueOf
. It means that numeric conversion is handled by toString
.
Custom valueOf
example
The magic method valueOf
can be customized, just like toString
:
var room = { num: 777, valueOf: function() { return this.num } } alert( +room ) // 777
If there is a custom toString
, but no valueOf
, the interpreter will use it for numeric conversion:
var room = { num: 777, *!*toString*/!*: function() { return this.num } } alert( room / 3 ) // 259
Numeric conversion does not mean, that a number is returned. It must return a primitive, but there is no limitation on its concrete type.
Because of that, a good way to convert an object to a string is the binary addition:
var arr = [1,2,3] alert( arr + '' ) // first tries arr.valueOf(), but arrays have no valueOf // so arr.toString() is called and returns a list of elements: '1,2,3'
For historical reasons, new Date + ''
also returns a string representation of Date
even though new Date
has valueOf
. That’s an exception.
Other mathematical functions not only perform the numeric conversion, but enforce a number. For example, the unary addition
+arr
would give NaN
:alert( +[1,2,3] ) // [1,2,3] -> '1,2,3' -> not a number
Conversion in equality/comparison tests
Non-strict equality and comparisons use numeric context.
The equality converts an object only if it is compared against a primitive:
if (obj == true) { ... }
There will no be conversion in equity check for two objects: obj1 == obj2
is true only if they refer to the same object.
The comparison always converts to primitive:
var a = { valueOf: function() { return 1 } } var b = { valueOf: function() { return 0 } } alert( a > b ) // 1 > 0, true
Why the following is true?
alert( ['x'] == 'x' )
There is an array to the left and primitive value to the right.
So, a numeric conversion is applied to the array. The Array
has no valueOf
, so toString
is used.
The default implementation of Array#toString
lists it’s comma-delimited values:
alert( ['a','b'] + '' ) // 'a,b'
Because, there is a single value, ['x']
becomes 'x'
.
P.S.
Same logic leads to:
['x','y'] == 'x,y' [] == ''
Boolean context
There is one more standard conversion in JavaScript, called [[toBoolean]]
in the specification.
If happens in boolean context, like if(obj)
, while(obj)
etc.
Object may not implement such conversion on their own, there is no magic method. Instead, there is a hardcoded table of conversions:
Value | Converted to… |
---|---|
true/false |
no conversion |
undefined , null |
false |
Number |
0 , NaN become false , others - true . |
String |
"" becomes false , any other - true |
Object |
true |
Unlike many programming languages (for example PHP), "0"
is true
in JavaScript.
In the example below, we have numeric conversion (equality does it):
alert( [0] == 0 ) // true alert( "\n0\n" == 0 ) // true alert( "\n0\n" == false ) // true
So one may guess that [0]
and "\n0\n"
are falsy, because they equal 0
.
But now let’s see how the left part behaves in boolean context:
if ([0]) alert(1) // 1, if treats [0] as true if ("\n0\n") alert(2) // 2, if treats "\n0\n" as true
It is possible that a == b
, but in boolean context a
is true
and b
is false
.
To convert a value to boolean, you may use double-negation: !!val
or direct call Boolean(val)
.
Of course, we never use new Boolean
for any purpose. Funny things happen if we do.
For example, let’s try to get a boolean out of zero:
alert( new Boolean(false) ) // false
But…
if ( new Boolean(false) ) { alert(true) // true }
That’s because new Boolean
is an object. The alert
converts it to String, and it becomes "false"
… Right.
But if
converts it to boolean primitive, and here any object is true… Wops!
Java programmers’ eyes usually pop out when they see that.
Why they are equal?
alert( [] == ![] ) // true
- First, the two sides of comparison are evaluated. The right side is
![]
. Logical NOT'!'
converts to boolean. According to the table, an object[]
istrue
. So, the right side becomes![] = !true = false
. It brings us to:
[] == false
- The equality check between an object and a primitive converts the object to primitives in numeric way.
The array has no
valueOf
, but hastoString
which converts it to a comma-delimited list of items. In our case, no items lead to an empty string:
'' == false
- Comparison between different primitives converts them to numbers:
0 == 0
Now the result is obvious.
Figure out the result of expressions. When you are done, check against the solution.
6 / "3" "2" * "3" 4 + 5 + "px" "$" + 4 + 5 "4" - 2 "4px" - 2 7 / 0 {}[0] parseInt("09") 5 && 2 2 && 5 5 || 0 0 || 5
6 / "3" = 2 "2" * "3" = 6 4 + 5 + "px" = "9px" "$" + 4 + 5 = "$45" "4" - 2 = 2 "4px" - 2 = NaN 7 / 0 = Infinity {}[0] = undefined parseInt("09") = "0" or "9" // octal or decimal, depends on the browser 5 && 2 = 2 2 && 5 = 5 5 || 0 = 5 0 || 5 = 5
Summary
There are three conversions in JavaScript, which depend on the context:
- String: output, uses
toString
. - Numeric: maths, operators, uses
valueOf
->toString
. - Boolean: converts according to the table.
That’s different from most other programmer languages, But simple when you get it.
P.S. Actually, the conversion is a bit more sophisticated than described here. I’ve left out a good bit of complexity to concentrate on how it really works.
For a maximally precise conversion algorithms, refer to the specification: ECMA-262 5th ed., especially 11.8.5 (relational comparison), and 11.9.3 (equality comparison) and 9.1 (toPrimitive) and 9.3 (toNumber).