Object conversion: toString and valueOf

Lecture




  1. String conversion
  2. Numerical conversion
  3. Transformation to primitive
  4. Total

Earlier, in the chapter Type Conversion for Primitives, we looked at type conversion, focusing on primitives. Now let's add objects to our slender picture of type conversion.

In this case there will be the following changes:

  1. In objects, numerical and string conversions can be redefined.
  2. If the operation requires a primitive value, then the object is first converted to a primitive, and then everything else.
    In this case, a numerical transformation is used to transform to the primitive.

In order to better integrate them into the big picture, consider examples.

String conversion

String conversion is easiest to see if you display an object using alert :

1 var user = {
2    firstName: 'Василий'
3 };
4
5 alert(user); // [object Object]

As you can see, the contents of the object is not displayed. This is because the standard string representation of the user object is the string "[object Object]" .

This output object does not contain interesting information. Therefore, it makes sense to change it to something more useful.

If the object has a toString method that returns a primitive, then it is used for conversion.

01 var user = {
02
03    firstName: 'Василий' ,
04
05    toString: function () {
06      return 'Пользователь ' + this .firstName;
07    }
08 };
09
10 alert( user ); // Пользователь Василий

The result of toString can be any primitive.

The toString method is not required to return exactly the string.

Its result can be of any primitive type. For example, it can be a number, as in the example below:

1 var obj = {
2    toString: function () { return 123; } 123; }
3 };
4
5 alert(obj); // 123

Therefore, we call it here a "string conversion" , and not a "conversion to a string."

All objects, including embedded ones, have their own implementation of the toString method, for example:

1 alert( [1,2] ); // toString для массивов выводит список элементов "1,2"
2 alert( new Date ); // toString для дат выводит дату в виде строки
3 alert( function () { } ); // toString для функции выводит её код

Numerical conversion

For the numerical transformation of an object, the valueOf method is used, and if it is not, then toString :

01 var room = {
02    number: 777,
03
04    valueOf: function () { return this .number; }, .number; },
05    toString: function () { return this .number; } .number; }
06 };
07
08 alert( +room ); // 777, вызвался valueOf
09
10 delete room.valueOf;
11
12 alert( +room ); // 777, вызвался toString

The valueOf must return a primitive value, otherwise its result will be ignored. At the same time - not necessarily numeric.

Most built-in objects do not have such a valueOf , so the numerical and string conversions work the same for them.

The exception is the Date object, which supports both types of transformations:

1 alert( new Date() ); // toString: Дата в виде читаемой строки
2 alert( + new Date() ); // valueOf: кол-во миллисекунд, прошедших с 01.01.1970

Standard valueOf

If you look at the standard, then in 15.2.4.4 valueOf defined for any objects. But he does nothing, just returns the object itself (non-primitive value!), And therefore is ignored.

Transformation to primitive

Most operations that expect a primitive value, when viewed as an object, first convert it to a primitive.

For example, an arithmetic operation or comparison > < >= <= first converts an object into a primitive.

And then there is an operation with primitives, with subsequent transformations possible.

When converting an object to a primitive, a numerical transformation is used.

For example:

1 var obj = {
2    valueOf: function () { return 1; } 1; }
3 };
4
5 // операции сравнения, кроме ===, приводят к примитиву
6 alert(obj == true ); // true, объект приведён к примитиву 1
7
8 // бинарный + приводит к примитиву, а затем складывает
9 alert(obj + "test" ); // 1test

The example below demonstrates that despite the fact that the cast is “numerical” - its result can be any primitive value, not necessarily a number:

1 var a = {
2    valueOf: function () { return "1" ; } ; }
3 };
4 var b = {
5    valueOf: function () { return true ; } ; }
6 };
7
8 alert(a + b); // "1" + true = "1true"

After the object is reduced to a primitive, there may be additional transformations. For example:

1 var a = {
2    valueOf: function () { return "1" ; } ; }
3 };
4 var b = {
5    valueOf: function () { return "2" ; } ; }
6 };
7
8 alert(a - b); // "1" - "2" = -1

Exception: Date

