renovations

This commit is contained in:
Ilya Kantor 2015-01-14 10:23:45 +03:00
parent c7d4c7e3ff
commit e1948130f6
170 changed files with 1496 additions and 1161 deletions

View file

@ -0,0 +1,15 @@
Если с одной стороны -- объект, а с другой -- нет, то сначала приводится объект.
В данном случае сравнение означает численное приведение. У массивов нет `valueOf`, поэтому вызывается `toString`, который возвращает список элементов через запятую.
В данном случае, элемент только один - он и возвращается. Так что `['x']` становится `'x'`. Получилось `'x' == 'x'`, верно.
P.S.
По той же причине верны равенства:
```js
//+ run
alert( ['x','y'] == 'x,y' ); // true
alert( [] == '' ); // true
```

View file

@ -0,0 +1,11 @@
# ['x'] == 'x'
[importance 5]
Почему результат `true` ?
```js
//+ run
alert( ['x'] == 'x' );
```

View file

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

View file

@ -0,0 +1,24 @@
# Преобразование
[importance 5]
Объявлен объект с `toString` и `valueOf`.
Какими будут результаты `alert`?
```js
var foo = {
toString: function () {
return 'foo';
},
valueOf: function () {
return 2;
}
};
alert(foo);
alert(foo + 1);
alert(foo + "3");
```
Подумайте, прежде чем ответить.

View file

@ -0,0 +1,34 @@
# Ответ по первому равенству
Два объекта равны только тогда, когда это один и тот же объект.
В первом равенстве создаются два массива, это разные объекты, так что они неравны.
# Ответ по второму равенству
<ol>
<li>Первым делом, обе части сравнения вычисляются. Справа находится `![]`. Логическое НЕ `'!'` преобразует аргумент к логическому типу. Массив является объектом, так что это `true`. Значит, правая часть становится `![] = !true = false`. Так что получили:
```js
alert( [] == false );
```
</li>
<li>Проверка равенства между объектом и примитивом вызывает численное преобразование объекта.
У массива нет `valueOf`, сработает `toString` и преобразует массив в список элементов, то есть - в пустую строку:
```js
alert( '' == false );
```
</li>
<li>Сравнение различных типов вызывает численное преобразование слева и справа:
```js
alert( 0 == 0 );
```
Теперь результат очевиден.
</li>
</ol>

View file

@ -0,0 +1,13 @@
# Почему [] == [] неверно, а [ ] == ![ ] верно?
[importance 5]
Почему первое равенство -- неверно, а второе -- верно?
```js
//+ run
alert( [] == [] ); // false
alert( [] == ![] ); // true
```
Какие преобразования происходят при вычислении?

View file

@ -0,0 +1,33 @@
```js
new Date(0) - 0 = 0 // (1)
new Array(1)[0] + "" = "undefined" // (2)
({})[0] = undefined // (3)
[1] + 1 = "11" // (4)
[1,2] + [3,4] = "1,23,4" // (5)
[] + null + 1 = "null1" // (6)
[[0]][0][0] = 0 // (7)
({} + {}) = "[object Object][object Object]" // (8)
```
<ol>
<li>`new Date(0)` -- дата, созданная по миллисекундам и соответствующая 0мс от 1 января 1970 года 00:00:00 UTC. Оператор минус `-` преобразует дату обратно в число миллисекунд, то есть в `0`.</li>
<li>`new Array(num)` при вызове с единственным аргументом-числом создаёт массив данной длины, без элементов. Поэтому его нулевой элемент равен `undefined`, при сложении со строкой получается строка `"undefined"`.</li>
<li>Фигурные скобки -- это создание пустого объекта, у него нет свойства `'0'`. Так что значением будет `undefined`.
Обратите внимание на внешние, круглые скобки. Если их убрать и запустить `{}[0]` в отладочной консоли браузера -- будет `0`, т.к. скобки `{}` будут восприняты как пустой блок кода, после которого идёт массив.</li>
<li>Массив преобразуется в строку `"1"`. Оператор `"+"` при сложении со строкой приводит второй аргумент к строке -- значит будет `"1" + "1" = "11"`.</li>
<li>Массивы приводятся к строке и складываются.</li>
<li>Массив преобразуется в пустую строку `"" + null + 1`, оператор `"+"` видит, что слева строка и преобразует `null` к строке, получается `"null" + 1`, и в итоге `"null1"`.</li>
<li>`[[0]]` -- это вложенный массив `[0]` внутри внешнего `[ ]`. Затем мы берём от него нулевой элемент, и потом еще раз.
Если это непонятно, то посмотрите на такой пример:
```js
alert( [1,[0],2][1] );
```
Квадратные скобки после массива/объекта обозначают не другой массив, а взятие элемента.
</li>
<li>Каждый объект преобразуется к примитиву. У встроенных объектов `Object` нет подходящего `valueOf`, поэтому используется `toString`, так что складываются в итоге строковые представления объектов.</li>
</ol>

View file

