renovations
This commit is contained in:
parent
c7d4c7e3ff
commit
e1948130f6
170 changed files with 1496 additions and 1161 deletions
|
@ -0,0 +1,15 @@
|
|||
Если с одной стороны -- объект, а с другой -- нет, то сначала приводится объект.
|
||||
|
||||
В данном случае сравнение означает численное приведение. У массивов нет `valueOf`, поэтому вызывается `toString`, который возвращает список элементов через запятую.
|
||||
|
||||
В данном случае, элемент только один - он и возвращается. Так что `['x']` становится `'x'`. Получилось `'x' == 'x'`, верно.
|
||||
|
||||
P.S.
|
||||
По той же причине верны равенства:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( ['x','y'] == 'x,y' ); // true
|
||||
alert( [] == '' ); // true
|
||||
```
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
# ['x'] == 'x'
|
||||
|
||||
[importance 5]
|
||||
|
||||
Почему результат `true` ?
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( ['x'] == 'x' );
|
||||
```
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
# Первый alert(foo)
|
||||
|
||||
Возвращает строковое представление объекта, используя `toString`, т.е. `"foo"`.
|
||||
|
||||
# Второй alert(foo + 1)
|
||||
Оператор `'+'` преобразует объект к примитиву, используя `valueOf`, так что результат: `3`.
|
||||
|
||||
# Третий alert(foo + '3')
|
||||
|
||||
То же самое, что и предыдущий случай, объект превращается в примитив `2`. Затем происходит сложение `2 + '3'`. Оператор `'+'` при сложении чего-либо со строкой приводит и второй операнд к строке, а затем применяет конкатенацию, так что результат -- строка `"23"`.
|
|
@ -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");
|
||||
```
|
||||
|
||||
Подумайте, прежде чем ответить.
|
|
@ -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>
|
|
@ -0,0 +1,13 @@
|
|||
# Почему [] == [] неверно, а [ ] == ![ ] верно?
|
||||
|
||||
[importance 5]
|
||||
|
||||
Почему первое равенство -- неверно, а второе -- верно?
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( [] == [] ); // false
|
||||
alert( [] == ![] ); // true
|
||||
```
|
||||
|
||||
Какие преобразования происходят при вычислении?
|
|
@ -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>
|
|
@ -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]
|
||||
({} + {})
|
||||
```
|
||||
|
|
@ -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`.
|
|
@ -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).
|
238
1-js/6-objects-more/2-object-conversion/article.md
Normal file
238
1-js/6-objects-more/2-object-conversion/article.md
Normal 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).
|
||||
|
||||
|
||||
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue