Вам бонус- начислено 1 монета за дневную активность. Сейчас у вас 1 монета

Преобразование объектов: toString и valueOf

Лекция



Привет, сегодня поговорим про преобразование объектов, обещаю рассказать все что знаю. Для того чтобы лучше понимать что такое преобразование объектов, tostring, valueof , настоятельно рекомендую прочитать все из категории Выполнение скриптов на стороне клиента JavaScript, jqvery, JS фреймворки (Frontend).


  1. Строковое преобразование
  2. Численное преобразование
  3. Преобразование в примитив
  4. Итого

Ранее, в главе Преобразование типов для примитивов мы рассматривали преобразование типов, уделяя основное внимание примитивам. Теперь добавим в нашу стройную картину преобразования типов объекты.

При этом будут следующие изменения:

  1. В объектах численное и строковое преобразования можно переопределить.
  2. Если операция требует примитивное значение, то сначала объект преобразуется к примитиву, а затем — все остальное.
    При этом для преобразования к примитиву используется численное преобразование.

Для того, чтобы лучше интегрировать их в общую картину, рассмотрим примеры.

Строковое преобразование

Строковое преобразование проще всего увидеть, если вывести объект при помощи alert:

 

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

 

Как видно, содержимое объекта не вывелось. Это потому, что стандартным строковым представлением пользовательского объекта является строка "[object Object]".

Такой вывод объекта не содержит интересной информации. Поэтому имеет смысл его поменять на что-то более полезное.

Если в объекте присутствует метод toString, который возвращает примитив, то он используется для преобразования.

 

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

 

 

Результатом toString может быть любой примитив

Метод toString не обязан возвращать именно строку.

Его результат может быть любого примитивного типа. Например, это может быть число, как в примере ниже:

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

 

Поэтому мы и называем его здесь «строковое преобразование», а не «преобразование к строке».

 

Все объекты, включая встроенные, имеют свои реализации метода toString, например:

 

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

 

Численное преобразование

Для численного преобразования объекта используется метод valueOf, а если его нет — тоtoString:

 

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

 

Метод valueOf обязан возвращать примитивное значение, иначе его результат будет проигнорирован. При этом — не обязательно числовое.

У большинства встроенных объектов такого valueOf нет, поэтому численное и строковое преобразования для них работают одинаково.

Исключением является объект Date, который поддерживает оба типа преобразований:

 

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

 

 

Стандартный valueOf

Если посмотреть в стандарт, то в 15.2.4.4 определен valueOf для любых объектов. Но он ничего не делает, просто возвращает сам объект (не-примитивное значение!), а потому игнорируется.

 

Преобразование в примитив

Большинство операций, которые ожидают примитивное значение, при виде объекта сначала преобразуют его к примитиву.

Например, арифметическая операция или сравнение > < >= <= сначала преобразует объект в примитив.

А затем уже идет операция с примитивами, при этом возможны последующие преобразования.

При приведении объекта к примитиву используется численное преобразование.

Например:

 

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

 

Пример ниже демонстрирует, что несмотря на то, что приведение «численное» — его результатом может быть любое примитивное значение, не обязательно число:

 

   
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"

 

После того, как объект приведен к примитиву, могут быть дополнительные преобразования. Об этом говорит сайт https://intellect.icu . Например:

 

   
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

 

 

Исключение: Date

Существует исключение: объект Date преобразуется в примитив, используя строковое преобразование. С этим можно столкнуться в операторе "+":

 

   
1 // бинарный вариант, преобразование к примитиву
2 alert( new Date + "" ); // "строка даты"
3  
4 // унарный вариант, наравне с - * / и другими приводит к числу
5 alert( +new Date ); // число миллисекунд
Это исключение явно прописано в стандарте и является единственным в своем роде.

 

 

 

Как испугать Java-разработчика

В языке Java логические значения можно создавать, используя синтаксисnew Boolean(true/false). Также можно преобразовывать значения к логическому типу, применяя к ним new Boolean.

В JavaScript тоже есть подобная возможность, которая возвращает «объектную обертку» для логического значения. Эта возможность сохраняется для совместимости и не используется на практике, поскольку приводит к странным результатам.