@ -0,0 +1,17 @@
# Вопросник по преобразованиям, для объектов
[importance 5]
Подумайте, какой результат будет у выражений ниже. Когда закончите -- сверьтесь с решением.
```js
new Date(0) - 0
new Array(1)[0] + ""
({})[0]
[1] + 1
[1,2] + [3,4]
[] + null + 1
[[0]][0][0]
({} + {})
```

View file

@ -0,0 +1,59 @@
# Подсказка
Чтобы `sum(1)`, а также `sum(1)(2)` можно было вызвать новыми скобками -- результатом `sum` должна быть функция.
Но эта функция также должна уметь превращаться в число. Для этого нужно дать ей соответствующий `valueOf`. А если мы хотим, чтобы и в строковом контексте она вела себя так же -- то `toString`.
# Решение
Функция, которая возвращается `sum`, должна накапливать значение при каждом вызове.
Удобнее всего хранить его в замыкании, в переменной `currentSum`. Каждый вызов прибавляет к ней очередное значение:
```js
//+ run
function sum(a) {
var currentSum = a;
function f(b) {
currentSum += b;
return f;
}
f.toString = function() { return currentSum; };
return f;
}
alert( sum(1)(2) ); // 3
alert( sum(5)(-1)(2) ); // 6
alert( sum(6)(-1)(-2)(-3) ); // 0
alert( sum(0)(1)(2)(3)(4)(5) ); // 15
```
При внимательном взгляде на решение легко заметить, что функция `sum` срабатывает только один раз. Она возвращает функцию `f`.
Затем, при каждом запуске функция `f` добавляет параметр к сумме `currentSum`, хранящейся в замыкании, и возвращает сама себя.
**В последней строчке `f` нет рекурсивного вызова.**
Вот так была бы рекурсия:
```js
function f(b) {
currentSum += b;
return f(); // <-- подвызов
}
```
А в нашем случае, мы просто возвращаем саму функцию, ничего не вызывая.
```js
function f(b) {
currentSum += b;
return f; // <-- не вызывает сама себя, а возвращает ссылку на себя
}
```
Эта `f` используется при следующем вызове, опять возвратит себя, и так сколько нужно раз. Затем, при использовании в строчном или численном контексте -- сработает `toString`, который вернет текущую сумму `currentSum`.

View file

@ -0,0 +1,17 @@
# Сумма произвольного количества скобок
[importance 2]
Напишите функцию `sum`, которая будет работать так:
```js
sum(1)(2) == 3; // 1 + 2
sum(1)(2)(3) == 6; // 1 + 2 + 3
sum(5)(-1)(2) == 6
sum(6)(-1)(-2)(-3) == 0
sum(0)(1)(2)(3)(4)(5) == 15
```
Количество скобок может быть любым.
Пример такой функции для двух аргументов -- есть в решении задачи [](/task/closure-sum).

View file