There is an exception: the Date object is converted to a primitive using a string conversion. This can be encountered in the "+" operator:

1 // бинарный вариант, преобразование к примитиву
2 alert( new Date + "" ); // "строка даты"
3
4 // унарный вариант, наравне с - * / и другими приводит к числу
5 alert( + new Date ); // число миллисекунд
This exception is clearly spelled out in the standard and is unique.

How to scare Java developer

In Java, you can create boolean values ​​using the syntax new Boolean(true/false) . You can also convert values ​​to a boolean type by applying new Boolean to them.

JavaScript also has a similar feature that returns an “object wrapper” for a boolean value. This feature is reserved for compatibility and is not used in practice, as it leads to strange results.

For example:

1 var value = new Boolean( false );
2 if ( value ) {
3    alert( true ); // сработает!
4 }

Why did the alert start? After all, if is false ... Check:

1 var value = new Boolean( false );
2
3 alert(value); // выводит false, все ок..
4
5 if ( value ) {
6    alert( true ); // ..но тогда почему выполняется alert в if ?!?
7 }

The fact is that new Boolean is an object. In a logical context, it is certainly true . Therefore, the first example works.

And the second example calls alert , which converts an object to a string, and it becomes "false" .

To convert a value to a boolean type, you need to use a double negative: !!val or a direct call to Boolean(val) .

Total

  • When a object is converted to a string, its toString method is used. It must return a primitive value, and not necessarily just a string.

    The standard states that if there is no toString , or if it returns not a primitive, but an object, then valueOf is called, but usually there is a toString .

  • When numerically converting an object, the valueOf method is used, and if not, then toString . valueOf objects usually do not.
  • With an operation on an object that requires a primitive value, the object is first converted to a primitive. For this, a numerical conversion is used, the exception is the built-in Date object.

The complete transformation algorithm is in the EcmaScript specification, see clauses 11.8.5,11.9.3, as well as 9.1 and 9.3.

Importance: 5

Why is the result true ?

1 alert( [ 'x' ] == 'x' );

Decision

If on the one hand there is an object, and on the other, it does not, then the object is first given.

In this case, the comparison means numerical cast. Arrays have no valueOf , so toString is called, which returns a list of elements separated by commas.

In this case, there is only one element - it is returned. So ['x'] becomes 'x' . It turned out 'x' == 'x' , right.

PS
For the same reason, equality is true:

1 alert( [ 'x' , 'y' ] == 'x,y' ); // true
2 alert( [] == '' ); // true

[Open task in new window]

Importance: 5

An object is declared with toString and valueOf .

What will be the results of the alert ?

01 var foo = {
02      toString: function () {
03          return 'foo' ;
04      },
05      valueOf: function () {
06          return 2;
07      }
08 };
09
10 alert(foo);
11 alert(foo + 1);
12 alert(foo + "3" );

Think before you answer.

Decision

Answers one by one.

alert(foo)
Returns a string representation of the object using toString , i.e. "foo" .
alert(foo + 1)
The '+' operator converts an object to a primitive using valueOf , so the result is: 3 .
alert(foo + '3')
Same as the previous case, the object becomes a primitive 2 . Then there is the addition of 2 + '3' . The operator '+' when adding something with a string results in the second operand to the string, and then applies concatenation, so the result is the string "23" .
[Open task in new window]

Importance: 5

Why is the first equality wrong, and the second right?

1 alert( [] == [] ); // false
2 alert( [] == ![] ); // true
What transformations occur in the calculation?

Decision
First Equality Answer

Two objects are equal only when it is one and the same object.

In the first equality, two arrays are created, they are different objects, so they are unequal.

The answer to the second equality
[Open task in new window]

Importance: 5

Think about the result of the expressions below. When done - check with the decision.

1 new Date(0) - 0
2 new Array(1)[0] + ""
3 ({})[0]
4 [1] + 1
5 [1,2] + [3,4]
6 [] + null + 1
7 [[0]][0][0]
8 ({} + {})

Decision