Например:

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

 

Почему запустился alert? Ведь в if находится false… Проверим:

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

 

Дело в том, что new Boolean - это объект. В логическом контексте он, безусловно,true. Поэтому работает первый пример.

А второй пример вызывает alert, который преобразует объект к строке, и он становится "false".

Чтобы преобразовать значение к логическому типу, нужно использовать двойное отрицание: !!val или прямой вызов Boolean(val).

 

Итого

  • При строковом преобразовании объекта используется его метод toString. Он должен возвращать примитивное значение, причем не обязательно именно строку.

    В стандарте прописано, что если toString нет, или он возвращает не примитив, а объект, то вызывается valueOf, но обычно toString есть.

  • При численном преобразовании объекта используется метод valueOf, а если его нет, тоtoString. У встроенных объектов valueOf обычно нет.
  • При операции над объектом, которая требует примитивное значение, объект первым делом преобразуется в примитив. Для этого используется численное преобразование, исключение — встроенный объект Date.

Полный алгоритм преобразований есть в спецификации EcmaScript, смотрите пункты 11.8.5,11.9.3, а также 9.1 и 9.3.

 

Важность: 5

Почему результат true ?

 

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

 

Решение

Если с одной стороны — объект, а с другой — нет, то сначала приводится объект.

В данном случае сравнение означает численное приведение. У массивов нетvalueOf, поэтому вызывается toString, который возвращает список элементов через запятую.

В данном случае, элемент только один - он и возвращается. Так что ['x'] становится'x'. Получилось 'x' == 'x', верно.

P.S.
По той же причине верны равенства:

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

 

[Открыть задачу в новом окне]

Важность: 5

Объявлен объект с toString и valueOf.

Какими будут результаты 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");

 

Подумайте, прежде чем ответить.

Решение

Ответы, один за другим.

alert(foo)
Возвращает строковое представление объекта, используя toString, т.е. "foo".
alert(foo + 1)
Оператор '+' преобразует объект к примитиву, используя valueOf, так что результат: 3.
alert(foo + '3')
То же самое, что и предыдущий случай, объект превращается в примитив 2. Затем происходит сложение 2 + '3'. Оператор '+' при сложении чего-либо со строкой приводит и второй операнд к строке, а затем применяет конкатенацию, так что результат — строка "23".
[Открыть задачу в новом окне]

 

 

Важность: 5

Почему первое равенство — неверно, а второе — верно?

 

   
1 alert( [] == [] );  // false
2 alert( [] == ![] ); // true
Какие преобразования происходят при вычислении?

 

Решение
Ответ по первому равенству

Два объекта равны только тогда, когда это один и тот же объект.

В первом равенстве создаются два массива, это разные объекты, так что они неравны.

Ответ по второму равенству
[Открыть задачу в новом окне]

Важность: 5

Подумайте, какой результат будет у выражений ниже. Когда закончите — сверьтесь с решением.

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 ({} + {})

 

Решение

 

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) — дата, созданная по миллисекундам и соответствующая 0мс от 1 января 1970 года 00:00:00 UTC. Оператор минус - преобразует дату обратно в число миллисекунд, то есть в 0.
  2. new Array(num) при вызове с единственным аргументом-числом создает массив данной длины, без элементов. Поэтому его нулевой элемент равенundefined, при сложении со строкой получается строка "undefined".
  3. Фигурные скобки — это создание пустого объекта, у него нет свойства '0'. Так что значением будет undefined.
    Обратите внимание на внешние, круглые скобки. Если их убрать и запустить{}[0] в отладочной консоли браузера — будет 0, т.к. скобки {} будут восприняты как пустой блок кода, после которого идет массив.
  4. Массив преобразуется в строку "1". Оператор "+" при сложении со строкой приводит второй аргумент к строке — значит будет "1" + "1" = "11".
  5. Массивы приводятся к строке и складываются.
  6. Массив преобразуется в пустую строку "" + null + 1, оператор "+" видит, что слева строка и преобразует null к строке, получается "null" + 1, и в итоге"null1".
  7. [[0]] — это вложенный массив [0] внутри внешнего [ ]. Затем мы берем от него нулевой элемент, и потом еще раз.

    Если это непонятно, то посмотрите на такой пример:

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

     

    Квадратные скобки после массива/объекта обозначают не другой массив, а взятие элемента.

  8. Каждый объект преобразуется к примитиву. У встроенных объектов Object нет подходящего valueOf, поэтому используется toString, так что складываются в итоге строковые представления объектов.