@ -0,0 +1,238 @@
# Преобразование объектов: toString и valueOf
Ранее, в главе [](/types-conversion) мы рассматривали преобразование типов для примитивов. Теперь добавим в нашу картину мира объекты.
Бывают операции, при которых объект должен быть преобразован в примитив.
[cut]
Например:
<ul>
<li>Строковое преобразование -- если объект выводится через `alert(obj)`.</li>
<li>Численное преобразование -- при арифметических операциях, сравнении с примитивом.</li>
<li>Логическое преобразование -- при `if(obj)` и других логических операциях.</li>
</ul>
Рассмотрим эти преобразования по очереди.
## Логическое преобразование
Проще всего -- с логическим преобразованием.
**Любой объект в логическом контексте -- `true`, даже если это пустой массив `[]` или объект `{}`.**
```js
//+ run
if ( {} && [] ) {
alert("Все объекты - true!"); // alert сработает
}
```
## Строковое преобразование
Строковое преобразование проще всего увидеть, если вывести объект при помощи `alert`:
```js
//+ run
var user = {
firstName: 'Василий'
};
alert(user); // [object Object]
```
Как видно, содержимое объекта не вывелось. Это потому, что стандартным строковым представлением пользовательского объекта является строка `"[object Object]"`.
Такой вывод объекта не содержит интересной информации. Поэтому имеет смысл его поменять на что-то более полезное.
**Если в объекте присутствует метод `toString`, который возвращает примитив, то он используется для преобразования.**
```js
//+ run
var user = {
firstName: 'Василий',
*!*toString:*/!* function() {
return 'Пользователь ' + this.firstName;
}
};
alert( user ); // Пользователь Василий
```
[smart header="Результатом `toString` может быть любой примитив"]
Метод `toString` не обязан возвращать именно строку.
Его результат может быть любого примитивного типа. Например, это может быть число, как в примере ниже:
```js
//+ run
var obj = {
toString: function() { return 123; }
};
alert(obj); // 123
```
Поэтому мы и называем его здесь *"строковое преобразование"*, а не "преобразование к строке".
[/smart]
Все объекты, включая встроенные, имеют свои реализации метода `toString`, например:
```js
//+ run
alert( [1,2] ); // toString для массивов выводит список элементов "1,2"
alert( new Date ); // toString для дат выводит дату в виде строки
alert( function() { } ); // toString для функции выводит её код
```
## Численное преобразование
Для численного преобразования объекта используется метод `valueOf`, а если его нет -- то `toString`:
```js
//+ run
var room = {
number: 777,
valueOf: function() { return this.number; },
toString: function() { return this.number; }
};
alert( +room ); // 777, *!*вызвался valueOf*/!*
delete room.valueOf; // *!*valueOf удалён*/!*
alert( +room ); // 777, *!*вызвался toString*/!*
```
Метод `valueOf` обязан возвращать примитивное значение, иначе его результат будет проигнорирован. При этом -- не обязательно числовое.
[smart header="У большинства объектов нет `valueOf`"]
У большинства встроенных объектов такого `valueOf` нет, поэтому численное и строковое преобразования для них работают одинаково.
Исключением является объект `Date`, который поддерживает оба типа преобразований:
```js
//+ run
alert( new Date() ); // toString: Дата в виде читаемой строки
alert( +new Date() ); // valueOf: кол-во миллисекунд, прошедших с 01.01.1970
```
[/smart]
[smart header="Детали спецификации"]
Если посмотреть в стандарт, то в пункте [15.2.4.4](http://es5.github.com/x15.2.html#x15.2.4.4) говорится о том, что `valueOf` есть у любых объектов. Но он ничего не делает, просто возвращает сам объект (не-примитивное значение!), а потому игнорируется.
[/smart]
## Две стадии преобразования
Итак, объект преобразован в примитив при помощи `toString` или `valueOf`. Далее, вполне возможно,
Если необходимо, что полученный из объекта примитив будет преобразован дальше, уже по правилам для примитивов.
Например, рассмотрим применение к объекту операции `==`:
```js
//+ run
var obj = {
valueOf: function() { return 1; }
};
alert(obj == true); // true
```
Объект `obj` был сначала преобразован в примитив, используя численное преобразование, получилось `1 == true`.
Далее, так как значения всё ещё разных типов, применяются правила преобразования примитивов, результат: `true`.
То же самое -- при сложении с объектом при помощи `+`:
```js
//+ run
var obj = {
valueOf: function() { return 1; }
};
alert(obj + "test"); // 1test
```
Или вот, для разности объектов:
```js
//+ run
var a = {
valueOf: function() { return "1"; }
};
var b = {
valueOf: function() { return "2"; }
};
alert(a - b); // "1" - "2" = -1
```
[warn header="Исключение: `Date`"]
Объект `Date`, по историческим причинам, является исключением.
Бинарный оператор плюс `+` обычно использует числовое преобразование, но в случае с `Date` -- строковое:
```js
//+ run
// бинарный вариант, строчное преобразование
alert( new Date + "" ); // "строка даты"
// унарный вариант, как и - * /, приводит к числу
alert( +new Date ); // число миллисекунд
```
[/warn]
[warn header="Как испугать Java-разработчика"]
В языке Java (это не JavaScript, другой язык, здесь приведён для примера) логические значения можно создавать, используя синтаксис `new Boolean(true/false)`, например `new Boolean(true)`.
В JavaScript тоже есть подобная возможность, которая возвращает "объектную обёртку" для логического значения.
Эта возможность давно существует лишь для совместимости, она и не используется на практике, поскольку приводит к странным результатам. Некоторые из них могут сильно удивить человека, не привыкшего к JavaScript, например:
```js
//+ run
var value = new Boolean(false);
if ( value ) {
alert(true); // сработает!
}
```
Почему запустился `alert`? Ведь в `if` находится `false`... Проверим:
```js
//+ run
var value = new Boolean(false);
*!*
alert(value); // выводит false, все ок..
*/!*
if ( value ) {
alert(true); // ..но тогда почему выполняется alert в if ?!?
}
```
Дело в том, что `new Boolean` -- это не примитивное значение, а объект. Поэтому в логическом контексте он преобразуется к`true`, в результате работает первый пример.
А второй пример вызывает `alert`, который преобразует объект к строке, и он становится `"false"`.
**В JavaScript вызовы `new Boolean/String/Number` не используются, а используются простые вызовы соответствующих функций, они преобразуют значение в примитив нужного типа, например `Boolean(val) === !!val`.**
[/warn]
## Итого
<ul>
<li>В логическом контексте объект -- всегда `true`.</li>
<li>При строковом преобразовании объекта используется его метод `toString`. Он должен возвращать примитивное значение, причём не обязательно именно строку.
</li>
<li>Для численного преобразования используется метод `valueOf`, который также может возвратить любое примитивное значение. У большинства объектов `valueOf` не работает (возвращает сам объект и потому игнорируется), при этом для численного преобразования используется `toString`.</li>
</ul>
Полный алгоритм преобразований есть в спецификации EcmaScript, смотрите пункты [11.8.5](http://es5.github.com/x11.html#x11.8.5), [11.9.3](http://es5.github.com/x11.html#x11.9.3), а также [9.1](http://es5.github.com/x9.html#x9.1) и [9.3](http://es5.github.com/x9.html#x9.3).