update
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
describe("ucFirst", function() {
|
||||
it('делает первый символ заглавным', function() {
|
||||
assert.strictEqual( ucFirst("вася"), "Вася" );
|
||||
});
|
||||
|
||||
it('для пустой строки возвращает пустую строку', function() {
|
||||
assert.strictEqual( ucFirst(""), "" );
|
||||
});
|
||||
});
|
||||
20
1-js/4-data-structures/1-string/1-ucfirst/solution.md
Normal 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. Возможны и более короткие решения, использующие методы для работы со строками, которые мы пройдём далее.
|
||||
12
1-js/4-data-structures/1-string/1-ucfirst/task.md
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# Сделать первый символ заглавным
|
||||
|
||||
[importance 5]
|
||||
|
||||
Напишите функцию `ucFirst(str)`, которая возвращает строку `str` с заглавным первым символом, например:
|
||||
|
||||
```js
|
||||
ucFirst("вася") == "Вася";
|
||||
ucFirst("") == ""; // нет ошибок при пустой строке
|
||||
```
|
||||
|
||||
P.S. В JavaScript нет встроенного метода для этого. Создайте функцию, используя `toUpperCase()` и `charAt()`.
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
function checkSpam(str) {
|
||||
var lowerStr = str.toLowerCase();
|
||||
|
||||
return !!(~lowerStr.indexOf('viagra') || ~lowerStr.indexOf('xxx'));
|
||||
}
|
||||
|
|
@ -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') );
|
||||
});
|
||||
});
|
||||
17
1-js/4-data-structures/1-string/2-check-spam/solution.md
Normal 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") );
|
||||
```
|
||||
|
||||
14
1-js/4-data-structures/1-string/2-check-spam/task.md
Normal 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
|
||||
```
|
||||
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
function truncate(str, maxlength) {
|
||||
return (str.length > maxlength) ?
|
||||
str.slice(0, maxlength - 3) + '...' : str;
|
||||
}
|
||||
16
1-js/4-data-structures/1-string/3-truncate/_js.view/test.js
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
describe("truncate", function() {
|
||||
it("обрезает строку до указанной длины (включая троеточие)", function() {
|
||||
assert.equal(
|
||||
truncate("Вот, что мне хотелось бы сказать на эту тему:", 20),
|
||||
"Вот, что мне хоте..."
|
||||
);
|
||||
});
|
||||
|
||||
it("не меняет короткие строки", function() {
|
||||
assert.equal(
|
||||
truncate("Всем привет!", 20),
|
||||
"Всем привет!"
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
28
1-js/4-data-structures/1-string/3-truncate/solution.md
Normal 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: `…`), который можно использовать вместо трёх точек. Если его использовать, то можно отрезать только один символ.
|
||||
17
1-js/4-data-structures/1-string/3-truncate/task.md
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# Усечение строки
|
||||
|
||||
[importance 5]
|
||||
|
||||
Создайте функцию `truncate(str, maxlength)`, которая проверяет длину строки `str`, и если она превосходит `maxlength` -- заменяет конец `str` на `"..."`, так чтобы ее длина стала равна `maxlength`.
|
||||
|
||||
Результатом функции должна быть (при необходимости) усечённая строка.
|
||||
|
||||
Например:
|
||||
|
||||
```js
|
||||
truncate("Вот, что мне хотелось бы сказать на эту тему:", 20) = "Вот, что мне хотело..."
|
||||
|
||||
truncate("Всем привет!", 20) = "Всем привет!"
|
||||
```
|
||||
|
||||
Эта функция имеет применение в жизни. Она используется, чтобы усекать слишком длинные темы сообщений.
|
||||
538
1-js/4-data-structures/1-string/article.md
Normal 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` -- юникодное представление символа копирайт ©
|
||||
</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 > 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).
|
||||
|
||||
Для этого нужно написать сначала `&#`, затем код, и завершить точкой с запятой `';'`. Например, символ `'а'` в виде числовой ссылки: `а`.
|
||||
|
||||
Если код хотят дать в 16-ричной системе счисления, то начинают с `&#x`.
|
||||
|
||||
В юникоде есть много забавных и полезных символов, например, символ ножниц: ✂ (`✂`), дроби: ½ (`½`) ¾ (`¾`) и другие. Их можно использовать вместо картинок в дизайне.
|
||||
[/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]().
|
||||
10
1-js/4-data-structures/10-datetime/1-new-date/solution.md
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
Дата в местной временной зоне создается при помощи `new Date`.
|
||||
|
||||
Месяцы начинаются с нуля, так что февраль имеет номер 1. Параметры можно указывать с точностью до минут:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var d = new Date(2012, 1, 20, 3, 12);
|
||||
alert(d);
|
||||
```
|
||||
|
||||
7
1-js/4-data-structures/10-datetime/1-new-date/task.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# Создайте дату
|
||||
|
||||
[importance 5]
|
||||
|
||||
Создайте объект `Date` для даты: 20 февраля 2012 года, 3 часа 12 минут.
|
||||
|
||||
Временная зона -- местная. Выведите его на экран.
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
function getWeekDay(date) {
|
||||
var days = ['вс','пн','вт','ср','чт','пт','сб'] ;
|
||||
|
||||
return days[ date.getDay() ];
|
||||
}
|
||||
|
|
@ -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)), 'чт');
|
||||
});
|
||||
});
|
||||
|
|
@ -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) ); // 'пт'
|
||||
```
|
||||
|
||||
13
1-js/4-data-structures/10-datetime/2-get-week-day/task.md
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# Имя дня недели
|
||||
|
||||
[importance 5]
|
||||
|
||||
Создайте функцию `getWeekDay(date)`, которая выводит текущий день недели в коротком формате 'пн', 'вт', ... 'вс'.
|
||||
|
||||
Например:
|
||||
|
||||
```js
|
||||
var date = new Date(2012,0,3); // 3 января 2012
|
||||
alert( getWeekDay(date) ); // Должно вывести 'вт'
|
||||
```
|
||||
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
function getLocalDay(date) {
|
||||
|
||||
var day = date.getDay();
|
||||
|
||||
if ( day == 0 ) { // день 0 становится 7
|
||||
day = 7;
|
||||
}
|
||||
|
||||
return day;
|
||||
}
|
||||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
19
1-js/4-data-structures/10-datetime/3-weekday/solution.md
Normal 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(вс).
|
||||
13
1-js/4-data-structures/10-datetime/3-weekday/task.md
Normal 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
|
||||
```
|
||||
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
function getDateAgo(date, days) {
|
||||
var dateCopy = new Date(date);
|
||||
|
||||
dateCopy.setDate( date.getDate() - days );
|
||||
return dateCopy.getDate();
|
||||
}
|
||||
|
|
@ -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());
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -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)
|
||||
```
|
||||
|
||||
17
1-js/4-data-structures/10-datetime/4-get-date-ago/task.md
Normal 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`.
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
function getLastDayOfMonth(year, month) {
|
||||
var date = new Date(year, month+1, 0);
|
||||
return date.getDate();
|
||||
}
|
||||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
@ -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
|
||||
```
|
||||
|
||||
|
|
@ -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` (високосный год, февраль).
|
||||
|
|
@ -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() );
|
||||
```
|
||||
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
# Сколько секунд уже прошло сегодня?
|
||||
|
||||
[importance 5]
|
||||
|
||||
Напишите функцию `getSecondsToday()` которая возвращает, сколько секунд прошло с начала сегодняшнего дня.
|
||||
|
||||
Например, если сейчас `10:00` и не было перехода на зимнее/летнее время, то:
|
||||
|
||||
```js
|
||||
getSecondsToday() == 36000 // (3600 * 10)
|
||||
```
|
||||
|
||||
Функция должна работать в любой день, т.е. в ней не должно быть конкретного значения сегодняшней даты.
|
||||
|
|
@ -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); // перевести в секунды
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
# Сколько секунд - до завтра?
|
||||
|
||||
[importance 5]
|
||||
|
||||
Напишите функцию `getSecondsToTomorrow()` которая возвращает, сколько секунд осталось до завтра.
|
||||
|
||||
Например, если сейчас `23:00`, то:
|
||||
|
||||
```js
|
||||
getSecondsToTomorrow() == 3600
|
||||
```
|
||||
|
||||
P.S. Функция должна работать в любой день, т.е. в ней не должно быть конкретного значения сегодняшней даты.
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
|
|
@ -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'
|
||||
```
|
||||
|
||||
|
|
@ -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.
|
||||
|
|
@ -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(':');
|
||||
}
|
||||
|
|
@ -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" );
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -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) ) ); // вчерашняя дата в формате "дд.мм.гг чч:мм"
|
||||
```
|
||||
|
||||
|
|
@ -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) ) ); // вчерашняя дата в формате "дд.мм.гг чч:мм"
|
||||
```
|
||||
|
||||
492
1-js/4-data-structures/10-datetime/article.md
Normal 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<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>
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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" );
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -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
|
||||
```
|
||||
|
||||
|
|
@ -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
|
||||
```
|
||||
|
||||
162
1-js/4-data-structures/11-typeof-duck-typing/article.md
Normal 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>
|
||||
|
||||
Там, где нужно различать объекты, обычно используется утиная типизация, то есть мы смотрим, есть ли в объекте нужный метод, желательно -- тот, который мы собираемся исползовать, но это не обязательно.
|
||||
|
||||
11
1-js/4-data-structures/2-number/1-sum-interface/solution.md
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
|
||||
|
||||
```js
|
||||
//+ run demo
|
||||
var a = +prompt("Введите первое число", "");
|
||||
var b = +prompt("Введите второе число", "");
|
||||
|
||||
alert( a + b );
|
||||
```
|
||||
|
||||
Обратите внимание на оператор `+` перед `prompt`, он сразу приводит вводимое значение к числу. Если бы его не было, то `a` и `b` были бы строками и складывались бы как строки, то есть `"1" + "2" = "12"`.
|
||||
9
1-js/4-data-structures/2-number/1-sum-interface/task.md
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# Интерфейс суммы
|
||||
|
||||
[importance 5]
|
||||
|
||||
Создайте страницу, которая предлагает ввести два числа и выводит их сумму.
|
||||
|
||||
[demo /]
|
||||
|
||||
P.S. Есть "подводный камень" при работе с типами.
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
Во внутреннем двоичном представлении `6.35` является бесконечной двоичной дробью. Хранится она с потерей точности.. А впрочем, посмотрим сами:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( 6.35.toFixed(20) ); // 6.34999999999999964473
|
||||
```
|
||||
|
||||
Интерпретатор видит число как `6.34...`, поэтому и округляет вниз.
|
||||
19
1-js/4-data-structures/2-number/2-why-rounded-down/task.md
Normal 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
|
||||
```
|
||||
|
||||
13
1-js/4-data-structures/2-number/3-sum-prices/solution.md
Normal 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>
|
||||
16
1-js/4-data-structures/2-number/3-sum-prices/task.md
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# Сложение цен
|
||||
|
||||
[importance 5]
|
||||
|
||||
Представьте себе электронный магазин. Цены даны с точностью до копейки(цента, евроцента и т.п.).
|
||||
|
||||
Вы пишете интерфейс для него. Основная работа происходит на сервере, но и на клиенте все должно быть хорошо. Сложение цен на купленные товары и умножение их на количество является обычной операцией.
|
||||
|
||||
Получится глупо, если при заказе двух товаров с ценами `0.10$` и `0.20$` человек получит общую стоимость `0.30000000000000004$`:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( 0.1 + 0.2 + '$' );
|
||||
```
|
||||
|
||||
Что можно сделать, чтобы избежать проблем с ошибками округления?
|
||||
|
|
@ -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`.
|
||||
13
1-js/4-data-structures/2-number/4-endless-loop-error/task.md
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# Бесконечный цикл по ошибке
|
||||
|
||||
[importance 4]
|
||||
|
||||
Этот цикл - бесконечный. Почему?
|
||||
|
||||
```js
|
||||
var i = 0;
|
||||
while(i != 10) {
|
||||
i += 0.2;
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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 );
|
||||
});
|
||||
});
|
||||
82
1-js/4-data-structures/2-number/5-get-decimal/solution.md
Normal 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
|
||||
```
|
||||
|
||||
12
1-js/4-data-structures/2-number/5-get-decimal/task.md
Normal 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
|
||||
```
|
||||
|
||||
33
1-js/4-data-structures/2-number/6-formula-binet/solution.md
Normal 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 -- бесконечная дробь.
|
||||
|
||||
Ошибки округления при вычислениях множатся и, в итоге, дают расхождение.
|
||||
27
1-js/4-data-structures/2-number/6-formula-binet/task.md
Normal 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)` выше и по формуле Бине? Если нет, то почему и какой из них верный?**
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
Сгенерируем значение в диапазоне `0..1` и умножим на `max`:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var max = 10;
|
||||
|
||||
alert( Math.random()*max );
|
||||
```
|
||||
|
||||
5
1-js/4-data-structures/2-number/7-random-0-max/task.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# Случайное из интервала (0, max)
|
||||
|
||||
[importance 2]
|
||||
|
||||
Напишите код для генерации случайного значения в диапазоне от `0` до `max`, не включая `max`.
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
Сгенерируем значение из интервала `0..max-min`, а затем сдвинем на `min`:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var min=5, max = 10;
|
||||
|
||||
alert( min + Math.random()*(max-min) );
|
||||
```
|
||||
|
||||
5
1-js/4-data-structures/2-number/8-random-min-max/task.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# Случайное из интервала (min, max)
|
||||
|
||||
[importance 2]
|
||||
|
||||
Напишите код для генерации случайного числа от `min` до `max`, не включая `max`.
|
||||
|
|
@ -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) );
|
||||
```
|
||||
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
# Случайное целое от min до max
|
||||
|
||||
[importance 2]
|
||||
|
||||
Напишите функцию `randomInteger(min, max)` для генерации случайного **целого** числа между `min` и `max`, включая `min,max` как возможные значения.
|
||||
|
||||
Любое число из интервала `min..max` должно иметь одинаковую вероятность.
|
||||
615
1-js/4-data-structures/2-number/article.md
Normal 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)), и если подразумевать предел, то в качестве результата деления на `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>.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
10
1-js/4-data-structures/3-object/1-hello-object/solution.md
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
|
||||
|
||||
```js
|
||||
var user = {};
|
||||
user.name = "Вася";
|
||||
user.surname = "Петров";
|
||||
user.name = "Сергей";
|
||||
delete user.name;
|
||||
```
|
||||
|
||||
13
1-js/4-data-structures/3-object/1-hello-object/task.md
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# Первый объект
|
||||
|
||||
[importance 3]
|
||||
|
||||
Мини-задача на синтаксис объектов. Напишите код, по строке на каждое действие.
|
||||
<ol>
|
||||
<li>Создайте пустой объект `user`.</li>
|
||||
<li>Добавьте свойство `name` со значением `Вася`.</li>
|
||||
<li>Добавьте свойство `surname` со значением `Петров`.</li>
|
||||
<li>Поменяйте значение `name` на `Сергей`.</li>
|
||||
<li>Удалите свойство `name` из объекта.</li>
|
||||
</ol>
|
||||
|
||||
328
1-js/4-data-structures/3-object/article.md
Normal 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>
|
||||
|
||||
|
||||
|
||||
|
||||
BIN
1-js/4-data-structures/3-object/objectLiteral.png
Executable file
|
After Width: | Height: | Size: 9.3 KiB |
|
|
@ -0,0 +1,6 @@
|
|||
function isEmpty(obj) {
|
||||
for(var key in obj) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
describe("isEmpty", function() {
|
||||
it("если объект пустой - возвращает true", function() {
|
||||
assert.isTrue( isEmpty({}) );
|
||||
});
|
||||
|
||||
it("если у объекта есть любое свойство, не важно какое - возвращает false", function() {
|
||||
assert.isFalse( isEmpty({ anything: false }) );
|
||||
});
|
||||
});
|
||||
|
|
@ -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
|
||||
```
|
||||
|
||||
22
1-js/4-data-structures/4-object-for-in/1-is-empty/task.md
Normal 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
|
||||
```
|
||||
|
||||
|
|
@ -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);
|
||||
```
|
||||
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
# Сумма свойств
|
||||
|
||||
[importance 5]
|
||||
|
||||
Есть объект `salaries` с зарплатами. Напишите код, который выведет сумму всех зарплат.
|
||||
|
||||
Если объект пустой, то результат должен быть `0`.
|
||||
|
||||
Например:
|
||||
|
||||
```js
|
||||
"use strict";
|
||||
|
||||
var salaries = {
|
||||
"Вася": 100,
|
||||
"Петя": 300,
|
||||
"Даша": 250
|
||||
};
|
||||
|
||||
//... ваш код выведет 650
|
||||
```
|
||||
|
||||
P.S. Сверху стоит `use strict`, чтобы не забыть объявить переменные.
|
||||
|
|
@ -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 || "нет сотрудников");
|
||||
```
|
||||
|
||||
22
1-js/4-data-structures/4-object-for-in/3-max-salary/task.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# Свойство с наибольшим значением
|
||||
|
||||
[importance 5]
|
||||
|
||||
Есть объект `salaries` с зарплатами. Напишите код, который выведет имя сотрудника, у которого самая большая зарплата.
|
||||
|
||||
Если объект пустой, то пусть он выводит "нет сотрудников".
|
||||
|
||||
Например:
|
||||
|
||||
```js
|
||||
"use strict";
|
||||
|
||||
var salaries = {
|
||||
"Вася": 100,
|
||||
"Петя": 300,
|
||||
"Даша": 250
|
||||
};
|
||||
|
||||
// ... ваш код выведет "Петя"
|
||||
```
|
||||
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
function isNumeric(n) {
|
||||
return !isNaN(parseFloat(n)) && isFinite(n)
|
||||
}
|
||||
|
||||
// ... ваш код ...
|
||||
|
|
@ -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, "Моё меню" );
|
||||
});
|
||||
});
|
||||
|
|
@ -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);
|
||||
```
|
||||
|
||||
|
|
@ -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)
|
||||
}
|
||||
```
|
||||
|
||||
173
1-js/4-data-structures/4-object-for-in/article.md
Normal 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>
|
||||
|
||||
162
1-js/4-data-structures/5-object-reference/article.md
Normal 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>
|
||||
|
||||
Это -- одно из ключевых отличий объекта от примитива (числа, строки...), который при присвоении как раз копируется "по значению", то есть полностью.
|
||||
|
||||
BIN
1-js/4-data-structures/5-object-reference/box-message-hello.png
Executable file
|
After Width: | Height: | Size: 34 KiB |
BIN
1-js/4-data-structures/5-object-reference/message_box_hello.png
Executable file
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 123 KiB |
BIN
1-js/4-data-structures/5-object-reference/phrase_box_hello.png
Executable file
|
After Width: | Height: | Size: 34 KiB |
BIN
1-js/4-data-structures/5-object-reference/referenceUser.png
Executable file
|
After Width: | Height: | Size: 38 KiB |
BIN
1-js/4-data-structures/5-object-reference/referenceUser@2x.png
Executable file
|
After Width: | Height: | Size: 139 KiB |
BIN
1-js/4-data-structures/5-object-reference/referenceUserAdmin.png
Executable file
|
After Width: | Height: | Size: 74 KiB |
BIN
1-js/4-data-structures/5-object-reference/referenceUserAdmin@2x.png
Executable file
|
After Width: | Height: | Size: 278 KiB |
|
|
@ -0,0 +1,16 @@
|
|||
Последний элемент имеет индекс на `1` меньший, чем длина массива.
|
||||
|
||||
Например:
|
||||
|
||||
```js
|
||||
var fruits = ["Яблоко", "Груша", "Слива"];
|
||||
```
|
||||
|
||||
Длина массива этого массива `fruits.length` равна `3`. Здесь "Яблоко" имеет индекс `0`, "Груша" -- индекс `1`, "Слива" -- индекс `2`.
|
||||
|
||||
То есть, для массива длины `goods`:
|
||||
|
||||
```js
|
||||
var lastItem = goods[goods.length-1]; // получить последний элемент
|
||||
```
|
||||
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
# Получить последний элемент массива
|
||||
|
||||
[importance 5]
|
||||
|
||||
Как получить последний элемент из произвольного массива?
|
||||
|
||||
У нас есть массив `goods`. Сколько в нем элементов -- не знаем, но можем прочитать из `goods.length`.
|
||||
|
||||
Напишите код для получения последнего элемента `goods`.
|
||||