Conversion, toString and valueOf

  1. String conversion
    1. The algorithm of Object to String conversion
    2. Custom toString
  2. Numeric conversion
    1. Custom valueOf example
  3. Conversion in equality/comparison tests
  4. Boolean context
  5. Summary

Objects in JavaScript can be converted to primitives in three contexts:

  1. Numeric
  2. String
  3. 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

  1. If toString method exists and returns a primitive, then return it.
    Execution normally stops here, because toString exists on all objects by default.
  2. If valueOf method exists and returns a primitive, then return it.
  3. 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:

  1. If valueOf method exists and returns a primitive, then return it.
  2. Otherwise, if toString method exists and returns a primitive, then return it.
  3. 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 and being a number

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' )

Open solution
Solution

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.

A way to frighten Java programmers.

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

Open solution
Solution
  1. First, the two sides of comparison are evaluated. The right side is ![]. Logical NOT '!' converts to boolean. According to the table, an object [] is true. So, the right side becomes ![] = !true = false. It brings us to:
    [] == false
    
  2. The equality check between an object and a primitive converts the object to primitives in numeric way.

    The array has no valueOf, but has toString which converts it to a comma-delimited list of items. In our case, no items lead to an empty string:

    '' == false
    

  3. 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

Open solution
Solution

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:

  1. String: output, uses toString.
  2. Numeric: maths, operators, uses valueOf -> toString.
  3. 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).

Tutorial

Donate

Donate to this project