[Открыть задачу в новом окне]

 

 

Важность: 2

Напишите функцию sum, которая будет работать так:

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

 

Количество скобок может быть любым.

Пример такой функции для двух аргументов — есть в решении задачи Сумма через замыкание.

Решение
Решение, шаг 1

Чтобы sum(1), а также sum(1)(2) можно было вызвать новыми скобками — результатом sum должна быть функция.

Но эта функция также должна уметь превращаться в число. Для этого нужно дать ей соответствующий valueOf. А если мы хотим, чтобы и в строковом контексте она вела себя так же — то toString.

Решение, шаг 2

Функция, которая возвращается sum, должна накапливать значение при каждом вызове.

Удобнее всего хранить его в замыкании, в переменной currentSum. Каждый вызов прибавляет к ней очередное значение:

 

   
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; };
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

 

При внимательном взгляде на решение легко заметить, что функция sum срабатывает только один раз. Она возвращает функцию f.

Затем, при каждом запуске функция f добавляет параметр к сумме currentSum, хранящейся в замыкании, и возвращает сама себя.

В последней строчке f нет рекурсивного вызова.

Вот так была бы рекурсия:

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

 

А в нашем случае, мы просто возвращаем саму функцию, ничего не вызывая.

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

 

Эта f используется при следующем вызове, опять возвратит себя, и так сколько нужно раз. Затем, при использовании в строчном или численном контексте — сработает toString, который вернет текущую сумму currentSum.

К сожалению, в одной статье не просто дать все знания про преобразование объектов. Но я - старался. Если ты проявишь интерес к раскрытию подробностей,я обязательно напишу продолжение! Надеюсь, что теперь ты понял что такое преобразование объектов, tostring, valueof и для чего все это нужно, а если не понял, или есть замечания, то не стесняйся, пиши или спрашивай в комментариях, с удовольствием отвечу. Для того чтобы глубже понять настоятельно рекомендую изучить всю информацию из категории Выполнение скриптов на стороне клиента JavaScript, jqvery, JS фреймворки (Frontend)

создано: 2014-10-07
обновлено: 2021-03-13
132586



Рейтиг 9 of 10. count vote: 2
Вы довольны ?:


Поделиться:

Найди готовое или заработай

С нашими удобными сервисами без комиссии*

Как это работает? | Узнать цену?

Найти исполнителя
$0 / весь год.
  • У вас есть задание, но нет времени его делать
  • Вы хотите найти профессионала для выплнения задания
  • Возможно примерение функции гаранта на сделку
  • Приорететная поддержка
  • идеально подходит для студентов, у которых нет времени для решения заданий
Готовое решение
$0 / весь год.
  • Вы можите продать(исполнителем) или купить(заказчиком) готовое решение
  • Вам предоставят готовое решение
  • Будет предоставлено в минимальные сроки т.к. задание уже готовое
  • Вы получите базовую гарантию 8 дней
  • Вы можете заработать на материалах
  • подходит как для студентов так и для преподавателей
Я исполнитель
$0 / весь год.
  • Вы профессионал своего дела
  • У вас есть опыт и желание зарабатывать
  • Вы хотите помочь в решении задач или написании работ
  • Возможно примерение функции гаранта на сделку
  • подходит для опытных студентов так и для преподавателей



Комментарии


Оставить комментарий
Если у вас есть какое-либо предложение, идея, благодарность или комментарий, не стесняйтесь писать. Мы очень ценим отзывы и рады услышать ваше мнение.
To reply

Выполнение скриптов на стороне клиента JavaScript, jqvery, JS фреймворки (Frontend)

Термины: Выполнение скриптов на стороне клиента JavaScript, jqvery, JS фреймворки (Frontend)