This commit is contained in:
Ilya Kantor 2014-11-16 01:40:20 +03:00
parent 962caebbb7
commit 87bf53d076
1825 changed files with 94929 additions and 0 deletions

View file

@ -0,0 +1,9 @@
function ucFirst(str) {
var newStr = str.charAt(0).toUpperCase();
for(var i=1; i<str.length; i++) {
newStr += str.charAt(i);
}
return newStr;
}

View file

@ -0,0 +1,9 @@
describe("ucFirst", function() {
it('делает первый символ заглавным', function() {
assert.strictEqual( ucFirst("вася"), "Вася" );
});
it('для пустой строки возвращает пустую строку', function() {
assert.strictEqual( ucFirst(""), "" );
});
});

View file

@ -0,0 +1,20 @@
Мы не можем просто заменить первый символ, т.к. строки в JavaScript неизменяемы.
Но можно пересоздать строку на основе существующей, с заглавным первым символом:
```js
//+ run
function ucFirst(str) {
var newStr = str.charAt(0).toUpperCase();
for(var i=1; i<str.length; i++) {
newStr += str.charAt(i);
}
return newStr;
}
alert( ucFirst("вася") );
```
P.S. Возможны и более короткие решения, использующие методы для работы со строками, которые мы пройдём далее.

View file

@ -0,0 +1,12 @@
# Сделать первый символ заглавным
[importance 5]
Напишите функцию `ucFirst(str)`, которая возвращает строку `str` с заглавным первым символом, например:
```js
ucFirst("вася") == "Вася";
ucFirst("") == ""; // нет ошибок при пустой строке
```
P.S. В JavaScript нет встроенного метода для этого. Создайте функцию, используя `toUpperCase()` и `charAt()`.

View file

@ -0,0 +1,5 @@
function checkSpam(str) {
var lowerStr = str.toLowerCase();
return !!(~lowerStr.indexOf('viagra') || ~lowerStr.indexOf('xxx'));
}

View file

@ -0,0 +1,13 @@
describe("checkSpam", function() {
it('считает спамом "buy ViAgRA now"', function() {
assert.isTrue( checkSpam('buy ViAgRA now') );
});
it('считает спамом "free xxxxx"', function() {
assert.isTrue( checkSpam('free xxxxx') );
});
it('не считает спамом "innocent rabbit"', function() {
assert.isFalse( checkSpam('innocent rabbit') );
});
});

View file

@ -0,0 +1,17 @@
Метод `indexOf` ищет совпадение с учетом регистра. То есть, в строке `'xXx'` он не найдет `'XXX'`.
Для проверки приведем к нижнему регистру и строку `str` а затем уже будем искать.
```js
//+ run
function checkSpam(str) {
var lowerStr = str.toLowerCase();
return !!(~lowerStr.indexOf('viagra') || ~lowerStr.indexOf('xxx'));
}
alert( checkSpam('buy ViAgRA now') );
alert( checkSpam('free xxxxx') );
alert( checkSpam("innocent rabbit") );
```

View file

@ -0,0 +1,14 @@
# Проверьте спам
[importance 5]
Напишите функцию `checkSpam(str)`, которая возвращает `true`, если строка `str` содержит 'viagra' or 'XXX', а иначе `false`.
Функция должна быть нечувствительна к регистру:
```js
checkSpam('buy ViAgRA now') == true
checkSpam('free xxxxx') == true
checkSpam("innocent rabbit") == false
```

View file

@ -0,0 +1,4 @@
function truncate(str, maxlength) {
return (str.length > maxlength) ?
str.slice(0, maxlength - 3) + '...' : str;
}

View file

@ -0,0 +1,16 @@
describe("truncate", function() {
it("обрезает строку до указанной длины (включая троеточие)", function() {
assert.equal(
truncate("Вот, что мне хотелось бы сказать на эту тему:", 20),
"Вот, что мне хоте..."
);
});
it("не меняет короткие строки", function() {
assert.equal(
truncate("Всем привет!", 20),
"Всем привет!"
);
});
});

View file

@ -0,0 +1,28 @@
Так как окончательная длина строки должна быть `maxlength`, то нужно её обрезать немного короче, чтобы дать место для троеточия.
```js
//+ run
function truncate(str, maxlength) {
if (str.length > maxlength) {
return str.slice(0, maxlength - 3) + '...';
// итоговая длина равна maxlength
}
return str;
}
alert(truncate("Вот, что мне хотелось бы сказать на эту тему:", 20));
alert(truncate("Всем привет!", 20));
```
Можно было бы написать этот код ещё короче:
```js
//+ run
function truncate(str, maxlength) {
return (str.length > maxlength) ?
str.slice(0, maxlength - 3) + '...' : str;
}
```
P.S. Кстати, в кодироке Unicode существует специальный символ "троеточие": `…` (HTML: `&hellip;`), который можно использовать вместо трёх точек. Если его использовать, то можно отрезать только один символ.

View file

@ -0,0 +1,17 @@
# Усечение строки
[importance 5]
Создайте функцию `truncate(str, maxlength)`, которая проверяет длину строки `str`, и если она превосходит `maxlength` -- заменяет конец `str` на `"..."`, так чтобы ее длина стала равна `maxlength`.
Результатом функции должна быть (при необходимости) усечённая строка.
Например:
```js
truncate("Вот, что мне хотелось бы сказать на эту тему:", 20) = "Вот, что мне хотело..."
truncate("Всем привет!", 20) = "Всем привет!"
```
Эта функция имеет применение в жизни. Она используется, чтобы усекать слишком длинные темы сообщений.

View file