1 new Date(0) - 0 = 0 // (1)
2 new Array(1)[0] + "" = "undefined" // (2)
3 ({})[0] = undefined // (3)
4 [1] + 1 = "11" // (4)
5 [1,2] + [3,4] = "1,23,4" // (5)
6 [] + null + 1 = "null1" // (6)
7 [[0]][0][0] = 0 // (7)
8 ({} + {}) = "[object Object][object Object]" // (8)

  1. new Date(0) is a date created in milliseconds and corresponding to 0ms from January 1, 1970 00:00:00 UTC. The minus operator converts the date back to the number of milliseconds, that is, to 0 .
  2. new Array(num) when called with a single argument-number creates an array of a given length, without elements. Therefore, its null element is equal to undefined ; when added to a string, the string "undefined" obtained.
  3. Braces are the creation of an empty object, it does not have the property '0' . So the value will be undefined .
    Pay attention to the external, parentheses. If you remove them and run {}[0] in the browser debugging console, it will be 0 , because {} brackets will be perceived as an empty block of code, followed by an array.
  4. The array is converted to the string "1" . The operator "+" when adding with a string leads the second argument to the string - it means there will be "1" + "1" = "11" .
  5. Arrays are converted to a string and added together.
  6. The array is converted to the empty string "" + null + 1 , the operator "+" sees that the string on the left converts null to the string, it turns out "null" + 1 , and as a result "null1" .
  7. [[0]] is the nested array [0] inside the external [ ] . Then we take the zero element from it, and then again.

    If this is not clear, then look at this example:

    alert( [1,[0],2][1] );

    The square brackets after the array / object do not mean a different array, but take the element.

  8. Each object is converted to a primitive. The built-in Objects do not have the appropriate valueOf , so toString used, so the resulting string representations of the objects are added together.
[Open task in new window]

Importance: 2

Write a sum function that will work like this:

1 sum(1)(2) == 3; // 1 + 2
2 sum(1)(2)(3) == 6; // 1 + 2 + 3
3 sum(5)(-1)(2) == 6
4 sum(6)(-1)(-2)(-3) == 0
5 sum(0)(1)(2)(3)(4)(5) == 15

The number of brackets can be any.

An example of such a function for two arguments is in the solution of the Sum by closure problem.

Decision
Solution Step 1

So that sum(1) and sum(1)(2) can be called with new brackets, the result of sum should be a function.

But this function should also be able to turn into a number. To do this, give it the appropriate valueOf . And if we want it to behave in the same way in a string context, then toString .

Solution Step 2

A function that returns a sum should accumulate a value on each call.

The most convenient way is to store it in the closure, in the variable currentSum . Each call adds the next value to it:

01 function sum(a) {
02   
03    var currentSum = a;
04   
05    function f(b) {
06      currentSum += b;
07      return f;
08    }
09   
10    f.toString = function () { return currentSum; }; currentSum; };
11   
12    return f;
13 }
14
15 alert( sum(1)(2) ); // 3
16 alert( sum(5)(-1)(2) ); // 6
17 alert( sum(6)(-1)(-2)(-3) ); // 0
18 alert( sum(0)(1)(2)(3)(4)(5) ); // 15

A close look at the solution makes it easy to see that the sum function only works once. It returns the function f .

Then, each time the function is started, f adds the parameter to the sum of the currentSum stored in the closure, and returns itself.

The last line of f no recursive call.

So that would be a recursion:

1 function f(b) {
2    currentSum += b;
3    return f(); // <-- подвызов
4 }

And in our case, we simply return the function itself, without calling anything.

1 function f(b) {
2    currentSum += b;
3    return f; // <-- не вызывает сама себя, а возвращает ссылку на себя
4 }

This f used the next time it is called, will return itself again, and as many times as necessary. Then, when used in a string or numeric context, it will trigger toString , which will return the current sum of currentSum .


Comments


To leave a comment
If you have any suggestion, idea, thanks or comment, feel free to write. We really value feedback and are glad to hear your opinion.
To reply

Scripting client side JavaScript, jqvery, BackBone

Terms: Scripting client side JavaScript, jqvery, BackBone