@ -0,0 +1,538 @@
# Строки
В JavaScript любые текстовые данные являются строками. Не существует отдельного типа "символ", который есть в ряде других языков.
Внутренним форматом строк, вне зависимости от кодировки страницы, является [Юникод (Unicode)](http://ru.wikipedia.org/wiki/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4).
[cut]
## Создание строк
Строки создаются при помощи двойных или одинарных кавычек:
```js
var text = "моя строка";
var anotherText = 'еще строка';
var str = "012345";
```
В JavaScript **нет разницы между двойными и одинарными кавычками**.
### Специальные символы
Строки могут содержать специальные символы. Самый часто используемый из таких символов -- это *перевод строки*.
Он обозначается как `\n`, например:
```js
//+ run
alert('Привет\nМир'); // выведет "Мир" на новой строке
```
Есть и более редкие символы, вот их список:
<table>
<CAPTION>Специальные символы</CAPTION>
<tr><th>Символ</th><th>Описание</th>
<tr><td>\b</td><td>Backspace</td></tr>
<tr><td>\f</td><td>Form feed</td></tr>
<tr><td>\n</td><td>New line</td></tr>
<tr><td>\r</td><td>Carriage return</td></tr>
<tr><td>\t</td><td>Tab</td></tr>
<tr><td>\uNNNN</td><td>Символ в кодировке Юникод с шестнадцатеричным кодом `NNNN`. Например, `\u00A9` -- юникодное представление символа копирайт &#xA9;
</td></tr>
</table>
### Экранирование специальных символов
Если строка в одинарных кавычках, то внутренние одинарные кавычки внутри должны быть *экранированы*, то есть снабжены обратным слешем `\'`, вот так:
```js
var str = '*!*I\'m*/!* a JavaScript programmer';
```
В двойных кавычках -- экранируются внутренние двойные:
```js
//+ run
var str = "I'm a JavaScript \"programmer\" ";
alert(str);
```
Экранирование служит исключительно для правильного восприятия строки JavaScript. **В памяти строка будет содержать сам символ без `'\'`**. Вы можете увидеть это, запустив пример выше.
Сам символ обратного слэша `'\'` является служебным, поэтому всегда экранируется, т.е пишется как `\\`:
```js
//+ run
var str = ' символ \\ ';
alert(str); // символ \
```
Заэкранировать можно любой символ. Если он не специальный, то ничего не произойдёт:
```js
//+ run
alert( "\a" ); // a
// идентично alert( "a" );
```
## Методы и свойства
Здесь мы рассмотрим методы и свойства строк, с некоторыми из которых мы знакомились ранее, в главе [](/properties-and-methods).
### Длина length
Одно из самых частых действий со строкой -- это получение ее длины:
```js
//+ run
var str = "My\n"; // 3 символа. Третий - перевод строки
alert(str.length); // 3
```
### Доступ к символам
Чтобы получить символ, используйте вызов `charAt(позиция)`. Первый символ имеет позицию `0`:
```js
//+ run
var str = "jQuery";
alert( str.charAt(0) ); // "j"
```
В JavaScript **нет отдельного типа "символ"**, так что `charAt` возвращает строку, состоящую из выбранного символа.
[smart]
В современных браузерах (не IE7-) для доступа к символу можно также использовать квадратные скобки:
```js
//+ run
var str = "Я - современный браузер!";
alert(str[0]); // "Я", IE8+
```
Разница между этим способом и `charAt` заключается в том, что если символа нет -- `charAt` выдает пустую строку, а скобки -- `undefined`:
```js
//+ run
alert( "".charAt(0) ); // пустая строка
alert( ""[0] ); // undefined, IE8+
```
[/smart]
[warn header="Вызов метода -- всегда со скобками"]
Обратите внимание, `str.length` -- это *свойство* строки, а `str.charAt(pos)` -- *метод*, т.е. функция.
Обращение к методу всегда идет со скобками, а к свойству -- без скобок.
[/warn]
### Изменения строк
Строки в JavaScript нельзя изменять. Можно прочитать символ, но нельзя заменить его. Как только строка создана -- она такая навсегда.
Чтобы это обойти, создаётся новая строка и присваивается в переменную вместо старой:
```js
//+ run
var str = "строка";
str = str.charAt(3) + str.charAt(4) + str.charAt(5);
alert(str); // ока
```
### Смена регистра
Методы `toLowerCase()` и `toUpperCase()` меняют регистр строки на нижний/верхний:
```js
//+ run
alert( "Интерфейс".toUpperCase() ); // ИНТЕРФЕЙС
```
Пример ниже получает первый символ и приводит его к нижнему регистру:
```js
alert( "Интерфейс".charAt(0).toLowerCase() ); // 'и'
```
### Поиск подстроки
Для поиска подстроки есть метод <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String/indexOf">indexOf(подстрока[, начальная_позиция])</a>.
Он возвращает позицию, на которой находится `подстрока` или `-1`, если ничего не найдено. Например:
```js
//+ run
var str = "Widget with id";
alert( str.indexOf("Widget") ); // 0, т.к. "Widget" найден прямо в начале str
alert( str.indexOf("id") ); // 1, т.к. "id" найден, начиная с позиции 1
alert( str.indexOf("Lalala") ); // -1, подстрока не найдена
```
Необязательный второй аргумент позволяет искать, начиная с указанной позиции. Например, первый раз `"id"` появляется на позиции `1`. Чтобы найти его следующее появление -- запустим поиск с позиции `2`:
```js
//+ run
var str = "Widget with id";
alert( str.indexOf("id", 2) ) // 12, поиск начат с позиции 2
```
Также существует аналогичный метод <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String/lastIndexOf">lastIndexOf</a>, который ищет не с начала, а с конца строки.
[smart]
Для красивого вызова `indexOf` применяется побитовый оператор НЕ `'~'`.
Дело в том, что вызов `~n` эквивалентен выражению `-(n+1)`, например:
```js
//+ run
alert( ~2 ); // -(2+1) = -3
alert( ~1 ); // -(1+1) = -2
alert( ~0 ); // -(0+1) = -1
*!*
alert( ~-1 ); // -(-1+1) = 0
*/!*
```
Как видно, `~n` -- ноль только в случае, когда `n == -1`.
То есть, проверка `if ( ~str.indexOf(...) )` означает, что результат `indexOf` отличен от `-1, т.е. совпадение есть.
Вот так:
```js
//+ run
var str = "Widget";
if( ~str.indexOf("get") ) {
alert('совпадение есть!');
}
```
Вообще, использовать возможности языка неочевидным образом не рекомендуется, поскольку ухудшает читаемость кода.
Однако, в данном случае, все в порядке. Просто запомните: `'~'` читается как "не минус один", а `"if ~str.indexOf"` читается как `"если найдено"`.
[/smart]
### Поиск всех вхождений
Чтобы найти все вхождения подстроки, нужно запустить `indexOf` в цикле. Как только получаем очередную позицию -- начинаем следующий поиск со следующей.
Пример такого цикла:
```js
//+ run
var str = "Ослик Иа-Иа посмотрел на виадук"; // ищем в этой строке
var target = "Иа"; // цель поиска
var pos = 0;
while(true) {
var foundPos = str.indexOf(target, pos);
if (foundPos == -1) break;
alert(foundPos); // нашли на этой позиции
pos = foundPos + 1; // продолжить поиск со следующей
}
```
Такой цикл начинает поиск с позиции `0`, затем найдя подстроку на позиции `foundPos`, следующий поиск продолжит с позиции `pos = foundPos+1`, и так далее, пока что-то находит.
Впрочем, тот же алгоритм можно записать и короче:
```js
//+ run
var str = "Ослик Иа-Иа посмотрел на виадук"; // ищем в этой строке
var target = "Иа"; // цель поиска
*!*
var pos = -1;
while ( (pos = str.indexOf(target, pos+1)) != -1) {
alert(pos);
}
*/!*
```
### Взятие подстроки: substr, substring, slice.
В JavaScript существуют целых 3 (!) метода для взятия подстроки, с небольшими отличиями между ними.
<dl>
<dt>`substring(start [, end])`
<dd>
Метод `substring(start, end)` возвращает подстроку с позиции `start` до, но не включая `end`.
```js
//+ run
var str = "*!*s*/!*tringify";
alert(str.substring(0,1)); // "s", символы с позиции 0 по 1 не включая 1.
```
Если аргумент `end` отсутствует, то идет до конца строки:
```js
//+ run
var str = "st*!*ringify*/!*";
alert(str.substring(2)); // ringify, символы с позиции 2 до конца
```
</dd>
<dt>`substr(start [, length])`</dt>
<dd>Первый аргумент имеет такой же смысл, как и в `substring`, а второй содержит не конечную позицию, а количество символов.
```js
//+ run
var str = "st*!*ring*/!*ify";
str = str.substr(2,4); // ring, со 2й позиции 4 символа
alert(str)
```
Если второго аргумента нет -- подразумевается "до конца строки".</dd>
<dt>`slice(start [, end])`</dt>
<dd>Возвращает часть строки от позиции `start` до, но не включая, позиции `end`. Смысл параметров -- такой же как в `substring`.</dd>
</dl>
### Отрицательные аргументы
Различие между `substring` и `slice` -- в том, как они работают с отрицательными и выходящими за границу строки аргументами:
<dl>
<dt>`substring(start, end)`</dt>
<dd>Отрицательные аргументы интерпретируются как равные нулю. Слишком большие значения усекаются до длины строки:
```js
//+ run
alert( "testme".substring(-2) ); // "testme", -2 становится 0
```
Кроме того, если <code>start &gt; end</code>, то аргументы меняются местами, т.е. возвращается участок строки *между* `start` и `end`:
```js
//+ run
alert( "testme".substring(4, -1) ); // "test"
// -1 становится 0 -> получили substring(4, 0)
// 4 > 0, так что аргументы меняются местами -> substring(0, 4) = "test"
```
</dd>
<dt>`slice`</dt>
<dd>Отрицательные значения отсчитываются от конца строки:
```js
//+ run
alert( "testme".slice(-2) ); // "me", от 2 позиции с конца
```
```js
//+ run
alert( "testme".slice(1, -1) ); // "estm", от 1 позиции до первой с конца.
```
Это гораздо более удобно, чем странная логика `substring`.
</dd>
</dl>
Отрицательное значение первого параметра поддерживается в `substr` во всех браузерах, кроме IE8-.
[summary]
Самый удобный метод из списка -- это `slice(start, end)`. Очень забавно, что не `substr`, не `substring`, а именно метод `slice`, название которого наименее очевидно.
Впрочем, `substr(start, length)` тоже можно использовать, но с оглядкой на IE8-, который не поддерживает отрицательный `start`.
[/summary]
## Кодировка Юникод
Как мы знаем, символы сравниваются в алфавитном порядке `'А' < 'Б' < 'В' < ... < 'Я'`.
Но есть несколько странностей..
<ol>
<li>Почему буква `'а'` маленькая больше буквы `'Я'` большой?
```js
//+ run
alert( 'а' > 'Я' ); // true
```
</li><li>
Буква `'ё'` находится в алфавите между `е` и `ж`: <code>абвгде**ё**жз..</code>. Но почему тогда `'ё'` больше `'я'`?
```js
//+ run
alert( 'ё' > 'я' ); // true
```
</li>
</ol>
Чтобы разобраться с этим, обратимся к внутреннему представлению строк в JavaScript.
**Все строки имеют внутреннюю кодировку [Юникод](http://ru.wikipedia.org/wiki/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4).**
Неважно, на каком языке написана страница, находится ли она в windows-1251 или utf-8. Внутри JavaScript-интерпретатора все строки приводятся к единому "юникодному" виду. Каждому символу соответствует свой код.
Есть метод для получения символа по его коду:
<dl>
<dt>String.fromCharCode(code)</dt>
<dd>Возвращает символ по коду `code`:
```js
//+ run
alert( String.fromCharCode(1072) ); // 'а'
```
</dd>
</dl>
...И метод для получения цифрового кода из символа:
<dl>
<dt>str.charCodeAt(pos)</dt>
<dd>Возвращает код символа на позиции `pos`. Отсчет позиции начинается с нуля.
```js
//+ run
alert( "абрикос".charCodeAt(0) ); // 1072, код 'а'
```
</dd>
</dl>
Теперь вернемся к примерам выше. Почему сравнения `'ё' > 'я'` и `'а' > 'Я'` дают такой странный результат?
Дело в том, что **символы сравниваются не по алфавиту, а по коду**. У кого код больше -- тот и больше. В юникоде есть много разных символов. Кириллическим буквам соответствует только небольшая часть из них, подробнее -- [Кириллица в Юникоде](http://ru.wikipedia.org/wiki/%D0%9A%D0%B8%D1%80%D0%B8%D0%BB%D0%BB%D0%B8%D1%86%D0%B0_%D0%B2_%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4%D0%B5).
Выведем отрезок символов юникода с кодами от `1034` до `1113`:
```js
//+ run
var str = '';
for (var i=1034; i<=1113; i++) {
str += String.fromCharCode(i);
}
alert(str);
```
Результат:
<div style="overflow: auto">
<code>ЊЋЌЍЎЏАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяѐёђѓєѕіїјљ</code>
</div>
Мы можем увидеть из этого отрезка две важных вещи:
<ol>
<li>**Строчные буквы идут после заглавных, поэтому они всегда больше.**
В частности, `'а'(код 1072) > 'Я'(код 1071)`.
То же самое происходит и в английском алфавите, там `'a' > 'Z'`.
</li>
<li>**Ряд букв, например `ё`, находятся вне основного алфавита.**
В частности, маленькая буква `ё` имеет код, больший чем `я`, поэтому **`'ё'(код 1105) > 'я'(код 1103)`**.
Кстати, большая буква `Ё` располагается в Unicode до `А`, поэтому **`'Ё'`(код 1025) < `'А'`(код 1040)**. Удивительно: есть буква меньше чем `А` :)
</li>
</ol>
**Буква `ё` не уникальна, точки над буквой используются и в других языках, приводя к тому же результату.**
Например, при работе с немецкими названиями:
```js
//+ run
alert( "ö" > "z" ); // true
```
[smart header="Юникод в HTML"]
Кстати, если мы знаем код символа в кодировке юникод, то можем добавить его в HTML, используя "числовую ссылку" (numeric character reference).
Для этого нужно написать сначала `&#`, затем код, и завершить точкой с запятой `';'`. Например, символ `'а'` в виде числовой ссылки: `&#1072;`.
Если код хотят дать в 16-ричной системе счисления, то начинают с `&#x`.
В юникоде есть много забавных и полезных символов, например, символ ножниц: &#x2702; (`&#x2702;`), дроби: &#xBD; (`&#xBD;`) &#xBE; (`&#xBE;`) и другие. Их можно использовать вместо картинок в дизайне.
[/smart]
## Посимвольное сравнение
Сравнение строк работает *лексикографически*, иначе говоря, посимвольно.
Сравнение строк `s1` и `s2` обрабатывается по следующему алгоритму:
<ol><li>Сравниваются первые символы: `a = s1.charAt(0)` и `b = s2.charAt(0)`. Если они разные, то сравниваем их и, в зависимости от результата их сравнения, возвратить `true` или `false`. Если же они одинаковые, то...</li>
<li>Сравниваются вторые символы `a = s1.charAt(1)` и `b = s2.charAt(1)`</li>
<li>Затем третьи `a = s1.charAt(2)` и `b = s2.charAt(2)` и так далее, пока символы не будут наконец разными, и тогда какой символ больше -- та строка и больше. Если же в какой-либо строке закончились символы, то считаем, что она меньше, а если закончились в обеих -- они равны.</li>
</ol>
Спецификация языка определяет этот алгоритм более детально. Если же говорить простыми словами, смысл алгоритма в точности соответствует порядку, по которому имена заносятся в орфографический словарь.
```js
"Вася" > "Ваня" // true, т.к. начальные символы совпадают, а потом 'с' > 'н'
"Дома" > "До" // true, т.к. начало совпадает, но в 1й строке больше символов
```
[warn header="Числа в виде строк сравниваются как строки"]
Бывает, что числа приходят в скрипт в виде строк, например как результат `prompt`. В этом случае результат их сравнения будет неверным:
```js
//+ run
alert("2" > "14"); // true, так как это строки, и для первых символов верно "2" > "1"
```
Если хотя бы один аргумент -- не строка, то другой будет преобразован к числу:
```js
//+ run
alert(2 > "14"); // false
```
[/warn]
## Правильное сравнение
Все современные браузеры, кроме IE10- (для которых нужно подключить библиотеку [Intl.JS](https://github.com/andyearnshaw/Intl.js/)) поддерживают стандарт [ECMA 402](http://www.ecma-international.org/ecma-402/1.0/ECMA-402.pdf), поддерживающий сравнение строк на разных языках, с учётом их правил.
Способ использования:
```js
//+ run
var str = "Ёлки";
alert( str.localeCompare("Яблони") ); // -1
```
Метод `str1.localeCompare(str2)` возвращает `-1`, если `str1 < str2`, `1`, если `str1 > str2` и `0`, если они равны.
Более подробно про устройство этого метода можно будет узнать в статье [](/intl), когда это вам понадобится.
## Итого
<ul>
<li>Строки в JavaScript имеют внутреннюю кодировку Юникод. При написании строки можно использовать специальные символы, например `\n` и вставлять юникодные символы по коду.</li>
<li>Мы познакомились со свойством `length` и методами `charAt`, `toLowerCase/toUpperCase`, `substring/substr/slice` (предпочтителен `slice`)</li>
<li>Строки сравниваются побуквенно. Поэтому если число получено в виде строки, то такие числа могут сравниваться некорректно, нужно преобразовать его к типу *number*.</li>
<li>При сравнении строк следует иметь в виду, что буквы сравниваются по их кодам. Поэтому большая буква меньше маленькой, а буква `ё` вообще вне основного алфавита.</li>
<li>Для правильного сравнения существует целый стандарт ECMA 402. Это не такое простое дело, много языков и много правил. Он поддерживается во всех современных браузерах, кроме IE10-, в которых нужна библиотека [](https://github.com/andyearnshaw/Intl.js/). Такое сравнение работает через вызов `str1.localeCompare(str2)`.</li>
</ul>
Больше информации о методах для строк можно получить в справочнике: [http://javascript.ru/String]().

View file

@ -0,0 +1,10 @@
Дата в местной временной зоне создается при помощи `new Date`.
Месяцы начинаются с нуля, так что февраль имеет номер 1. Параметры можно указывать с точностью до минут:
```js
//+ run
var d = new Date(2012, 1, 20, 3, 12);
alert(d);
```

View file

@ -0,0 +1,7 @@
# Создайте дату
[importance 5]
Создайте объект `Date` для даты: 20 февраля 2012 года, 3 часа 12 минут.
Временная зона -- местная. Выведите его на экран.

View file

@ -0,0 +1,5 @@
function getWeekDay(date) {
var days = ['вс','пн','вт','ср','чт','пт','сб'] ;
return days[ date.getDay() ];
}

View file

@ -0,0 +1,29 @@
describe("getWeekDay", function() {
it("3 января 2014 - пятница", function() {
assert.equal( getWeekDay(new Date(2014, 0, 3)), 'пт');
});
it("4 января 2014 - суббота", function() {
assert.equal( getWeekDay(new Date(2014, 0, 4)), 'сб');
});
it("5 января 2014 - воскресенье", function() {
assert.equal( getWeekDay(new Date(2014, 0, 5)), 'вс');
});
it("6 января 2014 - понедельник", function() {
assert.equal( getWeekDay(new Date(2014, 0, 6)), 'пн');
});
it("7 января 2014 - вторник", function() {
assert.equal( getWeekDay(new Date(2014, 0, 7)), 'вт');
});
it("8 января 2014 - среда", function() {
assert.equal( getWeekDay(new Date(2014, 0, 8)), 'ср');
});
it("9 января 2014 - четверг", function() {
assert.equal( getWeekDay(new Date(2014, 0, 9)), 'чт');
});
});

View file

@ -0,0 +1,16 @@
Метод `getDay()` позволяет получить номер дня недели, начиная с воскресенья.
Запишем имена дней недели в массив, чтобы можно было их достать по номеру:
```js
//+ run
function getWeekDay(date) {
var days = ['вс','пн','вт','ср','чт','пт','сб'] ;
return days[ date.getDay() ];
}
var date = new Date(2014,0,3); // 3 января 2014
alert( getWeekDay(date) ); // 'пт'
```

View file

@ -0,0 +1,13 @@
# Имя дня недели
[importance 5]
Создайте функцию `getWeekDay(date)`, которая выводит текущий день недели в коротком формате 'пн', 'вт', ... 'вс'.
Например:
```js
var date = new Date(2012,0,3); // 3 января 2012
alert( getWeekDay(date) ); // Должно вывести 'вт'
```

View file

@ -0,0 +1,10 @@
function getLocalDay(date) {
var day = date.getDay();
if ( day == 0 ) { // день 0 становится 7
day = 7;
}
return day;
}

View file

@ -0,0 +1,29 @@
describe("getLocalDay возвращает день недели", function() {
it("3 января 2014 - пятница", function() {
assert.equal( getLocalDay(new Date(2014, 0, 3)), 5);
});
it("4 января 2014 - суббота", function() {
assert.equal( getLocalDay(new Date(2014, 0, 4)), 6);
});
it("5 января 2014 - воскресенье", function() {
assert.equal( getLocalDay(new Date(2014, 0, 5)), 7);
});
it("6 января 2014 - понедельник", function() {
assert.equal( getLocalDay(new Date(2014, 0, 6)), 1);
});
it("7 января 2014 - вторник", function() {
assert.equal( getLocalDay(new Date(2014, 0, 7)), 2);
});
it("8 января 2014 - среда", function() {
assert.equal( getLocalDay(new Date(2014, 0, 8)), 3);
});
it("9 января 2014 - четверг", function() {
assert.equal( getLocalDay(new Date(2014, 0, 9)), 4);
});
});

View file

@ -0,0 +1,19 @@
Решение - в использовании встроенной функции `getDay`. Она полностью подходит нашим целям, но для воскресенья возвращает 0 вместо 7:
```js
//+ run
function getLocalDay(date) {
var day = date.getDay();
if ( day == 0 ) { // день 0 становится 7
day = 7;
}
return day;
}
alert( getLocalDay(new Date(2012,0,3)) ); // 2
```
Если удобнее, чтобы день недели начинался с нуля, то можно возвращать в функции `day - 1`, тогда дни будут от 0 (пн) до 6(вс).

View file

@ -0,0 +1,13 @@
# День недели в европейской нумерации
[importance 5]
Напишите функцию, `getLocalDay(date)` которая возвращает день недели для даты `date`.
День нужно возвратить в европейской нумерации, т.е. понедельник имеет номер 1, вторник номер 2, ..., воскресенье - номер 7.
```js
var date = new Date(2012, 0, 3); // 3 янв 2012
alert( getLocalDay(date) ); // вторник, выведет 2
```

View file

@ -0,0 +1,6 @@
function getDateAgo(date, days) {
var dateCopy = new Date(date);
dateCopy.setDate( date.getDate() - days );
return dateCopy.getDate();
}

View file

@ -0,0 +1,27 @@
describe("getDateAgo", function() {
it("1 день до 02.01.2015 -> число 1", function() {
assert.equal( getDateAgo(new Date(2015, 0, 2), 1), 1 );
});
it("2 день до 02.01.2015 -> число 31", function() {
assert.equal( getDateAgo(new Date(2015, 0, 2), 2), 31 );
});
it("100 дней от 02.01.2015 -> число 24", function() {
assert.equal( getDateAgo(new Date(2015, 0, 2), 100), 24 );
});
it("365 дней от 02.01.2015 -> число 2", function() {
assert.equal( getDateAgo(new Date(2015, 0, 2), 365), 2 );
});
it("не меняет переданный объект Date", function() {
var date = new Date(2015, 0, 2);
var dateCopy = new Date(date);
getDateAgo(dateCopy, 100);
assert.equal(date.getTime(), dateCopy.getTime());
});
});

View file

@ -0,0 +1,29 @@
Из даты `date` нужно вычесть указанное количество дней. Это просто:
```js
function getDateAgo(date, days) {
date.setDate( date.getDate() - days );
return date.getDate();
}
```
Ситуацию осложняет то, что исходный объект даты не должен меняться. Это разумное требование, оно позволит избежать сюрпризов.
Для того чтобы ему соответствовать, создадим копию объекта даты:
```js
//+ run
function getDateAgo(date, days) {
var dateCopy = new Date(date);
dateCopy.setDate( date.getDate() - days );
return dateCopy.getDate();
}
var date = new Date(2015, 0, 2);
alert( getDateAgo(date, 1) ); // 1, (1 января 2015)
alert( getDateAgo(date, 2) ); // 31, (31 декабря 2014)
alert( getDateAgo(date, 365) ); // 2, (2 января 2014)
```

View file

@ -0,0 +1,17 @@
# День указанное количество дней назад
[importance 4]
Создайте функцию `getDateAgo(date, days)`, которая возвращает число, которое было `days` дней назад от даты `date`.
Например, для 2 января 2015:
```js
var date = new Date(2015, 0, 2);
alert( getDateAgo(date, 1) ); // 1, (1 января 2015)
alert( getDateAgo(date, 2) ); // 31, (31 декабря 2014)
alert( getDateAgo(date, 365) ); // 2, (2 января 2014)
```
P.S. Важная деталь: в процессе вычислений функция не должна менять переданный ей объект `date`.

View file

@ -0,0 +1,4 @@
function getLastDayOfMonth(year, month) {
var date = new Date(year, month+1, 0);
return date.getDate();
}

View file

@ -0,0 +1,13 @@
describe("getLastDayOfMonth", function() {
it("последний день 01.01.2012 - 31", function() {
assert.equal( getLastDayOfMonth(2012, 0), 31);
});
it("последний день 01.02.2012 - 29 (високосный год)", function() {
assert.equal( getLastDayOfMonth(2012, 1), 29);
});
it("последний день 01.02.2013 - 28", function() {
assert.equal( getLastDayOfMonth(2013, 1), 28);
});
});

View file

@ -0,0 +1,14 @@
Создадим дату из следующего месяца, но день не первый, а "нулевой" (т.е. предыдущий):
```js
//+ run
function getLastDayOfMonth(year, month) {
var date = new Date(year, month+1, 0);
return date.getDate();
}
alert( getLastDayOfMonth(2012, 0) ); // 31
alert( getLastDayOfMonth(2012, 1) ); // 29
alert( getLastDayOfMonth(2013, 1) ); // 28
```

View file

@ -0,0 +1,13 @@
# Последний день месяца?
[importance 5]
Напишите функцию `getLastDayOfMonth(year, month)`, которая возвращает последний день месяца.
Параметры:
<ul>
<li>`year` -- 4-значный год, например 2012.</li>
<li>`month` -- месяц от 0 до 11.</li>
</ul>
Например, `getLastDayOfMonth(2012, 1) = 29` (високосный год, февраль).

View file

@ -0,0 +1,19 @@
Для вывода достаточно сгенерировать объект `Date`, соответствующий началу дня, т.е. "сегодня" 00 часов 00 минут 00 секунд, и вычесть его из текущей даты.
Полученная разница -- это как раз количество миллисекунд от начала дня, которое достаточно поделить на `1000`, чтобы получить секунды:
```js
//+ run
function getSecondsToday() {
var now = new Date();
// создать объект из текущей даты, без часов-минут-секунд
var today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
var diff = now - today; // разница в миллисекундах
return Math.round(diff / 1000); // перевести в секунды
}
alert( getSecondsToday() );
```

View file

@ -0,0 +1,13 @@
# Сколько секунд уже прошло сегодня?
[importance 5]
Напишите функцию `getSecondsToday()` которая возвращает, сколько секунд прошло с начала сегодняшнего дня.
Например, если сейчас `10:00` и не было перехода на зимнее/летнее время, то:
```js
getSecondsToday() == 36000 // (3600 * 10)
```
Функция должна работать в любой день, т.е. в ней не должно быть конкретного значения сегодняшней даты.

View file

@ -0,0 +1,17 @@
Для получения оставшихся до конца дня миллисекунд нужно из "завтра 00ч 00мин 00сек" вычесть текущее время.
Чтобы сгенерировать "завтра" -- увеличим текущую дату на 1 день:
```js
//+ run
function getSecondsToTomorrow() {
var now = new Date();
// создать объект из завтрашней даты, без часов-минут-секунд
var tomorrow = new Date(now.getFullYear(), now.getMonth(), *!*now.getDate()+1*/!*);
var diff = tomorrow - now; // разница в миллисекундах
return Math.round(diff / 1000); // перевести в секунды
}
```

View file

@ -0,0 +1,13 @@
# Сколько секунд - до завтра?
[importance 5]
Напишите функцию `getSecondsToTomorrow()` которая возвращает, сколько секунд осталось до завтра.
Например, если сейчас `23:00`, то:
```js
getSecondsToTomorrow() == 3600
```
P.S. Функция должна работать в любой день, т.е. в ней не должно быть конкретного значения сегодняшней даты.

View file

@ -0,0 +1,13 @@
function formatDate(date) {
var dd = date.getDate();
if ( dd < 10 ) dd = '0' + dd;
var mm = date.getMonth()+1;
if ( mm < 10 ) mm = '0' + mm;
var yy = date.getFullYear() % 100;
if ( yy < 10 ) yy = '0' + yy;
return dd+'.'+mm+'.'+yy;
}

View file

@ -0,0 +1,13 @@
describe("formatDate", function() {
it("правильно форматирует дату 30.01.14", function() {
assert.equal( formatDate(new Date(2014, 0, 30)), '30.01.14');
});
it("правильно форматирует дату 01.01.01", function() {
assert.equal( formatDate(new Date(2001, 0, 1)), '01.01.01');
});
it("правильно форматирует дату 01.01.00", function() {
assert.equal( formatDate(new Date(2000, 0, 1)), '01.01.00');
});
});

View file

@ -0,0 +1,51 @@
Получим компоненты один за другим.
<ol>
<li>День можно получить как `date.getDate()`. При необходимости добавим ведущий ноль:
```js
var dd = date.getDate();
if (dd<10) dd= '0'+dd;
```
</li>
<li>`date.getMonth()` возвратит месяц, начиная с нуля. Увеличим его на 1:
```js
var mm = date.getMonth() + 1; // месяц 1-12
if (mm<10) mm= '0'+mm;
```
</li>
<li>`date.getFullYear()` вернет год в 4-значном формате. Чтобы сделать его двузначным - воспользуемся оператором взятия остатка `'%'`:
```js
var yy = date.getFullYear() % 100;
if (yy<10) yy= '0'+yy;
```
Заметим, что год, как и другие компоненты, может понадобиться дополнить нулем слева, причем возможно что `yy == 0` (например, 2000 год). При сложении со строкой `0+'0' == '00'`, так что будет все в порядке.
</li>
</ol>
Полный код:
```js
//+ run
function formatDate(date) {
var dd = date.getDate();
if ( dd < 10 ) dd = '0' + dd;
var mm = date.getMonth()+1;
if ( mm < 10 ) mm = '0' + mm;
var yy = date.getFullYear() % 100;
if ( yy < 10 ) yy = '0' + yy;
return dd+'.'+mm+'.'+yy;
}
var d = new Date(2014, 0, 30); // 30 Янв 2014
alert( formatDate(d) ); // '30.01.14'
```

View file

@ -0,0 +1,14 @@
# Вывести дату в формате дд.мм.гг
[importance 3]
Напишите функцию `formatDate(date)`, которая выводит дату `date` в формате `дд.мм.гг`:
Например:
```js
var d = new Date(2014, 0, 30); // 30 января 2014
alert( formatDate(d) ); // '30.01.14'
```
P.S. Обратите внимание, ведущие нули должны присутствовать, то есть 1 января 2001 должно быть 01.01.01, а не 1.1.1.

View file

@ -0,0 +1,34 @@
function formatDate(date) {
var diff = new Date() - date; // разница в миллисекундах
if (diff < 1000) { // прошло менее 1 секунды
return 'только что';
}
var sec = Math.floor( diff / 1000 ); // округлить diff до секунд
if (sec < 60) {
return sec + ' сек. назад';
}
var min = Math.floor( diff / 60000 ); // округлить diff до минут
if (min < 60) {
return min + ' мин. назад';
}
// форматировать дату, с учетом того, что месяцы начинаются с 0
var d = date;
d = [
'0'+d.getDate(),
'0'+(d.getMonth()+1),
''+d.getFullYear(),
'0'+d.getHours(),
'0'+d.getMinutes()
];
for(var i=0; i<d.length; i++) {
d[i] = d[i].slice(-2);
}
return d.slice(0,3).join('.')+' '+d.slice(3).join(':');
}

View file

@ -0,0 +1,18 @@
describe("formatDate", function() {
it("выводит дату 1мс назад как \"только что\"", function() {
assert.equal( formatDate( new Date(new Date - 1) ), 'только что' );
});
it('выводит дату "30 сек назад"', function() {
assert.equal( formatDate( new Date( new Date - 30*1000) ), "30 сек. назад" );
});
it('выводит дату "5 мин назад"', function() {
assert.equal( formatDate( new Date( new Date- 5*60*1000) ), "5 мин. назад");
});
it("выводит старую дату в формате дд.мм.гг чч:мм", function() {
assert.equal( formatDate( new Date(2014, 2, 1, 11, 22, 33) ), "01.03.14 11:22" );
});
});

View file

@ -0,0 +1,48 @@
Для того, чтобы узнать время от `date` до текущего момента - используем вычитание дат.
```js
//+ run
function formatDate(date) {
var diff = new Date() - date; // разница в миллисекундах
if (diff < 1000) { // прошло менее 1 секунды
return 'только что';
}
var sec = Math.floor( diff / 1000 ); // округлить diff до секунд
if (sec < 60) {
return sec + ' сек. назад';
}
var min = Math.floor( diff / 60000 ); // округлить diff до минут
if (min < 60) {
return min + ' мин. назад';
}
// форматировать дату, с учетом того, что месяцы начинаются с 0
var d = date;
d = [
'0'+d.getDate(),
'0'+(d.getMonth()+1),
''+d.getFullYear(),
'0'+d.getHours(),
'0'+d.getMinutes()
];
for(var i=0; i<d.length; i++) {
d[i] = d[i].slice(-2);
}
return d.slice(0,3).join('.')+' '+d.slice(3).join(':');
}
alert( formatDate( new Date( new Date - 1) ) ); // только что
alert( formatDate( new Date( new Date - 30*1000) ) ); // 30 сек. назад
alert( formatDate( new Date( new Date- 5*60*1000) ) ); // 5 мин. назад
alert( formatDate( new Date( new Date - 86400*1000) ) ); // вчерашняя дата в формате "дд.мм.гг чч:мм"
```

View file

@ -0,0 +1,26 @@
# Относительное форматирование даты
[importance 4]
Напишите функцию `formatDate(date)`, которая форматирует дату `date` так:
<ul>
<li>Если со времени `date` прошло менее секунды, то возвращает `"только что"`.</li>
<li>Иначе если со времени `date` прошло менее минуты, то `"n сек. назад"`.</li>
<li>Иначе если прошло меньше часа, то `"m мин. назад"`.</li>
<li>Иначе полная дата в формате `"дд.мм.гг чч:мм"`.</li>
</ul>
Например:
```js
function formatDate(date) { /* ваш код */ }
alert( formatDate( new Date(new Date - 1) ) ); // "только что"
alert( formatDate( new Date(new Date - 30*1000) ) ); // "30 сек. назад"
alert( formatDate( new Date(new Date- 5*60*1000) ) ); // "5 мин. назад"
alert( formatDate( new Date(new Date - 86400*1000) ) ); // вчерашняя дата в формате "дд.мм.гг чч:мм"
```

View file

@ -0,0 +1,492 @@
# Дата и Время
Для работы с датой и временем в JavaScript используются объекты [Date](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Date/).
[cut]
## Создание
Для создания нового объекта типа `Date` используется один из синтаксисов:
<dl>
<dt>`new Date()`</dt>
<dd>Создает объект `Date` с текущей датой и временем:
```js
//+ run
var now = new Date();
alert(now);
```
</dd>
<dt>`new Date(milliseconds)`</dt>
<dd>Создает объект `Date`, значение которого равно количеству миллисекунд (1/1000 секунды), прошедших с 1 января 1970 года GMT+0.
```js
//+ run
// 24 часа после 01.01.1970 GMT+0
var Jan02_1970 = new Date(3600*24*1000);
alert( Jan02_1970 );
```
</dd>
<dt>`new Date(datestring)`</dt>
<dd>Если единственный аргумент - строка, используется вызов `Date.parse` для ее разбора.</dd>
<dt>`new Date(year, month, date, hours, minutes, seconds, ms)`</dt>
<dd>Дату можно создать, используя компоненты в местной временной зоне. Для этого формата обязательны только первые два аргумента. Отсутствующие параметры, начиная с `hours` считаются равными нулю, а `date` -- единице.
**Заметим, что год `year` должен быть из 4 цифр, а отсчет месяцев `month` начинается с нуля 0.** Например:
```js
new Date(2011, 0, 1) // 1 января 2011, 00:00:00 в местной временной зоне
new Date(2011, 0) // то же самое, date по умолчанию равно 1
new Date(2011, 0, 1, 0, 0, 0, 0); // то же самое
```
Дата задана с точностью до миллисекунд:
```js
//+ run
var d = new Date(2011, 0, 1, 2, 3, 4, 567);
alert(d); // 1.01.2011, 02:03:04.567
```
</dd>
</dl>
## Получение компонентов даты
Для доступа к компонентам даты-времени объекта `Date` используются следующие методы:
<dl>
<dt>`getFullYear()`</dt>
<dd>Получить год(из 4 цифр)</dd>
<dt>`getMonth()`</dt>
<dd>Получить месяц, **от 0 до 11**.</dd>
<dt>`getDate()`</dt>
<dd>Получить число месяца, от 1 до 31.</dd>
<dt>`getHours(), getMinutes(), getSeconds(), getMilliseconds()`</dt>
<dd>Получить соответствующие компоненты.</dd>
</dl>
[warn header="Устаревший `getYear()`"]
Некоторые браузеры реализуют нестандартный метод `getYear()`. Где-то он возвращает только две цифры из года, где-то четыре. Так или иначе, этот метод отсутствует в стандарте JavaScript. Не используйте его. Для получения года есть `getFullYear()`.
[/warn]
Дополнительно можно получить день недели:
<dl>
<dt>`getDay()`</dt>
<dd>Получить номер дня в неделе. Неделя в JavaScript начинается с воскресенья, так что результат будет числом **от 0(воскресенье) до 6(суббота)**.</dd>
</dl>
**Все методы, указанные выше, возвращают результат для местной временной зоны.**
Существуют также UTC-варианты этих методов, возвращающие день, месяц, год и т.п. для зоны GMT+0 (UTC): `getUTCFullYear()`, `getUTCMonth()`, `getUTCDay()`. То есть, сразу после `"get"` вставляется `"UTC"`.
Если ваше локальное время сдвинуто относительно UTC, то следующий код покажет разные часы:
```js
//+ run
var date = new Date();
alert( date.getHours() ); // час в вашей зоне для даты date
alert( date.getUTCHours() ); // час в зоне GMT+0 для даты date
```
Кроме описанных выше, существуют два специальных метода без UTC-варианта:
<dl>
<dt>`getTime()`</dt>
<dd>Возвращает число миллисекунд, прошедших с 01.01.1970 00:00:00 UTC. Это то же число, которое используется в конструкторе `new Date(milliseconds)`.</dd>
<dt>`getTimezoneOffset()`</dt>
<dd>Возвращает разницу между местным и UTC-временем, в минутах.
```js
//+ run
alert( new Date().getTimezoneOffset() ); // Для GMT-1 выведет 60
```
</dd>
</dl>
## Установка компонентов даты
Следующие методы позволяют устанавливать компоненты даты и времени:
<ul>
<li>`setFullYear(year [, month, date])`</li>
<li>`setMonth(month [, date])`</li>
<li>`setDate(date)`</li>
<li>`setHours(hour [, min, sec, ms])`</li>
<li>`setMinutes(min [, sec, ms])`</li>
<li>`setSeconds(sec [, ms])`</li>
<li>`setMilliseconds(ms)`</li>
<li>`setTime(milliseconds)` (устанавливает всю дату по миллисекундам с 01.01.1970 UTC)</li>
</ul>
Все они, кроме `setTime()`, обладают также UTC-вариантом, например: `setUTCHours()`.
Как видно, некоторые методы могут устанавливать несколько компонентов даты одновременно, в частности, `setHours`. При этом если какая-то компонента не указана, она не меняется. Например:
```js
//+ run
var today = new Date;
today.setHours(0);
alert( today ); // сегодня, но час изменён на 0
today.setHours(0, 0, 0, 0);
alert (today ); // сегодня, ровно 00:00:00.
```
### Автоисправление даты
*Автоисправление* -- очень удобное свойство объектов `Date`. Оно заключается в том, что можно устанавливать заведомо некорректные компоненты (например 32 января), а объект сам себя поправит.
```js
//+ run
var d = new Date(2013, 0, *!*32*/!*); // 32 января 2013 ?!?
alert(d); // ... это 1 февраля 2013!
```
**Неправильные компоненты даты автоматически распределяются по остальным.**
Например, нужно увеличить на 2 дня дату "28 февраля 2011". Может быть так, что это будет 2 марта, а может быть и 1 марта, если год високосный. Но нам обо всем этом думать не нужно. Просто прибавляем два дня. Остальное сделает `Date`:
```js
//+ run
var d = new Date(2011, 1, 28);
*!*
d.setDate( d.getDate() + 2 );
*/!*
alert(d); // 2 марта, 2011
```
Также это используют для получения даты, отдаленной от имеющейся на нужный промежуток времени. Например, получим дату на 70 секунд большую текущей:
```js
//+ run
var d = new Date();
d.setSeconds( d.getSeconds()+70);
alert(d); // выведет корректную дату
```
Можно установить и нулевые, и даже отрицательные компоненты. Например:
```js
//+ run
var d = new Date;
d.setDate(1); // поставить первое число месяца
alert(d);
d.setDate(0); // нулевого числа нет, будет последнее число предыдущего месяца
alert(d);
```
```js
//+ run
var d = new Date;
d.setDate(-1); // предпоследнее число предыдущего месяца
alert(d);
```
### Преобразование к числу, разность дат
Когда объект `Date` используется в числовом контексте, он преобразуется в количество миллисекунд:
```js
//+ run
alert( +new Date ) // +date то же самое, что: +date.valueOf()
```
**Важный побочный эффект: даты можно вычитать, результат вычитания объектов `Date` -- их временная разница, в миллисекундах**.
Это используют для измерения времени:
```js
//+ run
var start = new Date; // засекли время
// что-то сделать
for (var i=0; i<100000; i++) {
var doSomething = i*i*i;
}
var end = new Date; // конец измерения
alert("Цикл занял " + (end-start) + " ms");
```
### Бенчмаркинг
Допустим, у нас есть несколько вариантов решения задачи, каждый описан функцией.
Как узнать, какой быстрее?
Для примера возьмем две функции, которые бегают по массиву:
```js
function walkIn(arr) {
for(var key in arr) arr[i]++
}
function walkLength(arr) {
for(var i=0; i<arr.length; i++) arr[i]++;
}
```
Чтобы померять, какая из них быстрее, нельзя запустить один раз `walkIn`, один раз `walkLength` и замерить разницу. Одноразовый запуск ненадежен, любая мини-помеха исказит результат.
Для правильного бенчмаркинга функция запускается много раз, чтобы сам тест занял существенное время. Это сведет влияние помех к минимуму. Сложную функцию можно запускать 100 раз, простую -- 1000 раз...
Померяем, какая из функций округления быстрее:
```js
//+ run
var arr = [];
for(var i=0; i<1000; i++) arr[i] = 0;
function walkIn(arr) { for(var key in arr) arr[i]++; }
function walkLength(arr) { for(var i=0; i<arr.length; i++) arr[i]++; }
function bench(f) {
var date = new Date();
for (var i=0; i<10000; i++) f(arr);
return new Date() - date;
}
alert('Время walkIn: ' + bench(walkIn) + 'мс');
alert('Время walkLength: ' + bench(walkLength) + 'мс');
```
Теперь представим себе, что во время первого бенчмаркинга `bench(walkIn)` компьютер что-то делал параллельно важное (вдруг) и это занимало ресурсы, а во время второго -- перестал. Реальная ситуация? Конечно реальна, особенно на современных ОС, где много процессов одновременно.
**Гораздо более надёжные результаты можно получить, весь пакет тестов прогнать много раз.**
```js
//+ run
var arr = [];
for(var i=0; i<1000; i++) arr[i] = 0;
function walkIn(arr) { for(var key in arr) arr[i]++; }
function walkLength(arr) { for(var i=0; i<arr.length; i++) arr[i]++; }
function bench(f) {
var date = new Date();
for (var i=0; i<1000; i++) f(arr);
return new Date() - date;
}
*!*
// bench для каждого теста запустим много раз, чередуя
var timeIn = 0, timeLength = 0;
for(var i=0; i<100; i++) {
timeIn += bench(walkIn);
timeLength += bench(walkLength);
}
*/!*
alert('Время walkIn: ' + timeIn + 'мс');
alert('Время walkLength: ' +timeLength + 'мс');
```
[smart header="Более точное время с `performance.now()`"]
В современных браузерах (кроме IE9-) вызов [performance.now()](https://developer.mozilla.org/en-US/docs/Web/API/performance.now) возвращает количество миллисекунд, прошедшее с начала загрузки страницы, а если точнее -- с момента выгрузки предыдущей страницы из памяти.
Его можно посмотреть в любом месте страницы, даже в `<head>`, чтобы узнать, сколько времени потребовалось браузеру, чтобы до него добраться, включая загрузку HTML.
Возвращаемое значение измеряется в миллисекундах, но дополнительно имеет точность 3 знака после запятой (до миллионных долей секунды!), поэтому можно использовать его и для более точного бенчмаркинга в том числе.
[/smart]
[smart header="`console.time(метка)` и `console.timeEnd(метка)`"]
Для измерения с одновременным выводом результатов в консоли есть методы:
<ul>
<li>`console.time(метка)` -- включить внутренний хронометр браузера с меткой.</li>
<li>`console.timeEnd(метка)` -- выключить внутренний хронометр браузера с меткой и вывести результат.</li>
</ul>
Параметр `"метка"` используется для идентификации таймера, чтобы можно было делать много замеров одновременно и даже вкладывать измерения друг в друга.
В коде ниже таймеры `walkIn`, `walkLength` -- конкретные тесты, а таймер "All Benchmarks" -- время "на всё про всё":
```js
//+ run
var arr = [];
for(var i=0; i<1000; i++) arr[i] = 0;
function walkIn(arr) { for(var key in arr) arr[i]++; }
function walkLength(arr) { for(var i=0; i<arr.length; i++) arr[i]++; }
function bench(f) {
for (var i=0; i<10000; i++) f(arr);
}
console.time("All Benchmarks");
console.time("walkIn");
bench(walkIn);
console.timeEnd("walkIn");
console.time("walkLength");
bench(walkLength);
console.timeEnd("walkLength");
console.timeEnd("All Benchmarks");
```
**При запуске этого примера нужно открыть консоль, иначе вы ничего не увидите.**
[/smart]
[warn header="Внимание, оптимизатор!"]
Современные интерпретаторы JavaScript делают массу оптимизаций, например:
<ol>
<li>Автоматически выносят инвариант, то есть постоянное в цикле значение типа `arr.length`, за пределы цикла.</li>
<li>Стараются понять, значения какого типа хранит данная переменная или массив, какую структуру имеет объект и, исходя из этого, оптимизировать внутренние алгоритмы.</li>
<li>Выполняют простейшие операции, например сложение явно заданных чисел и строк, на этапе компиляции.</li>
<li>В теории, могут выкинуть код, который ни на что не влияет, например присваивание к неиспользуемой локальной переменной, хотя делают это редко.</li>
</ol>
Они могут влиять на результаты тестов.
[/warn]
## Форматирование
Во всех браузерах, кроме IE10-, поддерживается новый стандарт [Ecma 402](http://www.ecma-international.org/publications/standards/Ecma-402.htm), который добавляет специальные методы для форматирования дат.
Это делается взыовом `date.toLocaleString(локаль, опции)`, у которого много настроек. Он позволяет указать, какие параметры даты нужно вывести, и ряд настроек вывода, после чего интерпретатор сам сформирует строку.
Пример с почти всеми параметрами даты и русским, затем английским (США) форматированием:
```js
//+ run
var date = new Date(2014, 11, 31, 12, 30, 0);
var options = {
era: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
weekday: 'long',
timezone: 'UTC',
hour: 'numeric',
minute: 'numeric',
second: 'numeric'
};
alert( date.toLocaleString("ru", options) ); // среда, 31 декабря 2014 г. н.э. 12:30:00
alert( date.toLocaleString("en-US", options) ); // Wednesday, December 31, 2014 Anno Domini 12:30:00 PM
```
Вы сможете подробно узнать о них в статье [](/intl), которая посвящена этому стандарту.
**Методы вывода без локализации:**
<dl>
<dt>`toString()`, `toDateString()`, `toTimeString()`</dt>
<dd>Возвращают стандартное строчное представление, не указанное в стандарте, а зависящее от браузера. Единственное требование - читаемость человеком. Метод `toString` возвращает дату целиком, `toDateString()` и `toTimeString()` - только дату и время соответственно.
```js
//+ run
var d = new Date();
alert( d.toString() ); // вывод, похожий на 'Wed Jan 26 2011 16:40:50 GMT+0300'
```
<dt>`toUTCString()`</dt>
<dd>То же самое, что `toString()`, но дата в зоне UTC.</dd>
</dl>
<dt>`toISOString()`</dt>
<dd>Возвращает дату в формате ISO Детали формата будут далее. Поддерживается современными браузерами, не поддерживается IE&lt;9.
```js
//+ run
var d = new Date();
alert( d.toISOString() ); // вывод, похожий на '2011-01-26T13:51:50.417Z'
```
</dd></dl>
**Если хочется иметь большую гибкость и кросс-браузерность, то также можно воспользоваться специальной библиотекой, например [Moment.JS](http://momentjs.com/) или написать свою функцию.**
## Разбор строки, Date.parse
Все современные браузеры, включая IE9+, понимают даты в упрощённом формате ISO 8601 Extended.
Этот формат выглядит так: `YYYY-MM-DDTHH:mm:ss.sssZ`. Для разделения даты и времени в нем используется символ `'T'`. Часть `'Z'` обозначает (необязательную) временную зону -- она может отсутствовать, тогда зона UTC, либо может быть символ `z` -- тоже UTC, или зона в формате `+-hh:mm`.
Также возможны упрощенные варианты, к примеру:
```js
YYYY
YYYY-MM
YYYY-MM-DD
```
Метод `Date.parse(str)` разбирает строку `str` в таком формате и возвращает соответствующее ей количество миллисекунд. Если это невозможно, `Date.parse` возвращает `NaN`.
На момент написания некоторые браузеры (Safari) воспринимали формат без `'Z'` как дату в локальной таймзоне (по стандарту UTC), поэтому пример ниже в них работает некорректно:
```js
//+ run
var msNoZone = Date.parse('2012-01-26T13:51:50.417'); // без зоны, значит UTC
alert(msNoZone); // 1327571510417 (число миллисекунд)
var msZ = Date.parse('2012-01-26T13:51:50.417z'); // зона z означает UTC
alert(msZ == msNoZone); // true, если браузер правильный
```
С таймзоной `-07:00 GMT` в конце все современные браузеры работают правильно:
```js
//+ run
var ms = Date.parse('2012-01-26T13:51:50.417-07:00');
alert(ms); // 1327611110417 (число миллисекунд)
```
[smart header="Формат дат для IE8-"]
До появления спецификации EcmaScript 5 формат не был стандартизован, и браузеры, включая IE8-, имели свои собственные форматы дат. Частично, эти форматы пересекаются.
Например, код ниже работает везде, включая старые IE:
```js
//+ run
var ms = Date.parse("January 26, 2011 13:51:50");
alert(ms);
```
Вы также можете почитать о старых форматах IE в документации к методу <a href="http://msdn.microsoft.com/en-us/library/k4w173wk%28v=vs.85%29.aspx">MSDN Date.parse</a>.
Конечно же, сейчас лучше использовать современный формат. Если же нужна поддержка IE8-, то метод `Date.parse`, как и ряд других современных методов, добавляется библиотекой [es5-shim](https://github.com/kriskowal/es5-shim).
[/smart]
## Метод Date.now()
Метод `Date.now()` возвращает дату сразу в виде миллисекунд.
Технически, он аналогичен вызову `+new Date()`, но в отличие от него не создаёт промежуточный объект даты, а поэтому -- во много раз быстрее.
Его использование особенно рекомендуется там, где производительность при работе с датами критична. Обычно это не на веб-страницах, а, к примеру, в разработке игр на JavaScript.
## Итого
<ul>
<li>Дата и время представлены в JavaScript одним объектом: [Date](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Date/). Создать "только время" при этом нельзя, оно должно быть с датой. Список методов `Date` вы можете найти в справочнике [Date](http://javascript.ru/Date) или выше.</li>
<li>Отсчёт месяцев начинается с нуля.</li>
<li>Отсчёт дней недели (для `getDay()`) тоже начинается с нуля (и это воскресенье).</li>
<li>Объект `Date` удобен тем, что автокорректируется. Благодаря этому легко сдвигать даты.</li>
<li>При преобразовании к числу объект `Date` даёт количество миллисекунд, прошедших с 1 января 1970 UTC. Побочное следствие -- даты можно вычитать, результатом будет разница в миллисекундах.</li>
<li>Для получения текущей даты в миллисекундах лучше использовать `Date.now()`, чтобы не создавать лишний объект `Date` (кроме IE8-)</li>
<li>Для бенчмаркинга лучше использовать `performance.now()` (кроме IE9-), он в 1000 раз точнее.</li>
</ul>

View file

@ -0,0 +1,28 @@
function formatDate(date) {
if (typeof date == 'number') {
// перевести секунды в миллисекунды и преобразовать к Date
date = new Date(date*1000);
} else if(typeof date == 'string') {
// разобрать строку и преобразовать к Date
date = date.split('-');
date = new Date(date[0], date[1]-1, date[2]);
} else if ( date.length ) { // есть длина, но не строка - значит массив
date = new Date(date[0], date[1], date[2]);
}
// преобразования для поддержки полиморфизма завершены,
// теперь мы работаем с датой (форматируем её)
var day = date.getDate();
if (day < 10) day = '0' + day;
var month = date.getMonth()+1;
if (month < 10) month = '0' + month;
// взять 2 последние цифры года
var year = date.getFullYear() % 100;
if (year < 10) year = '0' + year;
var formattedDate = day + '.' + month + '.' + year;
return formattedDate;
}

View file

@ -0,0 +1,18 @@
describe("formatDate", function() {
it("читает дату вида гггг-мм-дд из строки", function() {
assert.equal( formatDate( '2011-10-02' ), "02.10.11" );
});
it("читает дату из числа 1234567890 (миллисекунды)", function() {
assert.equal( formatDate( 1234567890 ), "14.02.09" );
});
it("читает дату из массива вида [гггг, м, д]", function() {
assert.equal( formatDate( [2014,0,1] ), "01.01.14" );
});
it("читает дату из объекта Date", function() {
assert.equal( formatDate( new Date(2014,0,1) ), "01.01.14" );
});
});

View file

@ -0,0 +1,55 @@
Для определения примитивного типа строка/число подойдет оператор [typeof](#type-typeof).
Примеры его работы:
```js
//+ run
alert(typeof 123); // "number"
alert(typeof "строка"); // "string"
alert(typeof new Date()); // "object"
alert(typeof []); // "object"
```
Оператор `typeof` не умеет различать разные типы объектов, они для него все на одно лицо: `"object"`. Поэтому он не сможет отличить `Date` от `Array`.
Используем для них утиную типизацию:
Функция:
```js
//+ run
function formatDate(date) {
if (typeof date == 'number') {
// перевести секунды в миллисекунды и преобразовать к Date
date = new Date(date*1000);
} else if(typeof date == 'string') {
// разобрать строку и преобразовать к Date
date = date.split('-');
date = new Date(date[0], date[1]-1, date[2]);
} else if ( date.length ) { // есть длина, но не строка - значит массив
date = new Date(date[0], date[1], date[2]);
}
// преобразования для поддержки полиморфизма завершены,
// теперь мы работаем с датой (форматируем её)
var day = date.getDate();
if (day < 10) day = '0' + day;
var month = date.getMonth()+1;
if (month < 10) month = '0' + month;
// взять 2 последние цифры года
var year = date.getFullYear() % 100;
if (year < 10) year = '0' + year;
var formattedDate = day + '.' + month + '.' + year;
return formattedDate;
}
alert( formatDate( '2011-10-02' ) ); // 02.10.11
alert( formatDate( 1234567890 ) ); // 14.02.09
alert( formatDate( [2014,0,1] ) ); // 01.01.14
alert( formatDate( new Date(2014,0,1) ) ); // 01.01.14
```

View file

@ -0,0 +1,26 @@
# Полиморфная функция formatDate
[importance 5]
Напишите функцию `formatDate(date)`, которая возвращает дату в формате `dd.mm.yy`.
Ее первый аргумент должен содержать дату в одном из видов:
<ol>
<li>Как объект `Date`.</li>
<li>Как строку в формате `yyyy-mm-dd`.</li>
<li>Как число *секунд* с `01.01.1970`.</li>
<li>Как массив `[гггг, мм, дд]`, месяц начинается с нуля</li>
</ol>
Для этого вам понадобится определить тип данных аргумента и, при необходимости, преобразовать входные данные в нужный формат.
Пример работы:
```js
function formatDate(date) { /* ваш код */ }
alert( formatDate( '2011-10-02' ) ); // 02.10.11
alert( formatDate( 1234567890 ) ); // 14.02.09
alert( formatDate( [2014,0,1] ) ); // 01.01.14
alert( formatDate( new Date(2014,0,1) ) ); // 01.01.14
```

View file

@ -0,0 +1,162 @@
# Оператор typeof и утиная типизация
В этой главе мы рассмотрим, как создавать *полиморфные* функции, то есть такие, которые по-разному обрабатывают аргументы, в зависимости от их типа. Например, функция вывода может по-разному форматировать числа и даты.
Для реализации такой возможности нужен способ определить тип переменной.
[cut]
Как мы знаем, существует несколько *примитивных типов*:
<dl>
<dt>`null`</dt>
<dd>Специальный тип, содержит только значение `null`.</dd>
<dt>`undefined`</dt>
<dd>Специальный тип, содержит только значение `undefined`.</dd>
<dt>`number`</dt>
<dd>Числа: `0`, `3.14`, а также значения `NaN` и `Infinity`</dd>
<dt>`boolean`</dt>
<dd>`true`, `false`.</dd>
<dt>`string`</dt>
<dd>Строки, такие как `"Мяу"` или пустая строка `""`.</dd>
</dl>
Все остальные значения являются **объектами**, включая функции и массивы.
## Оператор typeof [#type-typeof]
Оператор `typeof` возвращает тип аргумента. У него есть два синтаксиса:
<ol>
<li>Синтаксис оператора: `typeof x`.</li>
<li>Синтаксис функции: `typeof(x)`.</li>
</ol>
Работают они одинаково, но первый синтаксис короче.
**Результатом `typeof` является строка, содержащая тип:**
```js
typeof undefined // "undefined"
typeof 0 // "number"
typeof true // "boolean"
typeof "foo" // "string"
typeof {} // "object"
*!*
typeof null // "object"
*/!*
function f() { /* ... */ }
typeof f // "function"
*/!*
```
Последние две строки помечены, потому что `typeof` ведет себя в них по-особому.
<ol>
<li>Результат `typeof null == "object"` -- это официально признанная ошибка в языке, которая сохраняется для совместимости.
На самом деле `null` -- это не объект, а примитив. Это сразу видно, если попытаться присвоить ему свойство:
```js
//+ run
var x = null;
x.prop = 1; // ошибка, т.к. нельзя присвоить свойство примитиву
```
</li>
<li>Для функции `f` значением `typeof f` является `"function"`. На самом деле функция не является отдельным базовым типом в JavaScript, все функции являются объектами, но такое выделение функций на практике удобно, так как позволяет легко определить функцию.</li>
</ol>
**Оператор `typeof` надежно работает с примитивными типами, кроме `null`, а также с функциями. Но обычные объекты, массивы и даты для `typeof` все на одно лицо, они имеют тип `'object'`:**
```js
//+ run
alert( typeof {} ); // 'object'
alert( typeof [] ); // 'object'
alert( typeof new Date ); // 'object'
```
Поэтому различить их при помощи `typeof` нельзя.
## Утиная типизация
Основная проблема `typeof` -- неумение различать объекты, кроме функций. Но есть и другой способ проверки типа.
Так называемая "утиная типизация" основана на одной известной пословице: *"If it looks like a duck, swims like a duck and quacks like a duck, then it probably is a duck (who cares what it really is)"*.
В переводе: *"Если это выглядит как утка, плавает как утка и крякает как утка, то, вероятно, это утка (какая разница, что это на самом деле)"*.
Смысл утиной типизации -- в проверке необходимых методов и свойств.
Например, у нас функция работает с массивами. Мы можем проверить, что объект -- массив, уточнив наличие метода `splice`:
```js
//+ run
var something = [1,2,3];
if (something.splice) {
alert('Массив!');
}
```
Обратите внимание -- в `if` мы не вызываем метод `something.splice()`, а пробуем получить само свойство `something.splice`. Для массивов оно всегда есть и является функцией, т.е. даст в логическом контексте `true`.
Проверить на дату можно, определив наличие метода `getTime`:
```js
//+ run
var x = new Date();
if (x.getTime) {
alert('Дата!');
}
```
С виду такая проверка хрупка, ее можно "сломать", передав похожий объект с тем же методом.
Но как раз в этом и есть смысл утиной типизации: если объект похож на массив, у него есть методы массива, то будем работать с ним как с массивом (какая разница, что это на самом деле).
## Полиморфизм
Используем проверку типов для того, чтобы создать полиморфную функцию `sayHi(who)`, которая говорит "Привет" своему аргументу.
При этом, если передали массив, она должна вызвать себя для каждого подэлемента.
```js
//+ run
function sayHi(who) {
if (who.splice) { // проверка на массив (или что-то похожее)
for(var i=0; i<who.length; i++) sayHi(who[i]);
} else {
alert('Привет, ' + who);
}
}
// Вызов с примитивным аргументом
sayHi("Вася"); // Привет, Вася
// Вызов с массивом
sayHi( ["Саша", "Петя"] ); // Привет, Саша... Петя
sayHi( ["Саша", "Петя", ["Маша", "Юля"] ] ); // Привет Саша..Петя..Маша..Юля
```
Обратите внимание, получилась даже поддержка вложенных массивов :). Да здравствует рекурсия!
## Итого
Для написания полиморфных (это удобно!) функций нам нужна проверка типов.
Для примитивов с ней отлично справляется оператор `typeof`.
У него две особенности:
<ol>
<li>Он считает `null` объектом, это внутренняя ошибка в языке.</li>
<li>Для функций он возвращает `function`, по стандарту функция не считается базовым типом, но на практике это удобно и полезно.</li>
</ol>
Там, где нужно различать объекты, обычно используется утиная типизация, то есть мы смотрим, есть ли в объекте нужный метод, желательно -- тот, который мы собираемся исползовать, но это не обязательно.

View file

@ -0,0 +1,11 @@
```js
//+ run demo
var a = +prompt("Введите первое число", "");
var b = +prompt("Введите второе число", "");
alert( a + b );
```
Обратите внимание на оператор `+` перед `prompt`, он сразу приводит вводимое значение к числу. Если бы его не было, то `a` и `b` были бы строками и складывались бы как строки, то есть `"1" + "2" = "12"`.

View file

@ -0,0 +1,9 @@
# Интерфейс суммы
[importance 5]
Создайте страницу, которая предлагает ввести два числа и выводит их сумму.
[demo /]
P.S. Есть "подводный камень" при работе с типами.

View file

@ -0,0 +1,8 @@
Во внутреннем двоичном представлении `6.35` является бесконечной двоичной дробью. Хранится она с потерей точности.. А впрочем, посмотрим сами:
```js
//+ run
alert( 6.35.toFixed(20) ); // 6.34999999999999964473
```
Интерпретатор видит число как `6.34...`, поэтому и округляет вниз.

View file

@ -0,0 +1,19 @@
# Почему 6.35.toFixed(1) == 6.3?
[importance 4]
В математике принято, что `5` округляется вверх, например:
```js
//+ run
alert( 1.5.toFixed(0) ); // 2
alert( 1.35.toFixed(1) ); // 1.4
```
Но почему в примере ниже `6.35` округляется до `6.3`?
```js
//+ run
alert( 6.35.toFixed(1) ); // 6.3
```

View file

@ -0,0 +1,13 @@
Есть два основных подхода.
<ol>
<li>Можно хранить сами цены в "копейках" (центах и т.п.). Тогда они всегда будут целые и проблема исчезнет. Но при показе и при обмене данными нужно будет это учитывать и не забывать делить на 100.</li>
<li>При операциях, когда необходимо получить окончательный результат -- округлять до 2го знака после запятой. Все, что дальше -- ошибка округления:
```js
//+ run
var price1 = 0.1, price2 = 0.2;
alert( +(price1 + price2).toFixed(2) );
```
</li>
</ol>

View file

@ -0,0 +1,16 @@
# Сложение цен
[importance 5]
Представьте себе электронный магазин. Цены даны с точностью до копейки(цента, евроцента и т.п.).
Вы пишете интерфейс для него. Основная работа происходит на сервере, но и на клиенте все должно быть хорошо. Сложение цен на купленные товары и умножение их на количество является обычной операцией.
Получится глупо, если при заказе двух товаров с ценами `0.10$` и `0.20$` человек получит общую стоимость `0.30000000000000004$`:
```js
//+ run
alert( 0.1 + 0.2 + '$' );
```
Что можно сделать, чтобы избежать проблем с ошибками округления?

View file

@ -0,0 +1,14 @@
Потому что `i` никогда не станет равным `10`.
Запустите, чтобы увидеть *реальные* значения `i`:
```js
//+ run
var i = 0;
while(i < 11) {
i += 0.2;
if (i>9.8 && i<10.2) alert(i);
}
```
Ни одно из них в точности не равно `10`.

View file

@ -0,0 +1,13 @@
# Бесконечный цикл по ошибке
[importance 4]
Этот цикл - бесконечный. Почему?
```js
var i = 0;
while(i != 10) {
i += 0.2;
}
```

View file

@ -0,0 +1,7 @@
function getDecimal(num) {
var str = "" + num;
var zeroPos = str.indexOf(".");
if (zeroPos == -1) return 0;
str = str.slice( zeroPos );
return +str;
}

View file

@ -0,0 +1,21 @@
describe("getDecimal", function() {
it("возвращает дробную часть 1.2 как 0.2", function() {
assert.equal( getDecimal(1.2), 0.2 );
});
it("возвращает дробную часть 1.3 как 0.3", function() {
assert.equal( getDecimal(1.3), 0.3 );
});
it("возвращает дробную часть 12.345 как 0.345", function() {
assert.equal( getDecimal(12.345), 0.345 );
});
it("возвращает дробную часть -1.2 как 0.2", function() {
assert.equal( getDecimal(-1.2), 0.2 );
});
it("возвращает дробную часть 5 как 0", function() {
assert.equal( getDecimal(5), 0 );
});
});

View file

@ -0,0 +1,82 @@
# Функция
Первая идея может быть такой:
```js
//+ run
function getDecimal(num) {
return num - Math.floor(num);
}
alert( getDecimal(12.5) ); // 0.5
*!*
alert( getDecimal(-1.2) ); // 0.8, неверно!
*/!*
```
Как видно из примера выше, для отрицательных чисел она не работает.
Это потому, что округление `Math.floor` происходит всегда к ближайшему меньшему целому, то есть `Math.floor(-1.2) = -2`, а нам бы хотелось убрать целую часть, т.е. получить `-1`.
Можно попытаться решить проблему так:
```js
//+ run
function getDecimal(num) {
return num > 0 ? num - Math.floor(num) : Math.ceil(num) - num;
}
alert( getDecimal(12.5) ); // 0.5
*!*
alert( getDecimal(-1.2) ); // 0.19999999999999996, неверно!
alert( getDecimal(1.2) ); // 0.19999999999999996
*/!*
```
Проблема с отрицательными числами решена, но результат, увы, не совсем тот.
Внутреннее неточное представление чисел приводит к ошибке в вычислениях, которая проявляется при работе и с положительными и с отрицательными числами.
Давайте попробуем ещё вариант -- получим остаток при делении на `1`. При таком делении от любого числа в остатке окажется именно дробная часть:
```js
//+ run
function getDecimal(num) {
return num > 0 ? (num % 1) : (-num % 1);
}
alert( getDecimal(12.5) ); // 0.5
*!*
alert( getDecimal(1.2) ); // 0.19999999999999996, неверно!
*/!*
```
В общем-то, работает, функция стала короче, но, увы, ошибка сохранилась.
Что делать?
Увы, операции с десятичными дробями подразумевают некоторую потерю точности.
Зависит от ситуации.
<ul>
<li>Если внешний вид числа неважен и ошибка в вычислениях допустима -- она ведь очень мала, то можно оставить как есть.</li>
<li>Перейти на промежуточные целочисленные вычисления там, где это возможно.</li>
<li>Если мы знаем, что десятичная часть жёстко ограничена, к примеру, может содержать не более 2 знаков то можно округлить число, то есть вернуть `+num.toFixed(2)`.</li>
</ul>
Если эти варианты не подходят, то можно работать с числом как со строкой:
```js
//+ run
function getDecimal(num) {
var str = "" + num;
var zeroPos = str.indexOf(".");
if (zeroPos == -1) return 0;
str = str.slice( zeroPos );
return +str;
}
alert( getDecimal(12.5) ); // 0.5
alert( getDecimal(1.2) ); // 0.2
```

View file

@ -0,0 +1,12 @@
# Как получить дробную часть числа?
[importance 4]
Напишите функцию `getDecimal(num)`, которая возвращает десятичную часть числа:
```js
alert( getDecimal(12.345) ); // 0.345
alert( getDecimal(1.2) ); // 0.2
alert( getDecimal(-1.2) ); // 0.2
```

View file

@ -0,0 +1,33 @@
```js
//+ run
function fibBinet(n) {
var phi = (1 + Math.sqrt(5)) / 2;
// используем Math.round для округления до ближайшего целого
return Math.round( Math.pow(phi, n) / Math.sqrt(5) );
}
function fib(n){
var a=1, b=0, x;
for(i=0; i<n; i++) {
x = a+b;
a = b
b = x;
}
return b;
}
alert( fibBinet(2) ); // 1, равно fib(2)
alert( fibBinet(8) ); // 21, равно fib(8)
*!*
alert( fibBinet(77)); // 5527939700884755
alert( fib(77) ); // 5527939700884757, не совпадает!
*/!*
```
**Результат вычисления <code>F<sub>77</sub></code> получился неверным!**
Причина -- в ошибках округления, ведь √5 -- бесконечная дробь.
Ошибки округления при вычислениях множатся и, в итоге, дают расхождение.

View file

@ -0,0 +1,27 @@
# Формула Бине
[importance 4]
Последовательность [чисел Фибоначчи](http://ru.wikipedia.org/wiki/%D0%A7%D0%B8%D1%81%D0%BB%D0%B0_%D0%A4%D0%B8%D0%B1%D0%BE%D0%BD%D0%B0%D1%87%D1%87%D0%B8) имеет формулу <code>F<sub>n</sub> = F<sub>n-1</sub> + F<sub>n-2</sub></code>. То есть, следующее число получается как сумма двух предыдущих.
Первые два числа равны `1`, затем `2(1+1)`, затем `3(1+2)`, `5(2+3)` и так далее: `1, 1, 2, 3, 5, 8, 13, 21...`.
Код для их вычисления (из задачи [](/task/fibonacci-numbers)):
```js
function fib(n){
var a=1, b=0, x;
for(i=0; i<n; i++) {
x = a+b;
a = b
b = x;
}
return b;
}
```
Существует [формула Бине](http://ru.wikipedia.org/wiki/%D0%A7%D0%B8%D1%81%D0%BB%D0%B0_%D0%A4%D0%B8%D0%B1%D0%BE%D0%BD%D0%B0%D1%87%D1%87%D0%B8#.D0.A4.D0.BE.D1.80.D0.BC.D1.83.D0.BB.D0.B0_.D0.91.D0.B8.D0.BD.D0.B5), согласно которой <code>F<sub>n</sub></code> равно ближайшему целому для <code>ϕ<sup>n</sup>/√5</code>, где <code>ϕ=(1+√5)/2</code> -- золотое сечение.
Напишите функцию `fibBinet(n)`, которая будет вычислять <code>F<sub>n</sub></code>, используя эту формулу. Проверьте её для значения <code>F<sub>77</sub></code> (должно получиться `fibBinet(77) = 5527939700884757`).
**Одинаковы ли результаты, полученные при помощи кода `fib(n)` выше и по формуле Бине? Если нет, то почему и какой из них верный?**

View file

@ -0,0 +1,9 @@
Сгенерируем значение в диапазоне `0..1` и умножим на `max`:
```js
//+ run
var max = 10;
alert( Math.random()*max );
```

View file

@ -0,0 +1,5 @@
# Случайное из интервала (0, max)
[importance 2]
Напишите код для генерации случайного значения в диапазоне от `0` до `max`, не включая `max`.

View file

@ -0,0 +1,9 @@
Сгенерируем значение из интервала `0..max-min`, а затем сдвинем на `min`:
```js
//+ run
var min=5, max = 10;
alert( min + Math.random()*(max-min) );
```

View file

@ -0,0 +1,5 @@
# Случайное из интервала (min, max)
[importance 2]
Напишите код для генерации случайного числа от `min` до `max`, не включая `max`.

View file

@ -0,0 +1,78 @@
# Очевидное неверное решение (round)
Самый простой, но неверный способ -- это сгенерировать значение в интервале `min..max` и округлить его `Math.round`, вот так:
```js
//+ run
function randomInteger(min, max) {
var rand = min + Math.random()*(max-min)
rand = Math.round(rand);
return rand;
}
alert( randomInteger(1, 3) );
```
Эта функция работает. Но при этом она некорректна: вероятность получить крайние значения `min` и `max` будет в два раза меньше, чем любые другие.
При многократном запуске этого кода вы легко заметите, что `2` выпадает чаще всех.
Это происходит из-за того, что `Math.round()` получает разнообразные случайные числа из интервала от `1` до `3`, но при округлении до ближайшего целого получится, что:
```js
значения из диапазона 1 ... 1.49999.. станут 1
значения из диапазона 1.5 ... 2.49999.. станут 2
значения из диапазона 2.5 ... 2.99999.. станут 3
```
Отсюда явно видно, что в `1` (как и `3`) попадает диапазон значений в два раза меньший, чем в `2`. Из-за этого такой перекос.
# Верное решение с round
Правильный способ: `Math.round(случайное от min-0.5 до max+0.5)`
```js
//+ run
*!*
function randomInteger(min, max) {
var rand = min - 0.5 + Math.random()*(max-min+1)
rand = Math.round(rand);
return rand;
}
*/!*
alert( randomInteger(5, 10) );
```
В этом случае диапазон будет тот же (`max-min+1`), но учтена механика округления `round`.
# Решение с floor
Альтернативный путь - применить округление `Math.floor()` к случайному числу от `min` до `max+1`.
Например, для генерации целого числа от `1` до `3`, создадим вспомогательное случайное значение от `1` до `4` (не включая `4`).
Тогда `Math.floor()` округлит их так:
```js
1 ... 1.999+ станет 1
2 ... 2.999+ станет 2
3 ... 3.999+ станет 3
```
Все диапазоны одинаковы.
Итак, код:
```js
//+ run
*!*
function randomInteger(min, max) {
var rand = min + Math.random() * (max+1-min);
rand = Math.floor(rand);
return rand;
}
*/!*
alert( randomInteger(5, 10) );
```

View file

@ -0,0 +1,7 @@
# Случайное целое от min до max
[importance 2]
Напишите функцию `randomInteger(min, max)` для генерации случайного **целого** числа между `min` и `max`, включая `min,max` как возможные значения.
Любое число из интервала `min..max` должно иметь одинаковую вероятность.

View file

@ -0,0 +1,615 @@
# Числа
Все числа в JavaScript, как целые так и дробные, имеют тип `Number` и хранятся в 64-битном формате [IEEE-754](http://en.wikipedia.org/wiki/IEEE_754-1985), также известном как "double precision".
Здесь мы рассмотрим различные тонкости, связанные с работой с числами в JavaScript.
## Способы записи
В JavaScript можно записывать числа не только в десятичной, но и в шестнадцатеричной (начинается с `0x`), а также восьмеричной (начинается с `0`) системах счисления:
```js
//+ run
alert( 0xFF ); // 255 в шестнадцатиричной системе
alert( 010 ); // 8 в восьмеричной системе
```
Также доступна запись в *"научном формате"* (ещё говорят "запись с плавающей точкой"), который выглядит как `<число>e<кол-во нулей>`.
Например, `1e3` -- это `1` с `3` нулями, то есть `1000`.
```js
//+ run
// еще пример научной формы: 3 с 5 нулями
alert( 3e5 ); // 300000
```
Если количество нулей отрицательно, то число сдвигается вправо за десятичную точку, так что получается десятичная дробь:
```js
//+ run
// здесь 3 сдвинуто 5 раз вправо, за десятичную точку.
alert( 3e-5 ); // 0.00003 <-- 5 нулей, включая начальный ноль
```
## Деление на ноль, Infinity
Представьте, что вы собираетесь создать новый язык... Люди будут называть его "JavaScript" (или LiveScript... неважно).
Что должно происходить при попытке деления на ноль?
Как правило, ошибка в программе... Во всяком случае, в большинстве языков программирования это именно так.
Но создатель JavaScript решил пойти математически правильным путем. Ведь чем меньше делитель, тем больше результат. При делении на очень-очень маленькое число должно получиться очень большое. В математическом анализе это описывается через [пределы](http://ru.wikipedia.org/wiki/%D0%9F%D1%80%D0%B5%D0%B4%D0%B5%D0%BB_(%D0%BC%D0%B0%D1%82%D0%B5%D0%BC%D0%B0%D1%82%D0%B8%D0%BA%D0%B0&#041;), и если подразумевать предел, то в качестве результата деления на `0` мы получаем "бесконечность", которая обозначается символом `∞` или, в JavaScript: `"Infinity"`.
```js
//+ run
alert(1/0); // Infinity
alert(12345/0); // Infinity
```
**`Infinity` -- особенное численное значение, которое ведет себя в точности как математическая бесконечность `∞`.**
<ul>
<li>`Infinity` больше любого числа.</li>
<li>Добавление к бесконечности не меняет её.</li>
</ul>
```js
//+ run
alert(Infinity > 1234567890); // true
alert(Infinity + 5 == Infinity); // true
```
**Бесконечность можно присвоить и в явном виде: `var x = Infinity`.**
Бывает и минус бесконечность `-Infinity`:
```js
//+ run
alert( -1 / 0 ); // -Infinity
```
Бесконечность можно получить также, если сделать ну очень большое число, для которого количество разрядов в двоичном представлении не помещается в соответствующую часть стандартного 64-битного формата, например:
```js
//+ run
alert( 1e500 ); // Infinity
```
## NaN
Если математическая операция не может быть совершена, то возвращается специальное значение `NaN` (Not-A-Number).
Например, деление `0/0` в математическом смысле неопределено, поэтому возвращает `NaN`:
```js
//+ run
alert( 0 / 0 ); // NaN
```
Значение `NaN` используется для обозначения математической ошибки и обладает следующими свойствами:
<ul>
<li>Значение `NaN` -- единственное, в своем роде, которое *не равно ничему, включая себя*.
Следующий код ничего не выведет:
```js
//+ run
if (NaN == NaN) alert("=="); // Ни один вызов
if (NaN === NaN) alert("==="); // не сработает
```
</li>
<li>Значение `NaN` можно проверить специальной функцией `isNaN(n)`, которая возвращает `true` если аргумент -- `NaN` и `false` для любого другого значения.
```js
//+ run
var n = 0/0;
alert( isNaN(n) ); // true
```
[smart]
Отсюда вытекает забавный способ проверки значения на `NaN`: можно проверить его на равенство самому себе, вот так:
```js
//+ run
var n = 0/0;
if (n !== n) alert('n = NaN!');
```
Это работает, но для наглядности лучше использовать `isNaN(n)`.
[/smart]
</li>
<li>Значение `NaN` "прилипчиво". Любая операция с `NaN` возвращает `NaN`.
```js
//+ run
alert( NaN + 1 ); // NaN
```
</li>
</ul>
Если аргумент `isNaN` -- не число, то он автоматически преобразуется к числу.
[summary]Никакие математические операции в JavaScript не могут привести к ошибке или "обрушить" программу.
В худшем случае, результат будет `NaN`.
[/summary]
## isFinite(n)
Итак, в JavaScript есть обычные числа и три специальных числовых значения: `NaN`, `Infinity` и `-Infinity`.
**Функция `isFinite(n)` возвращает `true` только тогда, когда `n` -- обычное число, а не одно из этих значений:**
```js
//+ run
alert( isFinite(1) ); // true
alert( isFinite(Infinity) ); // false
alert( isFinite(NaN) ); // false
```
Если аргумент `isFinite` -- не число, то он автоматически преобразуется к числу.
## Преобразование к числу
Строгое преобразование можно осуществить унарным плюсом `'+'`:
```js
//+ run
var s = "12.34";
alert( +s ); // 12.34
```
*Строгое* -- означает, что если строка не является в точности числом, то результат будет `NaN`:
```js
//+ run
alert( +"12test" ); // NaN
```
Единственное исключение -- пробельные символы в начале и в конце строки, которые игнорируются:
```js
//+ run
alert( +" -12"); // -12
alert( +" \n34 \n"); // 34, перевод строки \n является пробельным символом
alert( +"" ); // 0, пустая строка становится нулем
alert( +"1 2" ); // NaN, пробел посередине числа - ошибка
```
Аналогичным образом происходит преобразование и в других математических операторах и функциях:
```js
//+ run
alert( '12.34' / "-2" ); // -6.17
```
### isNaN -- проверка на число для строк
Функция `isNaN` является математической, она преобразует аргумент в число, а затем проверяет, `NaN` это или нет.
Поэтому можно использовать ее для проверки:
```js
//+ run
var x = "-11.5";
if (isNaN(x)) {
alert("Строка преобразовалась в NaN. Не число");
} else {
alert("Число");
}
```
Единственный тонкий момент -- в том, что пустая строка и строка из пробельных символов преобразуются к `0`:
```js
//+ run
alert(isNaN(" \n\n ")) // false, т.к. строка из пробелов преобразуется к 0
```
В случае, если применить такую проверку не к строке, то могут быть сюрпризы, в частности `isNaN` посчитает числами значения `false, true, null`, так как они хотя и не числа, но преобразуются к ним:
```js
+false = 0 // isNaN(false) преобразует false в число, получится 0 - ок
+true = 1 // тоже ок
+null = 0 // тоже ок
+undefined = NaN; // а вот это точно не число
```
## Мягкое преобразование: parseInt и parseFloat
В мире HTML/CSS многие значения не являются в точности числами. Например, метрики CSS: `10pt` или `-12px`.
Оператор `'+'` для таких значений возвратит `NaN`:
```js
//+ run
alert( +"12px" ) // NaN
```
Для удобного чтения таких значений существует функция `parseInt`:
```js
//+ run
alert( parseInt('12px') ); // 12
```
**`parseInt` и ее аналог `parseFloat` преобразуют строку символ за символом, пока это возможно.**
При возникновении ошибки возвращается число, которое получилось. `parseInt` читает из строки целое число, а `parseFloat` -- дробное.
```js
//+ run
alert( parseInt('12px') ) // 12, ошибка на символе 'p'
alert( parseFloat('12.3.4') ) // 12.3, ошибка на второй точке
```
Конечно, существуют ситуации, когда `parseInt/parseFloat` возвращают `NaN`. Это происходит при ошибке на первом же символе:
```js
//+ run
alert( parseInt('a123') ); // NaN
```
[warn header="Ошибка `parseInt('0..')`"]
`parseInt` (но не `parseFloat`) понимает 16-ричную систему счисления:
```js
//+ run
alert( parseInt('0xFF') ) // 255
```
В старом стандарте JavaScript он умел понимать и восьмеричную:
```js
//+ run
alert( parseInt('010') ) // в некоторых браузерах 8
```
Если вы хотите быть уверенным, что число, начинающееся с нуля, будет интерпретировано верно -- используйте второй необязательный аргумент `parseInt` -- основание системы счисления:
```js
//+ run
alert( parseInt('010', 10) ); // во всех браузерах 10
```
[/warn]
## Проверка на число для всех типов
Если вам нужна действительно точная проверка на число, которая не считает числом строку из пробелов, логические и специальные значения -- используйте следующую функцию `isNumeric`:
```js
function isNumeric(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
}
```
Разберёмся, как она работает. Начнём справа.
<ul>
<li>Функция `isFinite(n)` преобразует аргумент к числу и возвращает `true`, если это не `Infinity/-Infinity/NaN`.
Таким образом, правая часть отсеет заведомо не-числа, но оставит такие значения как `true/false/null` и пустую строку `''`, т.к. они корректно преобразуются в числа.
</li>
<li>Для их проверки нужна левая часть. Вызов `parseFloat(true/false/null/'')` вернёт `NaN` для этих значений.
Так устроена функция `parseFloat`: она преобразует аргумент к строке, т.е. `true/false/null` становятся `"true"/"false"/"null"`, а затем считывает из неё число, при этом пустая строка даёт `NaN`.</li>
</ul>
В результате отсеивается всё, кроме строк-чисел и обычных чисел.
## toString(система счисления)
Как показано выше, числа можно записывать не только в 10-чной, но и в 16-ричной системе. Но бывает и противоположная задача: получить 16-ричное представление числа. Для этого используется метод `toString(основание системы)`, например:
```js
//+ run
var n = 255;
alert( n.toString(16) ); // ff
```
Основание может быть любым от `2` до `36`.
<ul>
<li>Основание `2` бывает полезно для отладки битовых операций, которые мы пройдём чуть позже:
```js
//+ run
var n = 4;
alert( n.toString(2) ); // 100
```
</li>
<li>Основание `36` (по количеству букв в английском алфавите -- 26, вместе с цифрами, которых 10) используется для того, чтобы "кодировать" число в виде буквенно-цифровой строки. В этой системе счисления сначала используются цифры, а затем буквы от `a` до `z`:
```js
//+ run
var n = 1234567890;
alert( n.toString(36) ); // kf12oi
```
При помощи такого кодирования можно сделать длинный цифровой идентификатор короче, чтобы затем использовать его в URL.
</li>
</ul>
## Округление
Одна из самых частых операций с числом -- округление. В JavaScript существуют целых 3 функции для этого.
<dl>
<dt>`Math.floor`</dt>
<dd>Округляет вниз</dd>
<dt>`Math.ceil`</dt>
<dd>Округляет вверх</dd>
<dt>`Math.round`</dt>
<dd>Округляет до ближайшего целого</dd>
</dl>
```js
//+ run
alert( Math.floor(3.1) ); // 3
alert( Math.ceil(3.1) ); // 4
alert( Math.round(3.1) ); // 3
```
[smart header="Округление битовыми операторами"]
[Битовые операторы](/bitwise-operators) делают любое число 32-битным целым, обрезая десятичную часть.
В результате побитовая операция, которая не изменяет число, например, двойное битовое НЕ -- округляет его:
```js
//+ run
alert( ~~12.3 ); // 12
```
Любая побитовая операция такого рода подойдет, например XOR (исключающее ИЛИ, `"^"`) с нулем:
```js
//+ run
alert( 12.3 ^ 0 ); // 12
alert( 1.2 + 1.3 ^ 0); // 2, приоритет ^ меньше, чем +
```
Это удобно в первую очередь тем, что легко читается и не заставляет ставить дополнительные скобки как `Math.floor(...)`:
```js
var x = a * b / c ^ 0; // читается так: "a*b/c *!*и округлить*/!*"
```
[/smart]
### Округление до заданной точности
Обычный трюк -- это умножить и поделить на 10 с нужным количеством нулей. Например, округлим `3.456` до 2го знака после запятой:
```js
//+ run
var n = 3.456;
alert( Math.round( n * 100 ) / 100 ); // 3.456 -> 345.6 -> 346 -> 3.46
```
Таким образом можно округлять число и вверх и вниз.
### num.toFixed(precision)
Существует специальный метод `num.toFixed(precision)`, который округляет число `num` до точности `precision` и возвращает результат *в виде строки*:
```js
//+ run
var n = 12.34;
alert( n.toFixed(1) ); // "12.3"
```
Округление идёт до ближайшего значения, аналогично `Math.round`:
```js
//+ run
var n = 12.36;
alert( n.toFixed(1) ); // "12.4"
```
Итоговая строка, при необходимости, дополняется нулями до нужной точности:
```js
//+ run
var n = 12.34;
alert( n.toFixed(5) ); // "12.34000", добавлены нули до 5 знаков после запятой
```
Если нам нужно именно число, то мы можем получить его, применив `'+'` к результату `n.toFixed(..)`:
```js
//+ run
var n = 12.34;
alert( +n.toFixed(5) ); // 12.34
```
[warn header="Метод `toFixed` не эквивалентен `Math.round`!"]
Например, произведём округление до одного знака после запятой с использованием двух способов:
```js
//+ run
var price = 6.35;
alert( price.toFixed(1) ); // 6.3
alert( Math.round(price*10)/10 ); // 6.4
```
Как видно, результат разный! Вариант округления через `Math.round` получился более корректным, так как по общепринятым правилам `5` округляется вверх. А `toFixed` может округлить его как вверх, так и вниз. Почему? Скоро узнаем!
[/warn]
## Неточные вычисления
Запустите этот пример:
```js
//+ run
alert(0.1 + 0.2 == 0.3);
```
Запустили? Если нет -- все же сделайте это.
Ок, вы запустили его. Результат несколько странный, не так ли? Возможно, ошибка в браузере? Поменяйте браузер, запустите еще раз.
Хорошо, теперь мы можем быть уверены: `0.1 + 0.2` это не `0.3`. Но тогда что же это?
```js
//+ run
alert(0.1 + 0.2); // 0.30000000000000004
```
Как видите, произошла небольшая вычислительная ошибка.
Дело в том, что в стандарте IEEE 754 на число выделяется ровно 8 байт(=64 бита), не больше и не меньше.
Число `0.1 (=1/10)` короткое в десятичном формате, а в двоичной системе счисления это бесконечная дробь ([перевод десятичной дроби в двоичную систему](http://www.klgtu.ru/students/literature/inf_asu/1760.html)). Также бесконечной дробью является `0.2 (=2/10)`.
Двоичное значение бесконечных дробей хранится только до определенного знака, поэтому возникает неточность. Это даже можно увидеть:
```js
//+ run
alert( 0.1.toFixed(20) ); // 0.10000000000000000555
```
Когда мы складываем `0.1` и `0.2`, то две неточности складываются, получаем третью.
Конечно, это не означает, что точные вычисления для таких чисел невозможны. Они возможны. И даже необходимы.
Например, есть два способа сложить `0.1` и `0.2`:
<ol>
<li>Сделать их целыми, сложить, а потом поделить:
```js
//+ run
alert( (0.1*10 + 0.2*10) / 10 ); // 0.3
```
Это работает, т.к. числа `0.1*10 = 1` и `0.2*10 = 2` могут быть точно представлены в двоичной системе.
</li>
<li>Сложить, а затем округлить до разумного знака после запятой. Округления до 10-го знака обычно бывает достаточно, чтобы отсечь ошибку вычислений:
```js
//+ run
var result = 0.1 + 0.2;
alert( +result.toFixed(10) ); // 0.3
```
</li>
</ol>
[smart header="Забавный пример"]
Привет! Я -- число, растущее само по себе!
```js
//+ run
alert(9999999999999999);
```
Причина та же -- потеря точности.
Из `64` бит, отведённых на число, сами цифры числа занимают до `52` бит, остальные `11` бит хранят позицию десятичной точки и один бит -- знак. Так что если `52` бит не хватает на цифры, то при записи пропадут младшие разряды.
Интерпретатор не выдаст ошибку, но в результате получится "не совсем то число", что мы и видим в примере выше. Как говорится: "как смог, так записал".
[/smart]
Ради справедливости заметим, что в точности то же самое происходит в любом другом языке, где используется формат IEEE 754, включая Java, C, PHP, Ruby, Perl.
## Другие математические методы
JavaScript предоставляет базовые тригонометрические и некоторые другие функции для работы с числами.
### Тригонометрия
Встроенные функции для тригонометрических вычислений:
<dl>
<dt>`Math.acos(x)`</dt>
<dd>Возвращает арккосинус `x` (в радианах)</dd>
<dt>`Math.asin(x)`</dt>
<dd>Возвращает арксинус `x` (в радианах)</dd>
<dt>`Math.atan`</dt>
<dd>Возвращает арктангенс `x` (в радианах)</dd>
<dt>`Math.atan2(y, x)`</dt>
<dd>Возвращает угол до точки `(y, x)`. Описание функции: [Atan2](http://en.wikipedia.org/wiki/Atan2).</dd>
<dt>`Math.sin(x)`</dt>
<dd>Вычисляет синус `x` (в радианах)</dd>
<dt>`Math.cos(x)`</dt>
<dd>Вычисляет косинус `x` (в радианах)</dd>
<dt>`Math.tan(x)`</dt>
<dd>Возвращает тангенс `x` (в радианах)</dd>
</dl>
### Функции общего назначения
Разные полезные функции:
<dl>
<dt>`Math.sqrt(x)`</dt>
<dd>Возвращает квадратный корень из `x`.</dd>
<dt>`Math.log(x)`</dt>
<dd>Возвращает натуральный (по основанию <code>e</code>) логарифм `x`.</dd>
<dt>`Math.pow(x, exp)`</dt>
<dd>Возводит число в степень, возвращает <code>x<sup>exp</sup></code>, например `Math.pow(2,3) = 8`. Работает в том числе с дробными и отрицательными степенями, например: `Math.pow(4, -1/2) = 0.5`.</dd>
<dt>`Math.abs(x)`</dt>
<dd>Возвращает абсолютное значение числа</dd>
<dt>`Math.exp(x)`</dt>
<dd>Возвращает <code>e<sup>x</sup></code>, где <code>e</code> -- основание натуральных логарифмов.</dd>
<dt>`Math.max(a, b, c...)`</dt>
<dd>Возвращает наибольший из списка аргументов</dd>
<dt>`Math.min(a, b, c...)`</dt>
<dd>Возвращает наименьший из списка аргументов</dd>
<dt>`Math.random()`</dt>
<dd>Возвращает псевдо-случайное число в интервале [0,1) - то есть между 0(включительно) и 1(не включая). Генератор случайных чисел инициализуется текущим временем.</dd>
</dl>
### Форматирование
Для красивого вывода чисел в стандарте [ECMA 402](http://www.ecma-international.org/ecma-402/1.0/ECMA-402.pdf) есть метод `toLocaleString()`:
```js
//+ run
var number = 123456789;
alert( number.toLocaleString() ); // 123 456 789
```
Его поддерживают все современные браузеры, кроме IE10- (для которых нужно подключить библиотеку [Intl.JS](https://github.com/andyearnshaw/Intl.js/)). Он также умеет форматировать валюту и проценты. Более подробно про устройство этого метода можно будет узнать в статье [](/intl), когда это вам понадобится.
## Итого
<ul>
<li>Числа могут быть записаны в шестнадцатиричной, восьмеричной системе, а также "научным" способом.</li>
<li>В JavaScript существует числовое значение бесконечность `Infinity`.</li>
<li>Ошибка вычислений дает `NaN`.</li>
<li>Арифметические и математические функции преобразуют строку в точности в число, игнорируя начальные и конечные пробелы.</li>
<li>Функции `parseInt/parseFloat` делают числа из строк, которые начинаются с числа.</li>
<li>Есть четыре способа округления: `Math.floor`, `Math.round`, `Math.ceil` и битовый оператор. Для округления до нужного знака используйте `+n.toFixed(p)` или трюк с умножением и делением на <code>10<sup>p</sup></code>.</li>
<li>Дробные числа дают ошибку вычислений. При необходимости ее можно отсечь округлением до нужного знака.</li>
<li>Случайные числа от `0` до `1` генерируются с помощью `Math.random()`, остальные -- преобразованием из них.</li>
</ul>
Существуют и другие математические функции. Вы можете ознакомиться с ними в справочнике в разделах <a href="http://javascript.ru/Number">Number</a> и <a href="http://javascript.ru/Math">Math</a>.

View file

@ -0,0 +1,10 @@
```js
var user = {};
user.name = "Вася";
user.surname = "Петров";
user.name = "Сергей";
delete user.name;
```

View file

@ -0,0 +1,13 @@
# Первый объект
[importance 3]
Мини-задача на синтаксис объектов. Напишите код, по строке на каждое действие.
<ol>
<li>Создайте пустой объект `user`.</li>
<li>Добавьте свойство `name` со значением `Вася`.</li>
<li>Добавьте свойство `surname` со значением `Петров`.</li>
<li>Поменяйте значение `name` на `Сергей`.</li>
<li>Удалите свойство `name` из объекта.</li>
</ol>

View file

@ -0,0 +1,328 @@
# Объекты как ассоциативные массивы
Объекты в JavaScript являются "двуличными". Они сочетают в себе два важных функционала.
Первый -- это ассоциативный массив: структура, пригодная для хранения любых данных. В этой главе мы рассмотрим использование объектов именно как массивов.
Второй -- языковые возможности для объектно-ориентированного программирования. Эти возможности мы изучим в последующих разделах учебника.
[cut]
## Ассоциативные массивы
[Ассоциативный массив](http://ru.wikipedia.org/wiki/%D0%90%D1%81%D1%81%D0%BE%D1%86%D0%B8%D0%B0%D1%82%D0%B8%D0%B2%D0%BD%D1%8B%D0%B9_%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2) -- структура данных, в которой можно хранить любые данные в формате ключ-значение.
Её можно легко представить как шкаф с подписанными ящиками. Все данные хранятся в ящичках. По имени можно легко найти ящик и взять то значение, которое в нём лежит.
В отличие от реальных шкафов, в ассоциативный массив можно в любой момент добавить новые именованные "ящики" или удалить существующие. Далее мы увидим примеры, как это делается.
Кстати, в других языках программирования такую структуру данных также называют *"словарь"* и *"хэш"*.
## Создание объектов
Пустой объект (*"пустой шкаф"*) может быть создан одним из двух синтаксисов:
```js
1. o = new Object();
2. o = {}; // пустые фигурные скобки
```
Обычно все пользуются синтаксисом `(2)`, т.к. он короче.
## Операции с объектом
Объект может содержать в себе любые значения, которые называются *свойствами объекта*. Доступ к свойствам осуществляется по *имени свойства* (иногда говорят *"по ключу"*).
Например, создадим объект `person` для хранения информации о человеке:
```js
var person = {}; // пока пустой
```
Основные операции с объектами -- это:
<ol>
<li>**Присвоение свойства по ключу.**</li>
<li>**Чтение свойства по ключу.**</li>
<li>**Удаление свойства по ключу.**</li>
</ol>
Для обращения к свойствам используется запись "через точку", вида `объект.свойство`:
```js
//+ run
var person = {};
// *!*1. присвоение*/!*
// при присвоении свойства в объекте автоматически создаётся "ящик"
// с именем "name" и в него записывается содержимое 'Вася'
person.name = 'Вася';
person.age = 25; // запишем ещё одно свойство: с именем 'age' и значением 25
// *!*2. чтение*/!*
alert(person.name + ': ' + person.age); // вывести значения
// *!*3. удаление*/!*
delete person.name; // удалить "ящик" с именем "name" вместе со значением в нём
```
Следующая операция:
<ol start="4">
<li>**Проверка существования свойства с определенным ключом.**</li>
</ol>
Например, есть объект `person`, и нужно проверить, существует ли в нем свойство `age`.
Для проверки существования есть оператор `in`. Его синтаксис: `"prop" in obj`, причем имя свойства -- в виде строки, например:
```js
var person = { };
if (*!*"age" in person*/!*) {
alert("Свойство age существует!");
}
```
Впрочем, чаще используется другой способ -- сравнение значения с `undefined`.
Дело в том, что **в JavaScript можно обратиться к любому свойству объекта, даже если его нет**. Ошибки не будет.
Но если свойство не существует, то вернется специальное значение `undefined`:
```js
//+ run
var person = {};
alert(person.lalala); // undefined, нет свойства с ключом lalala
```
Таким образом **мы можем легко проверить существование свойства -- получив его и сравнив с `undefined`**:
```js
//+ run
var person = { name: "Василий" };
alert(person.lalala === undefined); // true, свойства нет
alert(person.name === undefined); // false, свойство есть.
```
[smart header="Разница между проверками `in` и `=== undefined`"]
Есть два средства для проверки наличия свойства в объекте: первое -- оператор `in`, второе -- получить его и сравнить его с `undefined`.
Они почти идентичны, но есть одна небольшая разница.
Дело в том, что технически возможно, что *свойство есть и равно `undefined`*:
```js
//+ untrusted refresh run
var obj = {};
obj.test = undefined; // добавили свойство со значением undefined
*!*
// проверим наличие свойств test и заведомо отсутствующего blabla
alert(obj.test === undefined); // true
alert(obj.blabla === undefined); // true
*/!*
```
...При этом, как видно из кода, при простом сравнении наличие такого свойства будет неотличимо от его отсутствия.
Но оператор `in` гарантирует правильный результат:
```js
//+ untrusted refresh run
var obj = {};
obj.test = undefined;
*!*
alert( "test" in obj ); // true
alert( "blabla" in obj ); // false
*/!*
```
Как правило, в коде мы не будем присваивать `undefined`, чтобы корректно работали обе проверки. А в качестве значения, обозначающего неизвестность и неопределенность, будем использовать `null`.
[/smart]
### Доступ через квадратные скобки
Существует альтернативный синтаксис работы со свойствами, использующий квадратные скобки `объект['свойство']`:
```js
//+ run
var person = {};
person['name'] = 'Вася'; // то же что и person.name = 'Вася'
```
Записи `person['name']` и `person.name` идентичны, но квадратные скобки позволяют использовать в качестве имени свойства любую строку:
```js
//+ run
var person = {};
person['любимый стиль музыки'] = 'Джаз'; // то же что и person.name = 'Вася'
```
Такое присвоение было бы невозможно "через точку", так интерпретатор после первого пробела подумает, что свойство закончилось, и далее выдаст ошибку:
```js
//+ run
person.любимый стиль музыки = 'Джаз'; // ??? ошибка
```
В обоих случаях, **имя свойства обязано быть строкой**. Если использовано значение другого типа -- JavaScript приведет его к строке автоматически.
### Доступ к свойству через переменную
Квадратные скобки также позволяют обратиться к свойству, имя которого хранится в переменной:
```js
//+ run
var person = { age: 25 };
var key = 'age';
alert( person[key] ); // выведет person['age']
```
Вообще, если имя свойства хранится в переменной (`var key = "age"`), то единственный способ к нему обратиться -- это квадратные скобки `person[key]`.
Доступ через точку используется, если мы на этапе написания программы уже знаем название свойства. А если оно будет определено по ходу выполнения, например, введено посетителем и записано в переменную, то единственный выбор -- квадратные скобки.
### Объявление со свойствами
Объект можно заполнить значениями при создании, указав их в фигурных скобках: `{ ключ1: значение1, ключ2: значение2, ... }`.
Такой синтаксис называется *литеральным* (оригинал - *literal*), например:
<img src="objectLiteral.png">
Следующие два фрагмента кода создают одинаковый объект:
```js
var menuSetup = {
width: 300,
height: 200,
title: "Menu"
};
// то же самое, что:
var menuSetup = {};
menuSetup.width = 300;
menuSetup.height = 200;
menuSetup.title = 'Menu';
```
**Названия свойств можно перечислять в кавычках или без, если они удовлетворяют ограничениям для имён переменных.**
Например:
```js
var menuSetup = {
width: 300,
'height': 200,
"мама мыла раму": true
};
```
**Значение у свойства может быть любое, в том числе и другой объект, который можно указать тут же:**
```js
var user = {
name: "Таня",
age: 25,
*!*
size: {
top: 90,
middle: 60,
bottom: 90
}
*/!*
}
alert( user.name ) // "Таня"
alert( user.size.top ) // 90
```
Здесь значением свойства `size` является объект `{top: 90, middle: 60, bottom: 90 }`.
## Компактное представление объектов
[warn header="Hardcore coders only"]
Эта секция относится ко внутреннему устройству структуры данных. Она не обязательна к прочтению.
[/warn]
Браузер использует специальное "компактное" представление объектов, чтобы сэкономить память в том случае, когда однотипных объектов много.
Например, посмотрим на такой объект:
```js
var user = {
name: "Vasya",
age: 25
};
```
Здесь содержится информация о свойстве `name` и его строковом значении, а также о свойстве `age` и его численном значении. Представим, что таких объектов много.
Получится, что информация об именах свойств `name` и `age` дублируется в каждом объекте. Чтобы этого избежать, браузер применяет оптимизацию.
**При создании множества объектов одного и того же вида (с одинаковыми полями) интерпретатор выносит описание полей в отдельную структуру. А сам объект остаётся в виде непрерывной области памяти с данными.**
Например, есть много объектов с полями `name` и `age`:
```js
{name: "Вася", age: 25}
{name: "Петя", age: 22}
{name: "Маша", age: 19}
...
```
Для их эффективного хранения будет создана структура, которая описывает данный вид объектов. Выглядеть она будет примерно так: `<string name, number age>`. А сами объекты будут представлены в памяти только данными:
```js
<структура: string name, number age>
Вася 25
Петя 22
Маша 19
```
При добавлении нового объекта такой структуры достаточно хранить значения полей, но не их имена. Экономия памяти -- налицо.
А что происходит, если к объекту добавляется новое свойство? Например, к одному из них добавили свойство `isAdmin`:
```js
user.isAdmin = true;
```
В этом случае браузер смотрит, есть ли уже структура, под которую подходит такой объект. Если нет -- она создаётся и объект привязывается к ней.
**Эта оптимизация является примером того, что далеко не всё то, что мы пишем, один-в-один переносится в память.**
Современные интерпретаторы очень стараются оптимизировать как код, так и структуры данных. Детали применения и реализации этого способа хранения варьируются от браузера к браузеру. О том, как это сделано в Chrome можно узнать, например, из презентации [Know Your Engines](http://www.slideshare.net/newmovie/know-yourengines-velocity2011). Она была некоторое время назад, но с тех пор мало что изменилось.
## Итого
Объекты -- это ассоциативные массивы с дополнительными возможностями:
<ul>
<li>Доступ к элементам осуществляется:
<ul>
<li>Напрямую по ключу `obj.prop = 5`</li>
<li>Через переменную, в которой хранится ключ:
```js
var key = "prop";
obj[key] = 5
```
</li>
</ul>
<li>Удаление ключей: `delete obj.name`.</li>
<li>Существование свойства может проверять оператор `in`: `if ("prop" in obj)`, как правило, работает и просто сравнение `if (obj.prop !== undefined)`.</li>
</ul>

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

View file

@ -0,0 +1,6 @@
function isEmpty(obj) {
for(var key in obj) {
return false;
}
return true;
}

View file

@ -0,0 +1,9 @@
describe("isEmpty", function() {
it("если объект пустой - возвращает true", function() {
assert.isTrue( isEmpty({}) );
});
it("если у объекта есть любое свойство, не важно какое - возвращает false", function() {
assert.isFalse( isEmpty({ anything: false }) );
});
});

View file

@ -0,0 +1,20 @@
```js
//+ run
function isEmpty(obj) {
for(var key in obj) {
return false;
}
return true;
}
var schedule = {};
alert( isEmpty( schedule ) ); // true
schedule["8:30"] = "подъём";
alert( isEmpty( schedule ) ); // false
```

View file

@ -0,0 +1,22 @@
# Определите, пуст ли объект
[importance 5]
Создайте функцию `isEmpty(obj)`, которая возвращает `true`, если в объекте нет свойств и `false` -- если хоть одно свойство есть.
Работать должно так:
```js
function isEmpty(obj) {
/* ваш код */
}
var schedule = {};
alert( isEmpty( schedule ) ); // true
schedule["8:30"] = "подъём";
alert( isEmpty( schedule ) ); // false
```

View file

@ -0,0 +1,20 @@
```js
//+ run
"use strict";
var salaries = {
"Вася": 100,
"Петя": 300,
"Даша": 250
};
var sum = 0;
for(var name in salaries) {
sum += salaries[name];
}
alert(sum);
```

View file

@ -0,0 +1,23 @@
# Сумма свойств
[importance 5]
Есть объект `salaries` с зарплатами. Напишите код, который выведет сумму всех зарплат.
Если объект пустой, то результат должен быть `0`.
Например:
```js
"use strict";
var salaries = {
"Вася": 100,
"Петя": 300,
"Даша": 250
};
//... ваш код выведет 650
```
P.S. Сверху стоит `use strict`, чтобы не забыть объявить переменные.

View file

@ -0,0 +1,24 @@
```js
//+ run
"use strict";
var salaries = {
"Вася": 100,
"Петя": 300,
"Даша": 250
};
var max = 0;
var maxName = "";
for(var name in salaries) {
if (max < salaries[name]) {
max = salaries[name];
maxName = name;
}
}
alert(maxName || "нет сотрудников");
```

View file

@ -0,0 +1,22 @@
# Свойство с наибольшим значением
[importance 5]
Есть объект `salaries` с зарплатами. Напишите код, который выведет имя сотрудника, у которого самая большая зарплата.
Если объект пустой, то пусть он выводит "нет сотрудников".
Например:
```js
"use strict";
var salaries = {
"Вася": 100,
"Петя": 300,
"Даша": 250
};
// ... ваш код выведет "Петя"
```

View file

@ -0,0 +1,11 @@
function isNumeric(n) {
return !isNaN(parseFloat(n)) && isFinite(n)
}
function multiplyNumeric(obj) {
for(var key in obj) {
if (isNumeric( obj[key] )) {
obj[key] *= 2;
}
}
}

View file

@ -0,0 +1,5 @@
function isNumeric(n) {
return !isNaN(parseFloat(n)) && isFinite(n)
}
// ... ваш код ...

View file

@ -0,0 +1,13 @@
describe("multiplyNumeric", function() {
it("умножает численные свойства на 2", function() {
var menu = {
width: 200,
height: "300",
title: "Моё меню"
};
multiplyNumeric(menu);
assert.equal( menu.width, 400 );
assert.equal( menu.height, 600 );
assert.equal( menu.title, "Моё меню" );
});
});

View file

@ -0,0 +1,27 @@
```js
//+ run
var menu = {
width: 200,
height: 300,
title: "My menu"
};
function isNumeric(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
}
function multiplyNumeric(obj) {
for(var key in obj) {
if (isNumeric( obj[key] )) {
obj[key] *= 2;
}
}
}
multiplyNumeric(menu);
alert("menu width="+menu.width+" height="+menu.height+" title="+menu.title);
```

View file

@ -0,0 +1,32 @@
# Умножьте численные свойства на 2
[importance 3]
Создайте функцию `multiplyNumeric`, которая получает объект и умножает все численные свойства на 2. Например:
```js
// до вызова
var menu = {
width: 200,
height: 300,
title: "My menu"
};
multiplyNumeric(menu);
// после вызова
menu = {
width: 400,
height: 600,
title: "My menu"
};
```
P.S. Для проверки на число используйте функцию:
```js
function isNumeric(n) {
return !isNaN(parseFloat(n)) && isFinite(n)
}
```

View file

@ -0,0 +1,173 @@
# Объекты: перебор свойств
Для перебора всех свойств из объекта используется цикл по свойствам `for..in`. Это специальная синтаксическая конструкция, которая работает не так, как обычный цикл.
[cut]
## for..in [#for..in]
Синтаксис:
```js
for (key in obj) {
/* ... делать что-то с obj[key] ... */
}
```
При этом в `key` будут последовательно записаны имена свойств. Конечно, вместо `key` может быть любое другое имя переменной.
[smart header="Объявление переменной в цикле `for (var key in obj)`"]
Переменную можно объявить прямо в цикле:
```js
for (*!*var key*/!* in menu) {
// ...
}
```
Так иногда пишут для краткости кода.
[/smart]
Например:
```js
//+ run
var menu = {
width: 300,
height: 200,
title: "Menu"
};
for (var key in menu) {
// этот код будет вызван для каждого свойства объекта
// ..и выведет имя свойства и его значение
*!*
alert("Ключ: " + key + " значение:" + menu[key]);
*/!*
}
```
Обратите внимание, мы использовали квадратные скобки `menu[key]`. Как уже говорилось, если имя свойства хранится в переменной, то обратиться к нему можно только так, не через точку.
## Количество свойств в объекте
Как узнать, сколько свойств хранит объект?
Готового метода для этого нет.
Самый кросс-браузерный способ -- это сделать цикл по свойствам и посчитать, вот так:
```js
//+ run
var menu = {
width: 300,
height: 200,
title: "Menu"
};
*!*
var counter = 0;
for (var key in menu) {
counter++;
}
*/!*
alert("Всего свойств: " + counter);
```
В следующих главах мы пройдём массивы и познакомимся с другим, более коротким, вызовом: `Object.keys(menu).length`.
## В каком порядке перебираются свойства?
Для примера, рассмотрим объект, который задаёт список опций для выбора страны:
```js
var codes = {
// телефонные коды в формате "код страны": "название"
"7": "Россия",
"38": "Украина",
// ..,
"1": "США"
};
```
Здесь мы предполагаем, что большинство посетителей из России, и поэтому начинаем с `7`, это зависит от проекта.
При выборе телефонного кода мы хотели бы предлагать варианты, начиная с первого. Обычно на основе списка генерируется `select`, но здесь нам важно не это, а важно другое.
**Правда ли, что при переборе `for(key in codes)` ключи `key` будут перечислены именно в том порядке, в котором заданы?**
**По стандарту -- нет. Но некоторое соглашение об этом, всё же, есть.**
Соглашение говорит, что если имя свойства -- нечисловая строка, то такие ключи всегда перебираются в том же порядке. Так получилось по историческим причинам и изменить это сложно: поломается много готового кода.
С другой стороны, если имя свойства -- число, то все современные браузеры сортируют такие свойства в целях внутренней оптимизации.
К примеру, рассмотрим объект с заведомо нечисловыми свойствами:
```js
//+ run
var user = {
name: "Вася",
surname: "Петров"
};
user.age = 25;
*!*
// порядок перебора соответствует порядку присвоения свойства
*/!*
for (var prop in user) {
alert(prop); // name, surname, age
}
```
А теперь -- что будет, если перебрать объект с кодами?
```js
//+ run
var codes = {
// телефонные коды в формате "код страны": "название"
"7": "Россия",
"38": "Украина",
"1": "США"
};
for(var code in codes) alert(code); // 1, 7, 38
```
При запуске этого кода в современном браузере мы увидим, что на первое место попал код США!
Нарушение порядка возникло, потому что ключи численные. Интерпретатор JavaScript видит, что строка на самом деле является числом и преобразует ключ в немного другой внутренний формат. Дополнительным эффектом внутренних оптимизаций является сортировка.
**А что, если мы хотим, чтобы порядок был именно таким, какой мы задали?**
Это возможно. Можно применить небольшой хак, который заключается в том, чтобы сделать все ключи нечисловыми, например, добавим в начало дополнительный символ `'+'`:
```js
//+ run
var codes = {
"+7": "Россия",
"+38": "Украина",
"+1": "США"
};
for (var code in codes ) {
var value = codes[code];
code = +code; // ..если нам нужно именно число, преобразуем: "+7" -> 7
alert( code + ": " + value ); // 7, 38, 1 во всех браузерах
}
```
## Итого
<ul>
<li>Цикл по ключам: `for (key in obj)`.</li>
<li>Порядок перебора соответствует порядку объявления для нечисловых ключей, а числовые -- сортируются (в современных браузерах).</li>
<li>Для того, чтобы гарантировать перебор ключей в нужном порядке, их делают "нечисловыми", например добавляя в начало `+`, а потом, в процессе обработки, преобразуют ключи в числа.</li>
</ul>

View file

@ -0,0 +1,162 @@
# Объекты: передача по ссылке
Фундаментальным отличием объектов от примитивов, является их копирование "по ссылке".
[cut]
## Копирование по значению
Обычные значения: строки, числа, булевы значения, `null/undefined` при присваивании переменных копируются целиком или, как говорят, *"по значению"*.
```js
var message = "Привет";
var phrase = message;
```
В результате такого копирования получились две полностью независимые переменные, в каждой из которых хранится значение `"Привет"`.
<img src="message_box_hello.png"> <img src="phrase_box_hello.png">
## Копирование по ссылке
С объектами -- всё не так.
**В переменной, которой присвоен объект, хранится не сам объект, а "адрес его места в памяти", иными словами -- "ссылка" на него.**
Например, обычную переменную можно изобразить как коробку с данными:
```js
var message = "Привет"; // значение в переменной
```
<img src="box-message-hello.png">
А вот как выглядит переменная, которой присвоен объект:
```js
var user = { name: "Вася" };
```
<img src="referenceUser.png">
Внимание: объект -- вне переменной. В переменной -- лишь ссылка на него.
**При копировании переменной с объектом -- копируется эта ссылка, а объект по-прежнему остается в единственном экземпляре.**
Например:
```js
var user = { name: "Вася" }; // в переменной - ссылка
var admin = user; // скопировали ссылку
```
Получили две переменные, в которых находятся ссылки на один и тот же объект:
<img src="referenceUserAdmin.png">
**Так как объект всего один, то изменения через любую переменную видны в других переменных:**
```js
//+ run
var user = { name: 'Вася' };
var admin = user;
*!*admin.name*/!* = 'Петя'; // поменяли данные через admin
alert(*!*user.name*/!*); // 'Петя', изменения видны в user
```
[smart header="Переменная с объектом как \"ключ\" к сейфу с данными"]
Ещё одна аналогия: переменная, в которую присвоен объект, на самом деле хранит не сами данные, а ключ к сейфу, где они хранятся.
При копировании её, получается что мы сделали копию ключа, но сейф по-прежнему один. По какому бы ключу мы не залезли в сейф, данные -- одни и те же.
[/smart]
## Клонирование объектов
Иногда, на практике -- очень редко, нужно скопировать объект целиком, создать именно полную копию, "независимый клон".
Что ж, можно сделать и это. Для этого нужно пройти по объекту, достать данные и скопировать на уровне примитивов.
Примерно так:
```js
//+ run
var user = {
name: "Вася",
age: 30
};
*!*
var clone = {}; // новый пустой объект
// скопируем в него все свойства user
for(var key in user) {
clone[key] = user[key];
}
*/!*
// теперь clone -- полностью независимая копия
clone.name = "Петя"; // поменяли данные в clone
alert(user.name); // по-прежнем "Вася"
```
В этом коде не учитывается, что свойства объектов, в свою очередь, могут хранить ссылки на другие объекты. Можно обойти такие подобъекты и тоже склонировать их.
Это называют "глубоким" клонированием. Для того, чтобы это сделать, нужно рекурсивно обойти объект вместе с подобъектами.
## Вывод в консоли
Откройте консоль браузера (обычно [key F12]) и запустите следующий код:
```js
//+ run
var time = {
year: 2345,
month: 11,
day: 10,
hour: 11,
minute: 12,
second: 13,
microsecond: 123456
}
console.log(time); // (*)
time.microsecond++; // (**)
console.log(time);
time.microsecond++;
console.log(time);
time.microsecond++;
```
Как видно, некий объект выводится `(*)`, затем он меняется `(**)` и снова выводится, и так несколько раз. Пока ничего необычного, типичная ситуация -- скрипт делает какую-то работу с объектом и выводит в консоли то, как она продвигается.
Необычное -- в другом!
При раскрытии каждый объект будет выглядеть примерно так (в Chrome):
<img src="object-reference-console.png">
**Судя по выводу, свойство `microsecond` всегда было равно `123459`... Или нет?**
Конечно, нет! Консоль нас просто дурит.
**При "раскрытии" свойств объекта в консоли -- браузер всегда выводит их текущие (на момент раскрытия) значения.**
Так происходит именно потому, что вывод не делает "копию" текущего содержимого, а сохраняет лишь ссылку на объект. В будущем, при отладке скриптов у вас не раз возникнет подобная ситуация :)
## Итого
<ul>
<li>Объект присваивается и копируется "по ссылке". То есть, в переменной хранится не сам объект а, условно говоря, адрес в памяти, где он находится.</li>
<li>Если переменная-объект скопирована или передана в функцию, то копируется именно эта ссылка, а объект остаётся один в памяти.</li>
</ul>
Это -- одно из ключевых отличий объекта от примитива (числа, строки...), который при присвоении как раз копируется "по значению", то есть полностью.

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 278 KiB

View file

@ -0,0 +1,16 @@
Последний элемент имеет индекс на `1` меньший, чем длина массива.
Например:
```js
var fruits = ["Яблоко", "Груша", "Слива"];
```
Длина массива этого массива `fruits.length` равна `3`. Здесь "Яблоко" имеет индекс `0`, "Груша" -- индекс `1`, "Слива" -- индекс `2`.
То есть, для массива длины `goods`:
```js
var lastItem = goods[goods.length-1]; // получить последний элемент
```

View file

@ -0,0 +1,9 @@
# Получить последний элемент массива
[importance 5]
Как получить последний элемент из произвольного массива?
У нас есть массив `goods`. Сколько в нем элементов -- не знаем, но можем прочитать из `goods.length`.
Напишите код для получения последнего элемента `goods`.

Some files were not shown because too many files have changed in this diff Show more