renovations

This commit is contained in:
Ilya Kantor 2015-01-10 00:54:38 +03:00
parent 223dd884ae
commit 4b8b168fd2
42 changed files with 562 additions and 490 deletions

View file

@ -2,7 +2,7 @@
Конструкция `switch` заменяет собой сразу несколько `if`.
Это -- более наглядный способ сравнить выражение сразу с несколькими вариантами.
Она представляет собой более наглядный способ сравнить выражение сразу с несколькими вариантами.
[cut]
## Синтаксис
@ -63,11 +63,13 @@ switch (a) {
}
```
Будет выведено только одно значение, соответствующее `4`. После чего `break` прервёт выполнение.
Здесь оператор `switch` последовательно сравнит `a` со всеми вариантами из `case`.
**Если его не прервать -- оно пойдёт далее, при этом остальные проверки игнорируются.**
Сначала `3`, затем -- так как нет совпадения -- `4`. Совпадение найдено, будет выполнен этот вариант, со строки `alert('В точку!')` и далее, до ближайшего `break`, который прервёт выполнение.
Например:
**Если `break` нет, то выполнение пойдёт ниже по следующим `case`, при этом остальные проверки игнорируются.**
Пример без `break`:
```js
//+ run
@ -87,7 +89,7 @@ switch (a) {
}
```
В примере выше последовательно выполнятся три `alert`.
В примере выше последовательно выполнятся три `alert`:
```js
alert('В точку!');
@ -95,7 +97,7 @@ alert('Перебор');
alert('Я таких значений не знаю');
```
**В `case` могут быть любые выражения**, в том числе включающие в себя переменные и функции.
В `case` могут быть любые выражения, в том числе включающие в себя переменные и функции.
Например:
@ -165,21 +167,19 @@ switch(arg) {
case 3:
alert('Никогда не выполнится');
case null:
alert('Отмена');
break;
default:
alert('Неизвестное значение: ' + arg)
}
```
Что оно выведет при вводе чисел 0, 1, 2, 3? Подумайте и *потом* читайте дальше...
Что оно выведет при вводе числа 0? Числа 1? 2? 3?
Подумайте, выпишите свои ответы, исходя из текущего понимания работы `switch` и *потом* читайте дальше...
<ul>
<li>При вводе `0` или `1` выполнится первый `alert`, далее выполнение продолжится вниз до первого `break` и выведет второй `alert('Два')`.</li>
<li>При вводе `2`, `switch` перейдет к `case '2'` и выведет `Два`.</li>
<li>**При вводе `3`, `switch` перейдет на `default`.** Это потому, что `prompt` возвращает строку `'3'`, а не число. Типы разные. `Switch` использует строгое равенство `===`, так что совпадения не будет.</li>
<li>При отмене сработает `case null`.</li>
<li>При вводе `0` выполнится первый `alert`, далее выполнение продолжится вниз до первого `break` и выведет второй `alert('Два')`. Итого, два вывода `alert`.</li>
<li>При вводе `1` произойдёт то же самое.</li>
<li>При вводе `2`, `switch` перейдет к `case '2'`, и сработает единственный `alert('Два')`.</li>
<li>**При вводе `3`, `switch` перейдет на `default`.** Это потому, что `prompt` возвращает строку `'3'`, а не число. Типы разные. Оператор `switch` предполагает строгое равенство `===`, так что совпадения не будет.</li>
</ul>

View file

@ -65,13 +65,14 @@ alert(message); // <-- будет ошибка, т.к. переменная ви
```js
function count() {
// переменные i,j не будут уничтожены по окончании цикла
for (*!*var*/!* i=0; i<3; i++) {
*!*var*/!* j = i * 2;
}
*!*
alert(i); // i=3, на этом значении цикл остановился
alert(j); // j=4, последнее значение, на котором цикл сработал, было i=2
alert(i); // i=3, последнее значение i, при нём цикл перестал работать
alert(j); // j=4, последнее значение j, которое вычислил цикл
*/!*
}
```
@ -114,13 +115,12 @@ showMessage(); // Привет, я Вася
```js
//+ run
var *!*userName*/!* = 'Вася';
var userName = 'Вася';
function showMessage() {
*!*
userName = 'Петя'; // (1) присвоение во внешнюю переменную
*/!*
var message = 'Привет, я ' + *!*userName*/!*;
var message = 'Привет, я ' + userName;
alert(message);
}
@ -133,13 +133,11 @@ alert(userName); // Петя, значение внешней переменно
Конечно, если бы внутри функции, в строке `(1)`, была бы объявлена своя локальная переменная `var userName`, то все обращения использовали бы её, и внешняя переменная осталась бы неизменной.
[summary]
**Переменные, объявленные на уровне всего скрипта, называют *"глобальными переменными"*.**
Делайте глобальными только те переменные, которые действительно имеют общее значение для вашего проекта.
В примере выше переменная `userName` -- глобальная.
Пусть каждая функция работает "в своей песочнице".
[/summary]
Делайте глобальными только те переменные, которые действительно имеют общее значение для вашего проекта, а нужные для решения конкретной задачи -- пусть будут локальными в соответствующей функции.
[warn header="Внимание: неявное объявление глобальных переменных!"]
@ -167,7 +165,7 @@ alert(message); // Привет
Здесь опасность даже не в автоматическом создании переменной, а в том, что глобальные переменные должны использоваться тогда, когда действительно нужны "общескриптовые" параметры.
Забыли `var` в одном месте, потом в другом -- в результате одна функция неожиданно поменяла глобальную переменную, которую использует другая. Возможна ошибка и потеря времени на поиск проблемы.
Забыли `var` в одном месте, потом в другом -- в результате одна функция неожиданно поменяла глобальную переменную, которую использует другая. И поди разберись, кто и когда её поменял, не самая приятная ошибка для отладки.
[/warn]
В будущем, когда мы лучше познакомимся с основами JavaScript, в главе [](/closures), мы более детально рассмотрим внутренние механизмы работы переменных и функций.
@ -201,7 +199,7 @@ showMessage('Маша', 'Как дела?');
//+ run
function showMessage(from, text) {
*!*
from = '**' + from + '**'; // меняем локальную переменную (1)
from = '**' + from + '**'; // меняем локальную переменную from
*/!*
alert(from + ': ' + text);
}
@ -210,32 +208,9 @@ var from = "Маша";
showMessage(from, "Привет");
alert(from); // "Маша", без изменений, так как в строке (1) была изменена копия значения
alert(from); // старое значение from без изменений, в функции была изменена копия
```
Здесь есть небольшая тонкость при работе с объектами. Как мы помним, в переменной хранится ссылка на объект. Поэтому функция, получив параметр-объект, работает с самим этим объектом:
Например, в коде ниже функция по ссылке меняет содержимое объекта `user`:
```js
//+ run
function makeAdmin(user) {
user.isAdmin = true;
}
var user = { name: "Вася" };
makeAdmin(user);
alert(user.isAdmin); // true
```
## Стиль объявления функций
В объявлении функции есть правила для расстановки пробелов. Они отмечены стрелочками:
<img src="style.png">
Конечно, вы можете ставить пробелы и по-другому, но эти правила используются в большинстве JavaScript-фреймворков.
## Аргументы по умолчанию
Функцию можно вызвать с любым количеством аргументов.

View file

@ -2,8 +2,6 @@
В JavaScript функция является значением, таким же как строка или число.
## Функция -- это значение
Как и любое значение, объявленную функцию можно вывести, вот так:
```js
@ -41,14 +39,16 @@ sayHi(); // ошибка (4)
<li>...Однако, в любой момент значение переменной можно поменять. При этом, если оно не функция, то вызов `(4)` выдаст ошибку.</li>
</ol>
Обычные значения, такие как числа или строки, представляют собой *данные*. А функцию можно воспринимать как *действие*. Это действие, как правило, хранится в переменной, но его можно скопировать или переместить из неё.
Обычные значения, такие как числа или строки, представляют собой *данные*. А функцию можно воспринимать как *действие*.
Это действие можно запустить через скобки `()`, а можно и скопировать в другую переменную, как было продемонстрировано выше.
## Объявление Function Expression [#function-expression]
Функцию можно создать и присвоить переменной в любом месте кода.
Существует альтернативный синтаксис для объявления функции, который ещё более наглядно показывает, что функция -- это всего лишь разновидность значения переменной.
Для этого используется объявление "Function Expression" (функциональное выражение), которое выглядит так:
Он называется "Function Expression" (функциональное выражение) и выглядит так:
```js
//+ run
@ -73,8 +73,8 @@ sayHi('Вася');
"Классическое" объявление функции, о котором мы говорили до этого, вида `function имя(параметры) {...}`, называется в спецификации языка "Function Declaration".
<ul>
<li>**Function Declaration** -- функция, объявленная в основном потоке кода.</li>
<li>**Function Expression** -- объявление функции в контексте какого-либо выражения, например присваивания.</li>
<li>*Function Declaration* -- функция, объявленная в основном потоке кода.</li>
<li>*Function Expression* -- объявление функции в контексте какого-либо выражения, например присваивания.</li>
</ul>
Несмотря на немного разный вид, по сути две эти записи делают одно и то же:
@ -93,13 +93,7 @@ var sum = function(a, b) {
Оба этих объявления говорят интерпретатору: "объяви переменную `sum`, создай функцию с указанными параметрами и кодом и сохрани её в `sum`".
**При этом название переменной, в которую записана функция, обычно называют "именем функции". Говорят: "функция sum". Но при этом имя к функции никак не привязано.**
Это всего лишь имя переменной, в которой *в данный момент* находится функция.
Функция может быть в процессе работы скрипта скопирована в другую переменную, а из этой удалена, передана в другое место кода, и так далее, как мы видели выше.
**Основное отличие между ними: функции, объявленные как Function Declaration, создаются интерпретатором *до выполнения кода*.**
**Основное отличие между ними: функции, объявленные как Function Declaration, создаются интерпретатором до выполнения кода.**
Поэтому их можно вызвать *до* объявления, например:
@ -127,12 +121,13 @@ var sayHi = function(name) {
}
```
Это из-за того, что JavaScript перед запуском кода ищет в нём Function Declaration (они начинаются в основном потоке с `function`) и обрабатывает их.
Это из-за того, что JavaScript перед запуском кода ищет в нём Function Declaration (их легко найти: они не являются частью выражений и начинаются со слова `function`) и обрабатывает их.
А Function Expression создаются при выполнении выражения, в котором созданы, в данном случае -- функция будет создана при операции присваивания `sayHi = ...`.
А Function Expression создаются в процессе выполнении выражения, в котором созданы, в данном случае -- функция будет создана при операции присваивания `sayHi = function...`
**Как правило, возможность Function Declaration вызвать функцию до объявления -- это удобно, так как даёт больше свободы в том, как организовать свой код.**
Как правило, возможность Function Declaration вызвать функцию до объявления -- это удобно, так как даёт больше свободы в том, как организовать свой код.
Можно расположить функции внизу, а их вызов -- сверху или наоборот.
### Условное объявление функции [#bad-conditional-declaration]
@ -142,7 +137,7 @@ var sayHi = function(name) {
```js
//+ run
var age = 20;
var age = +prompt("Сколько вам лет?", 20);
if (age >= 18) {
function sayHi() { alert('Прошу вас!'); }
@ -153,15 +148,7 @@ if (age >= 18) {
sayHi();
```
[smart header="Зачем условное объявление?"]
Конечно, можно произвести проверку условия внутри функции.
Но вынос проверки вовне даёт очевидный выигрыш в производительности в том случае, когда проверку достаточно произвести только один раз, и её результат никогда не изменится.
Например, мы можем проверить, поддерживает ли браузер определённые современные возможности, и если да -- функция будет использовать их, а если нет -- реализовать её другим способом. При этом проверка будет осуществляться один раз, на этапе объявления функции, а не при каждом запуске функции.
[/smart]
При запуске примера выше в любом браузере, кроме Firefox, мы увидим, что условное объявление не работает. Срабатывает `"До 18 нельзя"`, несмотря на то, что `age = 20`.
При вводе `20` в примере выше в любом браузере, кроме Firefox, мы увидим, что условное объявление не работает. Срабатывает `"До 18 нельзя"`, несмотря на то, что `age = 20`.
В чём дело? Чтобы ответить на этот вопрос -- вспомним, как работают функции.
@ -231,55 +218,54 @@ sayHi();
Взглянем ещё на один пример.
Функция `test(f, yes, no)` получает три функции, вызывает первую и, в зависимости от её результата, вызывает вторую или третью:
Функция `ask(question, yes, no)` предназначена для выбора действия в зависимости от результата `f`.
Она выводит вопрос на подтверждение `question` и, в зависимости от согласия пользователя, вызывает `yes` или `no`:
```js
//+ run
*!*
function test(f, yes, no) {
if (f()) yes()
function ask(question, yes, no) {
if (confirm(question)) yes()
else no();
}
*/!*
// вспомогательные функции
function f1() {
return confirm("OK?");
}
function f2() {
function showOk() {
alert("Вы согласились.");
}
function f3() {
function showCancel() {
alert("Вы отменили выполнение.");
}
// использование
test(f1, f2, f3);
ask("Вы согласны?", showOk, showCancel);
```
В этом примере для нас, наверно, нет ничего нового. Подумаешь, объявили функции `f1`, `f2`, `f3`, передали их в качестве параметров другой функции (ведь функция -- обычное значение), вызвали те, которые нужны...
Какой-то очень простой код, не правда ли? Зачем, вообще, может понадобиться такая `ask`?
А вот то же самое, но более коротко:
...Но при работе со страницей такие функции как раз очень востребованы, только вот спрашивают они не простым `confirm`, а выводят более красивое окно с вопросом и могут интеллектуально обработать ввод посетителя. Но это всё в своё время.
Здесь обратим внимание на то, что то же самое можно написать более коротко:
```js
//+ run
function test(f, yes, no) {
if (f()) yes()
function ask(question, yes, no) {
if (confirm(question)) yes()
else no();
}
*!*
test(
function() { return confirm("OK?"); },
ask(
"Вы согласны?",
function() { alert("Вы согласились."); },
function() { alert("Вы отменили выполнение."); }
);
*/!*
```
Здесь функции объявлены прямо внутри вызова `test(...)`, даже без присвоения им имени.
Здесь функции объявлены прямо внутри вызова `ask(...)`, даже без присвоения им имени.
**Функциональное выражение, которое не записывается в переменную, называют [анонимной функцией](http://ru.wikipedia.org/wiki/%D0%90%D0%BD%D0%BE%D0%BD%D0%B8%D0%BC%D0%BD%D0%B0%D1%8F_%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D1%8F).**
@ -289,9 +275,9 @@ test(
## new Function
Существует ещё один способ создания функции, который используется очень редко.
Существует ещё один способ создания функции, который используется очень редко, но упомянем и его для полноты картины.
Он выглядит так:
Он позволяет создавать функцию полностью "на лету" из строки, вот так:
```js
//+ run
@ -309,7 +295,9 @@ alert(result); // 3
<dd>Код функции в виде строки.</dd>
</dl>
Этот способ позволяет конструировать строку с кодом функции динамически, во время выполнения программы. Это, скорее, исключение, чем правило, но также бывает востребовано. Пример использования -- динамическая компиляция шаблонов на JavaScript, мы встретимся с ней позже, при работе с интерфейсами.
Таким образом можно конструировать функцию, код которой неизвестен на момент написания программы, но строка с ним генерируется или подгружается динамически во время её выполнения.
Пример использования -- динамическая компиляция шаблонов на JavaScript, мы встретимся с ней позже, при работе с интерфейсами.
## Итого

View file

@ -1,44 +1,51 @@
# Рекурсия, стек
В коде функции могут вызывать другие функции для выполнения подзадач. Частный случай подвызова -- когда функция вызывает сама себя. Это называется *рекурсией*.
В коде функции могут вызывать другие функции для выполнения подзадач.
Частный случай подвызова -- когда функция вызывает сама себя. Это называется *рекурсией*.
Рекурсия используется для ситуаций, когда выполнение одной сложной задачи можно представить как некое действие в совокупности с решением той же задачи в более простом варианте.
Сейчас мы посмотрим примеры.
Рекурсия -- общая тема программирования, не относящаяся напрямую к JavaScript. Если вы разрабатывали на других языках или изучали программирование раньше в ВУЗе, то наверняка уже знаете, что это такое.
Эта глава предназначена для читателей, которые пока с этой темой незнакомы и хотят лучше разобраться в том, как работают функции.
В этой главе мы рассмотрим, как рекурсия устроена изнутри, и как её можно использовать.
[cut]
## Реализация pow(x, n) через рекурсию
## Степень pow(x, n) через рекурсию
Чтобы возвести `x` в натуральную степень `n` -- можно умножить его на себя `n` раз в цикле:
В качестве первого примера использования рекурсивных вызовов -- рассмотрим задачу возведения числа `x` в натуральную степень `n`.
Её можно представить как совокупность более простого действия и более простой задачи того же типа вот так:
```js
function pow(x, n) {
var result = x;
for(var i=1; i<n; i++) {
result *= x;
}
return result;
}
pow(x, n) = x * pow(x, n-1)
```
А можно поступить проще.
То есть, <code>x<sup>n</sup> = x * x<sup>n-1</sup></code>.
Ведь <code>x<sup>n</sup> = x * x<sup>n-1</sup></code>, т.е. можно вынести один `x` из-под степени. Иначе говоря, значение функции `pow(x,n)` получается из `pow(x, n-1)` умножением на `x`.
Например, вычислим `pow(2, 4)`, последовательно переходя к более простой задаче:
Этот процесс можно продолжить. Например, вычислим `pow(2, 4)`:
<ol>
<li>`pow(2, 4) = 2 * pow(2, 3)`</li>
<li>`pow(2, 3) = 2 * pow(2, 2)`</li>
<li>`pow(2, 2) = 2 * pow(2, 1)`</li>
<li>`pow(2, 1) = 2`</li>
</ol>
```js
pow(2, 4) = 2 * pow(2, 3) = 2 * 2 * pow(2, 2) = 2 * 2 * 2 * pow(2, 1) = 2 * 2 * 2 * 2;
```
На шаге 1 нам нужно вычислить `pow(2,3)`, поэтому мы делаем шаг 2, дальше нам нужно `pow(2,2)`, мы делаем шаг 3, затем шаг 4, и на нём уже можно остановиться, ведь очевидно, что результат возведения числа в степень 1 -- равен самому числу.
Процесс перехода от `n` к `n-1` останавливается на `n==1`, так как очевидно, что `pow(x,1) == x`.
Далее, имея результат на шаге 4, он подставляется обратно в шаг 3, затем имеем `pow(2,2)` -- подставляем в шаг 2 и на шаге 1 уже получаем результат.
Код для такого вычисления:
Этот алгоритм на JavaScript:
```js
//+ run
function pow(x, n) {
if (n != 1) { // пока n!=1 сводить вычисление pow(..n) к pow(..n-1)
if (n != 1) { // пока n != 1, сводить вычисление pow(x,n) к pow(x,n-1)
return x * pow(x, n-1);
} else {
return x;
@ -48,13 +55,15 @@ function pow(x, n) {
alert( pow(2, 3) ); // 8
```
Говорят, что "функция `pow` *рекурсивно вызывает сама себя*" при `n != 1`.
Говорят, что "функция `pow` *рекурсивно вызывает сама себя*" до `n == 1`.
Значение, на котором рекурсия заканчивается называют *базисом рекурсии*. В примере выше базисом является `1`.
Общее количество вложенных вызовов называют *глубиной рекурсии*. В случае со степенью, всего будет `n` вызовов. Максимальная глубина рекурсии ограничена и составляет около `10000`, но это число зависит от конкретного интерпретатора JavaScript и может быть в 10 раз меньше.
Общее количество вложенных вызовов называют *глубиной рекурсии*. В случае со степенью, всего будет `n` вызовов.
**Рекурсию используют, когда вычисление функции можно свести к её более простому вызову, а его -- еще к более простому, и так далее, пока значение не станет очевидно.**
Максимальная глубина рекурсии в браузерах ограничена, точно можно рассчитывать на `10000` вложенных вызовов, но некоторые интерпретаторы допускают и больше.
Итак, рекурсию используют, когда вычисление функции можно свести к её более простому вызову, а его -- еще к более простому, и так далее, пока значение не станет очевидно.
## Контекст выполнения, стек
@ -62,60 +71,39 @@ alert( pow(2, 3) ); // 8
**У каждого вызова функции есть свой "контекст выполнения" (execution context).**
Контекст выполнения -- это служебная информация, которая соответствует текущему запуску функции. Она включает в себя локальные переменные функции.
Контекст выполнения -- это служебная информация, которая соответствует текущему запуску функции. Она включает в себя локальные переменные функции и конкретное место в коде, на котором находится интерпретатор.
Например, для вызова:
```js
//+ run
function pow(x, n) {
if (n != 1) { // пока n!=1 сводить вычисление pow(..n) к pow(..n-1)
return x * pow(x, n-1);
} else {
return x;
}
}
*!*
alert( pow(2, 3) ); // (*)
*/!*
```
При запуске функции `pow` в строке `(*)` будет создан контекст выполнения, который будет хранить переменные `x = 2, n = 3`. Мы схематично обозначим его так:
Например, для вызова `pow(2, 3)` из примера выше будет создан контекст выполнения, который будет хранить переменные `x = 2, n = 3`. Мы схематично обозначим его так:
<ul class="function-execution-context">
<li>Контекст: { x: 2, n: 3 }</li>
<li>Контекст: { x: 2, n: 3, строка 1 }</li>
</ul>
Далее функция `pow` начинает выполняться. Вычисляется выражение `n != 1` -- оно равно `true`, ведь в текущем контексте `n=3`. Поэтому задействуется первая ветвь `if` :
```js
if (n != 1) { // пока n!=1 сводить вычисление pow(..n) к pow(..n-1)
function pow(x, n) {
if (n != 1) { // пока n != 1 сводить вычисление pow(x,n) к pow(x,n-1)
*!*
return x * pow(x, n-1);
*/!*
} else {
return x;
}
}
```
Чтобы вычислить выражение `x * pow(x, n-1)`, требуется произвести запуск `pow` с новыми аргументами.
**При любом вложенном вызове JavaScript запоминает место, где он остановился в текущей функции в специальной внутренней структуре данных -- "стеке контекстов".**
**При любом вложенном вызове JavaScript запоминает текущий контекст выполнения в специальной внутренней структуре данных -- "стеке контекстов".**
Это как если бы мы куда-то ехали, но очень захотелось поесть. Можно остановиться у кафе, оставить машину, отойти, а потом, через некоторое время, вернуться к ней и продолжить дорогу.
Затем интерпретатор приступает к выполнению вложенного вызова.
Так и здесь -- мы запомним, где остановились в этой функции, пойдём выполним вложенный вызов, затем вернёмся и продолжим дорогу.
В данном случае вызывается та же `pow`, однако это абсолютно неважно. Для любых функций процесс одинаков.
**После того, как текущий контекст выполнения сохранён в стеке контекстов, JavaScript приступает к выполнению вложенного вызова.**
Для нового вызова создаётся свой контекст выполнения, и управление переходит в него, а когда он завершён -- старый контекст достаётся из стека и выполнение внешней функции возобновляется.
В данном случае вызывается та же `pow`, однако, это абсолютно неважно. Для любых функций процесс одинаков.
**Создаётся новый контекст выполнения, и управление переходит в подвызов, а когда он завершён -- старый контекст достаётся из стека и выполнение внешней функции возобновляется.**
## Разбор примера
Разберём происходящее более подробно, начиная с вызова `(*)`:
Разберём происходящее с контекстами более подробно, начиная с вызова `(*)`:
```js
//+ run
@ -137,26 +125,29 @@ alert( pow(2, 3) ); // (*)
<dd>Запускается функция `pow`, с аргументами `x=2`, `n=3`. Эти переменные хранятся в контексте выполнения, схематично изображённом ниже:
<ul class="function-execution-context">
<li>Контекст: { x: 2, n: 3 }</li>
<li>Контекст: { x: 2, n: 3, строка 1 }</li>
</ul>
Выполнение в этом контексте продолжается, пока не встретит вложенный вызов в строке 3.
</dd>
<dt>`pow(2, 2)`</dt>
<dd>В строке `3` происходит вложенный вызов `pow` с аргументами `x=2`, `n=2`. Для этой функции создаётся новый текущий контекст (выделен красным), а предыдущий сохраняется в "стеке":
<dd>В строке `3` происходит вложенный вызов `pow` с аргументами `x=2`, `n=2`. Текущий контекст сохраняется в стеке, а для вложеннного вызова создаётся новый контекст (выделен жирным ниже):
<ul class="function-execution-context">
<li>Контекст: { x: 2, n: 3 }</li>
<li>Контекст: { x: 2, n: 2 }</li>
<li>Контекст: { x: 2, n: 3, строка 3 }</li>
<li>Контекст: { x: 2, n: 2, строка 1 }</li>
</ul>
Обратим внимание, что контекст включает в себя не только переменные, но и место в коде, так что когда вложенный вызов завершится -- можно будет легко вернуться назад.
Слово "строка" здесь условно, на самом деле, конечно, запомнено более точное место в цепочке команд.
</dd>
<dt>`pow(2, 1)`</dt>
<dd>Опять вложенный вызов в строке `3`, на этот раз -- с аргументами `x=2`, `n=1`. Создаётся новый текущий контекст, предыдущий добавляется в стек:
<ul class="function-execution-context">
<li>Контекст: { x: 2, n: 3 }</li>
<li>Контекст: { x: 2, n: 2 }</li>
<li>Контекст: { x: 2, n: 1 }</li>
<li>Контекст: { x: 2, n: 3, строка 3 }</li>
<li>Контекст: { x: 2, n: 2, строка 3 }</li>
<li>Контекст: { x: 2, n: 1, строка 1 }</li>
</ul>
На текущий момент в стеке уже два старых контекста.
</dd>
<dt>Выход из `pow(2, 1)`.</dt>
<dd>При выполнении `pow(2, 1)`, в отличие от предыдущих запусков, выражение `n != 1` будет равно `false`, поэтому сработает вторая ветка `if..else`:
@ -176,19 +167,18 @@ function pow(x, n) {
Здесь вложенных вызовов нет, так что функция заканчивает свою работу, возвращая `2`. Текущий контекст больше не нужен и удаляется из памяти, из стека восстанавливается предыдущий:
<ul class="function-execution-context">
<li>Контекст: { x: 2, n: 3 }</li>
<li>Контекст: { x: 2, n: 2 }</li>
<li>Контекст: { x: 2, n: 3, строка 3 }</li>
<li>Контекст: { x: 2, n: 2, строка 3 }</li>
</ul>
Возобновляется обработка внешнего вызова `pow(2, 2)`.
</dd>
<dt>Выход из `pow(2, 2)`.</dt>
<dd>...И теперь уже `pow(2, 2)` может закончить свою работу, вернув `4`. Восстанавливается контекст предыдущего вызова:
<ul class="function-execution-context">
<li>Контекст: { x: 2, n: 3 }</li>
<li>Контекст: { x: 2, n: 3, строка 3 }</li>
</ul>
Возобновляется обработка внешнего вызова `pow(2, 3)`.
</dd>
<dt>Выход из `pow(2, 3)`.</dt>
<dd>Самый внешний вызов заканчивает свою работу, его результат: `pow(2, 3) = 8`.</dd>
</dl>
@ -197,15 +187,9 @@ function pow(x, n) {
Как видно из иллюстраций выше, глубина рекурсии равна максимальному числу контекстов, одновременно хранимых в стеке.
[smart]
В самом конце, как и в самом начале, выполнение попадает во внешний код, который находится вне любых функций.
Контекст, который соответствует самому внешнему коду, называют *"глобальный контекст"*. Естественно, он является начальной и конечной точкой любых вложенных подвызовов.
[/smart]
Обратим внимание на требования к памяти. Рекурсия приводит к хранению всех данных для неоконченных внешних вызовов в стеке, в данном случае это приводит к тому, что возведение в степень `n` хранит в памяти `n` различных контекстов.
Реализация степени через цикл гораздо более экономна:
Реализация возведения в степень через цикл гораздо более экономна:
```js
function pow(x, n) {
@ -221,31 +205,17 @@ function pow(x, n) {
**Любая рекурсия может быть переделана в цикл. Как правило, вариант с циклом будет эффективнее.**
...Но зачем тогда нужна рекурсия? Да просто затем, что рекурсивный код может быть гораздо проще и понятнее!
Переделка в цикл может быть нетривиальной, особенно когда в функции, в зависимости от условий, используются разные рекурсивные подвызовы.
В программировании мы в первую очередь стремимся сделать сложное простым, а повышенная производительность нужна... Лишь там, где она действительно нужна. Поэтому красивое рекурсивное решение во многих случаях лучше.
Недостатки и преимущества рекурсии:
[compare]
-Требования к памяти.
-Ограничена максимальная глубина стека.
+Краткость и простота кода.
[/compare]
Но переделка рекурсии в цикл может быть нетривиальной, особенно когда в функции, в зависимости от условий, используются различные рекурсивные подвызовы, когда ветвление более сложное.
## Итого
Рекурсия -- это когда функция вызывает сама себя, с другими аргументами.
Рекурсия -- это когда функция вызывает сама себя, как правило, с другими аргументами.
Существуют много областей применения рекурсивных вызовов. Здесь мы посмотрели на один из них -- решение задачи путём сведения её к более простой (с меньшими аргументами), но также рекурсия используется для работы с "естественно рекурсивными" структурами данных, такими как HTML-документы, для "глубокого" копирования сложных объектов.
Есть и другие применения, с которыми мы встретимся по мере изучения JavaScript.
Здесь мы постарались рассмотреть происходящее достаточно подробно, однако, если пожелаете, допустимо временно забежать вперёд и открыть главу [](/debugging-chrome), с тем чтобы при помощи отладчика построчно пробежаться по коду и посмотреть стек.
Здесь мы постарались рассмотреть происходящее достаточно подробно, однако, если пожелаете, допустимо временно забежать вперёд и открыть главу [](/debugging-chrome), с тем чтобы при помощи отладчика построчно пробежаться по коду и посмотреть стек на каждом шаге. Отладчик даёт к нему доступ.
@ -261,13 +231,13 @@ function pow(x, n) {
float: left;
clear: both;
border: 1px solid black;
font-family: "PT Mono", monospace;
font-family: "Consolas", monospace;
padding: 3px 5px;
}
.function-execution-context li:last-child {
color: red;
font-weight: bold;
}
</style>
[/head]

View file

@ -1,26 +1,28 @@
# Именованные функциональные выражения
Обычно то, что называют "именем функции", на самом деле -- всего лишь имя переменной, в которую присвоена функция. К самой функции это "имя" никак не привязано.
Специально для работы с рекурсией в JavaScript существует особое расширение функциональных выражений, которое называется "Named Function Expression" (сокращённо NFE) или, по-русски, *"именованное функциональное выражение"*.
Однако, есть в JavaScript способ указать имя, действительно привязанное к функции. Оно называется "Named Function Expression" (сокращённо NFE) или, по-русски, *"именованное функциональное выражение"*.
[cut]
## Named Function Expression [#functions-nfe]
Простейший пример NFE выглядит так:
Обычное функциональное выражение:
```js
var f = function (...) { /* тело функции */ };
```
Именованное с именем `sayHi`:
```js
var f = function *!*sayHi*/!*(...) { /* тело функции */ };
```
Проще говоря, NFE -- это `Function Expression` с дополнительным именем (в примере выше `sayHi`).
Что же это за имя, которое идёт в дополнение к `f`, и зачем оно?
Что же это за имя, которое идёт в дополнение к переменной `f`, и зачем оно?
Имя функционального выражения (`sayHi`) имеет особый смысл. Оно доступно только изнутри самой функции.
**Имя функционального выражения (`sayHi`) имеет особый смысл. Оно доступно только изнутри самой функции.**
**Это ограничение видимости входит в стандарт JavaScript и поддерживается всеми браузерами, кроме IE8-.**
Это ограничение видимости входит в стандарт JavaScript и поддерживается всеми браузерами, кроме IE8-.
Например:
@ -33,13 +35,13 @@ var f = function sayHi(name) {
alert(sayHi); // снаружи - не видно (ошибка: undefined variable 'sayHi')
```
**Кроме того, имя NFE нельзя перезаписать:**
Кроме того, имя NFE нельзя перезаписать:
```js
//+ run
var test = function sayHi(name) {
*!*
sayHi = "тест"; // перезапись
sayHi = "тест"; // попытка перезаписи
*/!*
alert(sayHi); // function... (перезапись не удалась)
};
@ -49,15 +51,7 @@ test();
В режиме `use strict` код выше выдал бы ошибку.
**Как правило, имя NFE используется для единственной цели -- позволить изнутри функции вызвать саму себя.**
[smart header="Устаревшее специальное значение `arguments.callee`"]
Если вы работали с JavaScript, то, возможно, знаете, что для этой цели также служило специальное значение `arguments.callee`.
Если это название вам ни о чём не говорит -- всё в порядке, читайте дальше, мы обязательно обсудим его [в отдельной главе](#arguments-callee).
Если же вы в курсе, то стоит иметь в виду, что оно официально исключено из современного стандарта. А NFE -- это наше настоящее.
[/smart]
Как правило, имя NFE используется для единственной цели -- позволить изнутри функции вызвать саму себя.
## Пример использования
@ -130,7 +124,23 @@ alert(f === factorial);
Все остальные браузеры полностью поддерживают именованные функциональные выражения.
[/warn]
[smart header="Устаревшее специальное значение `arguments.callee`"]
Если вы давно работаете с JavaScript, то, возможно, знаете, что раньше для этой цели также служило специальное значение `arguments.callee`.
Если это название вам ни о чём не говорит -- всё в порядке, читайте дальше, мы обязательно обсудим его [в отдельной главе](#arguments-callee).
Если же вы в курсе, то стоит иметь в виду, что оно официально исключено из современного стандарта. А NFE -- это наше настоящее.
[/smart]
## Итого
Если функция задана как Function Expression, её можно дать имя. Оно будет доступно только внутри функции (кроме IE8-) и предназначено для надёжного рекурсивного вызова функции, даже если она записана в другую переменную.
Если функция задана как Function Expression, ей можно дать имя.
Оно будет доступно только внутри функции (кроме IE8-).
Это имя предназначено для надёжного рекурсивного вызова функции, даже если она записана в другую переменную.
Обратим внимание, что с Function Declaration так поступить нельзя. Такое "специальное" внутреннее имя функции задаётся только в синтаксисе Function Expression.

View file

@ -23,6 +23,8 @@ alert('Привет');
alert('Мир');
```
## Точка с запятой [#semicolon]
Точку с запятой *во многих случаях* можно не ставить, если есть переход на новую строку.
Так тоже будет работать:

View file

@ -25,7 +25,7 @@ alert('Привет')
alert('Мир')
```
..Однако, иногда JavaScript не вставляет точку с запятой. Например:
...Однако, иногда JavaScript не вставляет точку с запятой. Например:
```js
//+ run
@ -35,7 +35,16 @@ var a = 2
alert(a); // 5
```
Бывают случаи, когда это ведёт к ошибкам, которые достаточно трудно найти и исправить. Правила, когда точка с запятой ставится, а когда нет -- конечно, есть в спецификации языка, но запомнить их поначалу сложно, так как они неочевидны, придуманы "не для людей".
Бывают случаи, когда это ведёт к ошибкам, которые достаточно трудно найти и исправить, например:
```js
//+ run
alert("После этого сообщения будет ошибка")
[1, 2].forEach(alert)
```
Детали того, как работает код выше (массивы `[...]` и `forEach`) мы скоро изучим, здесь важно то, что при установке точки с запятой после `alert` он будет работать корректно.
**Поэтому в JavaScript рекомендуется точки с запятой ставить. Сейчас это, фактически, общепринятый стандарт.**
@ -97,34 +106,6 @@ alert( x ); // undefined
Подробнее: [](/variables), [](/types-intro).
## Методы и свойства
Все значения в JavaScript, за исключением `null` и `undefined`, содержат набор вспомогательных функций и значений, доступных "через точку".
Такие функции называют "методами", а значения -- "свойствами".
Например:
```js
//+ run
alert( "Привет, мир!".length ); // 12
```
Еще у строк есть *метод* `toUpperCase()`, который возвращает строку в верхнем регистре:
```js
//+ run
var hello = "Привет, мир!";
*!*
alert( hello.toUpperCase() ); // "ПРИВЕТ, МИР!"
*/!*
```
Подробнее: [](/properties-and-methods).
## Строгий режим
Для того, чтобы интерпретатор работал в режиме максимального соответствия современному стандарту, нужно начинать скрипт директивой `'use strict';`
@ -300,7 +281,7 @@ for(;;) {
</li>
</ul>
Подробнее: [](/break-continue).
Подробнее: [](/while-for).
## Конструкция switch
@ -347,7 +328,7 @@ alert( sum(1, 2) ); // 3
<ul>
<li>`sum` -- имя функции, ограничения на имя функции -- те же, что и на имя переменной.</li>
<li>Переменные, объявленные через `var` внутри функции, видны везде внутри этой функции, блоки `if`, `for` и т.п. на видимость не влияют.</li>
<li>Параметры передаются "по значению", т.е. копируются в локальные переменные `a`, `b`, за исключением объектов, которые передаются "по ссылке", их мы подробно обсудим в главе [](/object).
<li>Параметры передаются копируются в локальные переменные `a`, `b`.
</li>
<li>Функция без `return` считается возвращающей `undefined`. Вызов `return` без значения также возвращает `undefined`:
@ -391,7 +372,7 @@ alert( sum(1, 2) ); // 3
Если объявление функции является частью какого-либо выражения, например `var = function...` или любого другого, то это Function Expression.
В этом случае имя, которое можно (но не обязательно) указать после `function`, будет видно только внутри этой функции и позволяет обратиться к функции изнутри себя. Обычно оно используется для рекурсивных вызовов.
В этом случае функции можно присвоить "внутреннее" имя, указав его после `function`. Оно будет видно только внутри этой функции и позволяет обратиться к функции изнутри себя. Обычно это используется для рекурсивных вызовов.
Например, создадим функцию для вычисления факториала как Function Expression и дадим ей имя `me`:

View file

@ -12,11 +12,11 @@
В вашей версии Chrome панель может выглядеть несколько по-иному, но что где находится, должно быть понятно.
Зайдите на страницу [debugging/pow/index.html](/debugging/pow/index.html) браузером Chrome.
Зайдите на [страницу с примером](debugging/index.html) браузером Chrome.
Откройте инструменты разработчика: [key F12] или в меню `Инструменты > Инструменты Разработчика`.
Выберите сверху `Sources` (вместо иконок у вас могут быть просто надписи "Elements", "Resources", "Network", "Sources"...)
Выберите сверху `Sources`.
<img src="chrome_sources.png">
@ -34,7 +34,7 @@
<img src="chrome_sources_buttons.png">
Три полезные кнопки управления:
Три наиболее часто используемые кнопки управления:
<dl>
<dt>Формат <span class="devtools" style="background-position:-264px 94px"></span></dt>
<dd>Нажатие форматирует текст текущего файла, расставляет отступы. Нужна, если вы хотите разобраться в чужом коде, плохо отформатированном или сжатом.</dd>
@ -46,10 +46,12 @@
## Точки остановки
Открыли `pow.js` в зоне текста? Кликните на 6й строке файла `pow.js`, прямо на цифре 6.
Открыли файл `pow.js` во вкладке Sources? Кликните на 6й строке файла `pow.js`, прямо на цифре 6.
Поздравляю! Вы поставили "точку остановки" или, как чаще говорят, "брейкпойнт".
Выглядет это должно примерно так:
<img src="chrome_sources_breakpoint.png">
Слово *Брейкпойнт* (breakpoint) -- часто используемый английский жаргонизм. Это то место в коде, где отладчик будет *автоматически* останавливать выполнение JavaScript, как только оно до него дойдёт.
@ -62,9 +64,9 @@
Вкладка Breakpoints очень удобна, когда код большой, она позволяет:
<ul>
<li>Быстро перейти на место кода, где стоит брейкпойнт -- кликом на текст.</li>
<li>Временно выключить брейкпойнт -- кликом на чекбокс.</li>
<li>Быстро удалить брейкпойнт -- правым кликом на текст и выбором Remove...</li>
<li>Быстро перейти на место кода, где стоит брейкпойнт кликом на текст.</li>
<li>Временно выключить брейкпойнт кликом на чекбокс.</li>
<li>Быстро удалить брейкпойнт правым кликом на текст и выбором Remove, и так далее.</li>
</ul>
[smart header="Дополнительные возможности"]
@ -89,7 +91,7 @@ function pow(x, n) {
## Остановиться и осмотреться
Наша функция выполняется сразу при загрузке страницы, так что самый простой способ активировать JavaScript -- перезагрузить её. Итак, нажимаем [key F5] (Windows, Linux) или [key Cmd+R] (Mac).
Наша функция выполняется сразу при загрузке страницы, так что самый простой способ активировать отладчик JavaScript -- перезагрузить её. Итак, нажимаем [key F5] (Windows, Linux) или [key Cmd+R] (Mac).
Если вы сделали всё, как описано выше, то выполнение прервётся как раз на 6й строке.
@ -117,17 +119,19 @@ function pow(x, n) {
## Управление выполнением
Пришло время "погонять" скрипт и "оттрейсить" (от англ. trace, отслеживать) его работу.
Пришло время, как говорят, "погонять" скрипт и "оттрейсить" (от англ. trace -- отслеживать) его работу.
Обратим внимание на панель управления справа-сверху, в ней есть 6 кнопок:
<dl>
<dt><img style="vertical-align:middle" src="manage1.png"> -- продолжить выполнение, горячая клавиша [key F8].</dt>
<dd> Если скрипт не встретит новых точек остановки, то на этом работа в отладчике закончится.
<dd>Продолжает выполнения скрипта с текущего момента в обычном режиме. Если скрипт не встретит новых точек остановки, то в отладчик управление больше не вернётся.
Нажмите на эту кнопку.
Вы увидите, что отладчик остался на той же строке, но в `Call Stack` появился новый вызов. Это произошло потому, что в 6й строке находится рекурсивный вызов функции `pow`, т.е. управление перешло в неё опять, но с другими аргументами.
Скрипт продолжится, далее, в 6й строке находится рекурсивный вызов функции `pow`, т.е. управление перейдёт в неё опять (с другими аргументами) и сработает точка остановки, вновь включая отладчик.
При этом вы увидите, что выполнение стоит на той же строке, но в `Call Stack` появился новый вызов.
Походите по стеку вверх-вниз -- вы увидите, что действительно аргументы разные.
</dd>
@ -162,17 +166,15 @@ function pow(x, n) {
**Процесс отладки заключается в том, что мы останавливаем скрипт, смотрим, что с переменными, переходим дальше и ищем, где поведение отклоняется от правильного.**
[smart header="Дополнительные возможности"]
Правый клик на номер строки открывает контекстное меню, в котором можно запустить выполнение кода до неё (Continue to here).
Это очень удобно, если промежуточные строки нас не интересуют.
[smart header="Continue to here"]
Правый клик на номер строки открывает контекстное меню, в котором можно запустить выполнение кода до неё (Continue to here). Это удобно, когда хочется сразу прыгнуть вперёд и breakpoint неохота ставить.
[/smart]
## Консоль
При отладке, кроме просмотра переменных, бывает полезно запускать команды JavaScript. Для этого нужна консоль.
При отладке, кроме просмотра переменных и передвижения по скрипту, бывает полезно запускать команды JavaScript. Для этого нужна консоль.
В неё можно перейти, нажав кнопку "Console" вверху-справа, а можно и открыть в дополнение к отладчику, нажав на кнопку <span class="devtools" style="background-position:-72px -28px"></span> или клавишей [key ESC].
@ -190,13 +192,13 @@ for(var i=0; i<5; i++) {
Полную информацию по специальным командам консоли вы можете получить на странице [](https://developers.google.com/chrome-developer-tools/docs/commandline-api?hl=ru). Эти команды также действуют в Firebug (отладчик для браузера Firefox).
Консоль поддерживают все браузеры, и, хотя IE10- поддерживает далеко не все функции, `console.log` работает везде, пользуйтесь им вместо `alert`.
Консоль поддерживают все браузеры, и, хотя IE10- поддерживает далеко не все функции, но `console.log` работает везде. Используйте его для вывода отладочной информации по ходу работы скрипта.
## Ошибки
Ошибки JavaScript выводятся в консоли.
Например, прервите отладку -- для этого достаточно закрыть инструменты разрабтчика -- и откройте страницу [debugging/pow-error/index.html](/debugging/pow-error/index.html).
Например, прервите отладку -- для этого достаточно закрыть инструменты разрабтчика -- и откройте [страницу с ошибкой](error/index.html).
Перейдите во вкладку Console инструментов разработчика ([key Ctrl+Shift+J] / [key Cmd+Shift+J]).

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

View file

@ -0,0 +1,19 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<script src="pow.js"></script>
Пример для отладчика.
<script>
var fiveInCube = pow(5, 3);
alert( fiveInCube );
</script>
</body>
</html>

View file

@ -0,0 +1,8 @@
function pow(x, n) {
if (n == 1) {
return x;
}
var result = x * pow(x, n-1);
return result;
}

View file

@ -0,0 +1,19 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<script src="pow.js"></script>
Пример для отладчика.
<script>
var fiveInCube = pow(5, 3);
alert( fiveInCube );
</script>
</body>
</html>

View file

@ -0,0 +1,8 @@
function pow(x, n) {
if (n == 1) {
return y;
}
var result = x * pow(x, n-1);
return result;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 717 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 504 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 431 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 418 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 554 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 463 B

View file

@ -29,9 +29,11 @@ else // <- можно на одной строке } else {
```js
function pow(x, n) {
var result = 1;
for(var i = 0; i < n; i++) {
result *=x;
}
return result;
}

View file

@ -5,27 +5,70 @@
[cut]
## Синтаксис
Шпаргалка с правилами синтаксиса:
Шпаргалка с правилами синтаксиса (детально они их варианты разобраны далее):
<img src="cheatsheet.png">
Разберём основные моменты.
<img src="code-style.svg">
<!--
```js
function pow(x, n) {
var result = 1;
for (var i = 0; i < n; i++) {
result *=x;
}
return result;
}
var x = prompt("x?", "");
var n = prompt("n?", "");
if (n < 0) {
alert('Степень ' + n +
'не поддерживается, введите целую степень, большую 0');
} else {
alert( pow(x, n) );
}
```
-->
Не всё здесь однозначно, так что разберём эти правила подробнее.
### Фигурные скобки
Пишутся на той же строке, так называемый "египетский" стиль. Перед скобкой -- пробел.
<img src="figure.png">
<!--
```js
if (n < 0) {alert('Степень ' + n + ' не поддерживается');}
Если у вас уже есть опыт в разработке и вы привыкли делать скобку на отдельной строке -- это тоже вариант. В конце концов, решать вам. Но в основных JavaScript-фреймворках (jQuery, Dojo, Google Closure Library, Mootools, Ext.JS, YUI...) стиль именно такой.
Если условие и код достаточно короткие, например `if (cond) return null;`, то запись в одну строку вполне читаема... Но, как правило, отдельная строка всё равно воспринимается лучше.
if (n < 0) alert('Степень ' + n + ' не поддерживается');
if (n < 0) {
alert('Степень ' + n + ' не поддерживается');
}
```
-->
<img src="figure-bracket-style.svg">
Если у вас уже есть опыт в разработке и вы привыкли делать скобку на отдельной строке -- это тоже вариант. В конце концов, решать вам. Но в большинстве JavaScript-фреймворков стиль именно такой.
Если условие и код достаточно короткие, например `if (cond) return null`, то запись в одну строку вполне читаема... Но, как правило, отдельная строка всё равно воспринимается лучше.
### Длина строки
Максимальную длину строки согласовывают в команде. Как правило, это либо `80`, либо `120` символов, в зависимости от того, какие мониторы у разработчиков.
Более длинные строки необходимо разбивать. Если этого не сделать, то перевод очень длинной строки сделает редактор, и это может быть менее красиво и читаемо.
Более длинные строки необходимо разбивать для улучшения читаемости.
### Отступы
@ -36,42 +79,29 @@
Как правило, используются именно пробелы, т.к. они позволяют сделать более гибкие "конфигурации отступов", чем символ "Tab".
Например:
Например, выровнять аргументы относительно открывающей скобки:
```js
function fib(n) {
*!*
var a = 1;
var b = 1;
*/!*
for (var i = 3; i <= n; i++) {
var c = a + b;
a = b;
b = c;
}
return b;
}
show("Строки" +
" выровнены" +
" строго" +
" одна под другой");
```
Кстати, обратите внимание, переменные в выделенном фрагменте объявлены по вертикали, а не в строку `var a=1, b=1`. Так более наглядно, человеческий глаз лучше воспринимает ("сканирует") вертикально выравненную информацию, нежели по горизонтали. Это известный факт среди дизайнеров и нам, программистам, он тоже будет полезен для лучшей организации кода.
</li>
<li>**Вертикальный отступ, для лучшей разбивки кода -- перевод строки.**
Используется, чтобы разделить логические блоки внутри одной функции. В примере ниже разделены функция `pow`, получение данных `x,n` и их обработка `if`.
Используется, чтобы разделить логические блоки внутри одной функции. В примере разделены инициализация переменных, главный цикл и возвращение результата:
```js
function pow(x, n) {
return (n != 1) ? pow(x, n-1) : x;
var result = 1;
// <--
for (var i = 0; i < n; i++) {
result *=x;
}
// <--
x = prompt(...);
n = prompt(...);
// <--
if (n >= 1) {
var result = pow(x, n);
alert(result);
return result;
}
```
Вставляйте дополнительный перевод строки туда, где это сделает код более читаемым. Не должно быть более 9 строк кода подряд без вертикального отступа.
@ -82,9 +112,7 @@ if (n >= 1) {
Точки с запятой нужно ставить, даже если их, казалось бы, можно пропустить.
Есть языки, в которых точка с запятой не обязательна, и её там никто не ставит. В JavaScript она тоже не обязательна, но ставить нужно. В чём же разница?
Она в том, что **в JavaScript без точки с запятой возможны трудноуловимые ошибки.** С некоторыми примерами вы встретитесь дальше в учебнике. Такая вот особенность синтаксиса. И поэтому рекомендуется её всегда ставить.
Есть языки, в которых точка с запятой не обязательна, и её там никто не ставит. В JavaScript перевод строки её заменяет, но лишь частично, поэтому лучше её ставить, как обсуждалось [ранее](#semicolon).
## Именование
@ -102,7 +130,7 @@ if (n >= 1) {
Уровней вложенности должно быть немного.
Например, [проверки в циклах лучше делать через "continue"](#continue), чтобы не было дополнительного уровня `if(..) { ... }`:
Например, [проверки в циклах можно делать через "continue"](#continue), чтобы не было дополнительного уровня `if(..) { ... }`:
Вместо:
@ -159,21 +187,15 @@ function isEven(n) { // проверка чётности
В случае с функцией `isEven` можно было бы поступить и проще:
```js
function isEven(n) { // проверка чётности
return n % 2 == 0;
}
```
..Казалось бы, можно пойти дальше, есть ещё более короткий вариант:
```js
function isEven(n) { // проверка чётности
return !(n % 2);
}
```
...Однако, код `!(n % 2)` менее очевиден чем `n % 2 == 0`. Поэтому, на самом деле, последний вариант хуже. **Главное для нас -- не краткость кода, а его простота и читаемость.**
...Однако, если код `!(n % 2)` для вас менее очевиден чем предыдущий вариант, то стоит использовать предыдущий.
Главное для нас -- не краткость кода, а его простота и читаемость. Совсем не всегда более короткий код проще для понимания, чем более развёрнутый.
## Функции = Комментарии
@ -185,7 +207,7 @@ function isEven(n) { // проверка чётности
Сравните, например, две функции `showPrimes(n)` для вывода простых чисел до `n`.
Первый вариант:
Первый вариант использует метку:
```js
function showPrimes(n) {
@ -201,7 +223,7 @@ function showPrimes(n) {
}
```
Второй вариант, вынесена подфункция `isPrime(n)` для проверки на простоту:
Второй вариант -- дополнительную функцию `isPrime(n)` для проверки на простоту:
```js
function showPrimes(n) {
@ -277,28 +299,41 @@ function walkAround() {
</li>
</ol>
...На самом деле существует еще третий "стиль", при котором функции хаотично разбросаны по коду ;), но это ведь не наш метод, да?
...На самом деле существует еще третий "стиль", при котором функции хаотично разбросаны по коду, но это ведь не наш метод, да?
**Как правило, лучше располагать функции под кодом, который их использует.** То есть, это 2й способ.
**Как правило, лучше располагать функции под кодом, который их использует.**
Дело в том, что при чтении такого кода мы хотим знать в первую очередь, *что он делает*, а уже затем *какие функции ему помогают.* Если первым идёт код, то это как раз дает необходимую информацию. Что же касается функций, то вполне возможно нам и не понадобится их читать, особенно если они названы адекватно и то, что они делают, понятно.
То есть, предпочтителен 2й способ.
У первого способа, впрочем, есть то преимущество, что на момент чтения мы уже знаем, какие функции существуют.
Дело в том, что при чтении такого кода мы хотим знать в первую очередь, *что он делает*, а уже затем *какие функции ему помогают.* Если первым идёт код, то это как раз дает необходимую информацию. Что же касается функций, то вполне возможно нам и не понадобится их читать, особенно если они названы адекватно и то, что они делают, понятно из названия.
Таким образом, если над названиями функций никто не думает -- может быть, это будет лучшим выбором :). Попробуйте оба варианта, но по моей практике предпочтителен всё же второй.
## Комментарии
## Плохие комментарии
В коде нужны комментарии.
**Как правило, комментарии отвечают на вопрос "что происходит в коде?"**
Сразу начну с того, каких комментариев быть почти не должно.
**Должен быть минимум комментариев, которые отвечают на вопрос "что происходит в коде?"**
Что интересно, в коде начинающих разработчиков обычно комментариев либо нет, либо они как раз такого типа: "что делается в этих строках".
Серьёзно, хороший код и так понятен.
Об этом замечательно выразился Р.Мартин в книге ["Чистый код"](http://www.ozon.ru/context/detail/id/21916535/): "Если вам кажется, что нужно добавить комментарий для улучшения понимания, это значит, что ваш код недостаточно прост, и, может, стоит переписать его".
Если у вас образовалась длинная "простыня", то, возможно, стоит разбить её на отдельные функции, и тогда из их названий будет понятно, что делает тот или иной фрагмент.
Да, конечно, бывают сложные алгоритмы, хитрые решения для оптимизации, поэтому нельзя такие комментарии просто запретить. Но перед тем, как писать подобное -- подумайте: "Нельзя ли сделать код понятным и без них?"
## Хорошие комментарии
А какие комментарии полезны и приветствуются?
Например:
<ul>
<li>**Архитектурный комментарий -- "как оно, вообще, устроено".**
Какие компоненты есть, какие технологии использованы, поток взаимодействия. О чём и зачем этот скрипт. Эти комментарии особенно нужны, если вы не один.
Какие компоненты есть, какие технологии использованы, поток взаимодействия. О чём и зачем этот скрипт. Взгляд с высоты птичьего полёта. Эти комментарии особенно нужны, если вы не один, а проект большой.
Для описания архитектуры, кстати, создан специальный язык [UML](http://ru.wikipedia.org/wiki/Unified_Modeling_Language), красивые диаграммы, но можно и без этого. Главное -- чтобы понятно.
</li>
@ -321,16 +356,9 @@ function pow(x, n) {
Такие комментарии позволяют сразу понять, что принимает и что делает функция, не вникая в код.
Кстати, они автоматически обрабатываются многими редакторами, например [Aptana](http://aptana.com) и редакторами от [JetBrains](http://www.jetbrains.com/), которые учитывают их при автодополнении.
</li>
<li>**Краткий комментарий, что именно происходит в данном блоке кода.**
Что интересно, в коде начинающих разработчиков обычно комментариев либо нет, либо они как раз такого типа: "что делается в этих строках кода".
На самом деле именно эти комментарии, как правило, являются самыми ненужными. Хороший код и так самоочевиден, если не используются особо сложные алгоритмы.
Об этом замечательно выразился Р. Мартин в книге ["Чистый код"](http://www.ozon.ru/context/detail/id/21916535/): "Если вам кажется, что нужно добавить комментарий для улучшения понимания, это значит, что ваш код не достаточно прост, и, может, стоит переписать его".
Кстати, они автоматически обрабатываются многими редакторами, например [Aptana](http://aptana.com) и редакторами от [JetBrains](http://www.jetbrains.com/), которые учитывают их при автодополнении, а также выводят их в автоподсказках при наборе кода.
Кроме того, есть инструменты, например [JSDoc 3](https://github.com/jsdoc3/jsdoc), которые умеют генерировать по таким комментариям документацию в формате HTML. Более подробную информацию об этом можно также найти на сайте [](http://usejsdoc.org/).
</li>
</ul>
@ -342,9 +370,9 @@ function pow(x, n) {
Например:
<ul>
<li>**Есть несколько способов решения задачи. Почему выбран именно этот?**
<dl>
<dt>Есть несколько способов решения задачи. Почему выбран именно этот?</dt>
<dd>
Например, пробовали решить задачу по-другому, но не получилось -- напишите об этом. Почему вы выбрали именно этот способ решения? Особенно это важно в тех случаях, когда используется не первый приходящий в голову способ, а какой-то другой.
Без этого возможна, например, такая ситуация:
@ -354,18 +382,18 @@ function pow(x, n) {
<li>...Порыв, конечно, хороший, да только этот вариант вы уже обдумали раньше. И отказались, а почему -- забыли. В процессе переписывания вспомнили, конечно (к счастью), но результат - потеря времени на повторное обдумывание.</li>
</ul>
Комментарии, которые объясняют поведение кода, очень важны. Они помогают понять происходящее и принять правильное решение о развитии кода.
</li>
<li>**Какие неочевидные возможности обеспечивает этот код?** Где в другом месте кода они используются?
Комментарии, которые объясняют выбор решения, очень важны. Они помогают понять происходящее и предпринять правильные шаги при развитии кода.
</dd>
<dt>Какие неочевидные возможности обеспечивает этот код? Где ещё они используются?</dt>
<dd>
В хорошем коде должно быть минимум неочевидного. Но там, где это есть -- пожалуйста, комментируйте.
</li>
</dd>
</dl>
</ul>
[smart header="Комментарии -- это важно"]
Один из показателей хорошего разработчика -- качество комментариев, которые позволяют эффективно поддерживать код, возвращаться к нему после любой паузы и легко вносить изменения.
[/smart]
## Руководства по стилю
@ -382,21 +410,23 @@ function pow(x, n) {
<li>[Dojo Style Guide](http://dojotoolkit.org/community/styleGuide)</li>
</ul>
Для того, чтобы начать разработку, вполне хватит элементов стилей, обозначенных в этой главе. В дальнейшем, посмотрите на эти руководства, найдите "свой" стиль ;)
Для того, чтобы начать разработку, вполне хватит элементов стилей, обозначенных в этой главе. В дальнейшем, посмотрев эти руководства, вы можете выработать и свой стиль, но лучше не делать его особенно "уникальным и неповторимым", себе дороже потом будет с людьми сотрудничать.
### Автоматизированные средства проверки
Существуют онлайн-сервисы, проверяющие стиль кода.
Существуют средства, проверяющие стиль кода.
Самые известные -- это:
<ul>
<li>[JSLint](http://www.jslint.com/) -- проверяет код на соответствие [стилю JSLint](http://www.jslint.com/lint.html), в онлайн-интерфейсе вверху можно ввести код, а внизу различные настройки проверки, чтобы сделать её более мягкой. </li>
<li>[JSHint](http://www.jshint.com/) -- ещё один вариант JSLint, ослабляющий требования в ряде мест.</li>
<li>[JSHint](http://www.jshint.com/) -- вариант JSLint с большим количеством настроек.</li>
<li>[Closure Linter](https://developers.google.com/closure/utilities/) -- проверка на соответствие [Google JavaScript Style Guide](http://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml).</li>
</ul>
Все они также доступны в виде программ, которые можно скачать.
В частности, JSLint и JSHint интегрированы с большинством редакторов, они гибко настраиваются под нужный стиль и совершенно незаметно улучшают разработку, подсказывая, где и что поправить.
Побочный эффект -- они видят некоторые ошибки, например необъявленные переменные. У меня это обычно результат опечатки, которые таким образом сразу отлавливаются. Очень рекомендую поставить что-то из этого. Я использую [JSHint](http://www.jshint.com/).
## Итого

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 125 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

View file

@ -1,18 +1,26 @@
# Как писать неподдерживаемый код?
[warn header="Познай свой код"]
Эта статья представляет собой мой вольный перевод [How To Write Unmaintainable Code](http://mindprod.com/jgloss/unmain.html) ("как писать неподдерживаемый код") с дополнениями, актуальными для JavaScript.
Возможно, в каких-то из этих советов вам даже удастся узнать "этого парня в зеркале".
[/warn]
Предлагаю вашему вниманию советы мастеров древности, следование которым создаст дополнительные рабочие места для JavaScript-разработчиков.
Если вы будете им следовать, то ваш код будет так сложен в поддержке, что у JavaScript'еров, которые придут после вас, даже простейшее изменение займет годы *оплачиваемого* труда! А сложные задачи оплачиваются хорошо, так что они, определённо, скажут вам "Спасибо".
Более того, *внимательно* следуя этим правилам, вы сохраните и своё рабочее место, так как все будут бояться вашего кода и бежать от него...
...Впрочем, всему своя мера. При написании такого кода он не должен *выглядеть* сложным в поддержке, код должен *быть* таковым. Явно кривой код может написать любой дурак. Это заметят, и вас уволят, а код будет переписан с нуля. Вы не можете такого допустить. Эти советы учитывают такую возможность. Да здравствует дзен.
...Впрочем, всему своя мера. При написании такого кода он не должен *выглядеть* сложным в поддержке, код должен *быть* таковым.
Явно кривой код может написать любой дурак. Это заметят, и вас уволят, а код будет переписан с нуля. Вы не можете такого допустить. Эти советы учитывают такую возможность. Да здравствует дзен.
Статья представляет собой мой вольный перевод [How To Write Unmaintainable Code](http://mindprod.com/jgloss/unmain.html) с дополнениями, актуальными для JavaScript.
[cut]
## Соглашения
## Соглашения -- по настроению
[quote author="Сериал \"Симпсоны\", серия Helter Shelter"]
Рабочий-чистильщик осматривает дом:<br>
@ -31,73 +39,26 @@
Как затруднить задачу? Можно везде нарушать соглашения -- это помешает ему, но такое могут заметить, и код будет переписан. Как поступил бы ниндзя на вашем месте?
**...Правильно! Следуйте соглашениям "в общем", но иногда -- нарушайте их.** Тщательно разбросанные по коду нарушения соглашений с одной стороны не делают код явно плохим при первом взгляде, а с другой -- имеют в точности тот же, и даже лучший эффект, чем явное неследование им!
**...Правильно! Следуйте соглашениям "в общем", но иногда -- нарушайте их.**
Если пример, который я приведу ниже, пока сложноват -- пропустите его, но обязательно вернитесь к нему позже. Поверьте, это стоит того.
Тщательно разбросанные по коду нарушения соглашений с одной стороны не делают код явно плохим при первом взгляде, а с другой -- имеют в точности тот же, и даже лучший эффект, чем явное неследование им!
### Пример из jQuery
[warn header="jQuery / DOM"]
Этот пример требует знаний jQuery/DOM, если пока их у вас нет -- пропустите его, ничего страшного, но обязательно вернитесь к нему позже. Подобное стоит многих часов отладки.
[/warn]
Во фреймворке jQuery есть метод [wrap](http://api.jquery.com/wrap/), который обёртывает один элемент вокруг другого:
```js
var img = $('<img/>'); // создали новые элементы (jQuery-синтаксис)
var div = $('<div/>'); // и поместили в переменную
*!*
img.wrap(div); // обернуть img в div
*/!*
```
Результат кода выше -- два элемента, один вложен в другой:
```html
<div>
<img/>
</div>
```
(`div` обернулся вокруг `img`)
А теперь, когда все расслабились и насладились этим замечательным методом...
...Самое время ниндзя нанести свой удар!
**Как вы думаете, что будет, если добавить к коду выше строку:**
```js
//+ lines first-line=5
div.append('<span/>');
```
[smart header="jQuery-справка"]
Вызов `elemA.append(elemB)` добавляет `elemB` в конец содержимого элемента `elemA`.
[/smart]
**Возможно, вы полагаете, что `<span/>` добавится в конец `div`, сразу после `img`?**
А вот и нет! А вот и нет!..
Оказывается, внутри вызова `img.wrap(div)` происходит *клонирование* `div`. И вокруг `img` оборачивается не сам `div`, а его <strike>злой</strike> клон.
При этом исходная переменная `div` не меняется, в ней как был пустой `div`, так и остался. В итоге, после применения к нему `append` получается два `div'а`: один обёрнут вокруг `span`, а в другом -- только `img`.
<table>
<tr>
<th>Переменная `div`</th>
<th>Клон `div`, созданный `wrap`
(не присвоен никакой переменной)</th>
</tr>
<tr>
<td>
```html
<div>
<span/>
</div>
```
</td>
<td>
Результат кода после операции `wrap` -- два элемента, один вложен в другой:
```html
<div>
@ -105,15 +66,24 @@ div.append('<span/>');
</div>
```
</td>
</tr>
</table>
А что же после `append`?
Странно? Неочевидно? Да, и не только вам :)
Можно предположить, что `<span/>` добавится в конец `div`, сразу после `img`... Но ничего подобного!
Соглашение в данном случае -- в том, что большинство методов jQuery не клонируют элементы. А вызов `wrap` -- клонирует.
Искусный ниндзя уже нанёс свой удар и поведение кода стало неправильным, хотя разработчик об этом даже не подозревает.
Как правило, методы jQuery работают с теми элементами, которые им переданы. Но не здесь!
Внутри вызова `img.wrap(div)` происходит клонирование `div` и вокруг `img` оборачивается не сам `div`, а его клон. При этом исходная переменная `div` не меняется, в ней как был пустой `div`, так и остался.
В итоге, после вызова получается два независимых `div'а`: первый содержит `img` (этот неявный клон никуда не присвоен), а второй -- наш `span`.
Злая магия? Плохой феншуй?
Ничего подобного, просто избирательное следование соглашениям. Вызов `wrap` -- неявно клонирует элемент.
Такой сюрприз, бесспорно, стоит многих часов отладки.
Код его истинный ниндзя писал!
## Краткость -- сестра таланта!
@ -148,7 +118,7 @@ i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0;
Остановите свой взыскательный взгляд на чём-нибудь более экзотическом. Например, `x` или `y`.
Эффективность этого подхода особенно заметна, если тело цикла занимает одну-две страницы.
Эффективность этого подхода особенно заметна, если тело цикла занимает одну-две страницы (чем длиннее -- тем лучше).
В этом случае заметить, что переменная -- счетчик цикла, без пролистывания вверх, невозможно.
@ -281,7 +251,7 @@ function ninjaFunction(elem) {
var *!*user*/!* = authenticateUser();
function render() {
var *!*user*/!* = ...
var *!*user*/!* = anotherValue();
...
...многобукв...
...
@ -290,7 +260,7 @@ function render() {
}
```
Зашедший в середину метода `render` программист, скорее всего, не заметит, что переменная `user` "уже не та" и использует её... Ловушка захлопнулась! Здравствуй, отладчик.
Зашедший в середину метода `render` программист, скорее всего, не заметит, что переменная `user` локально перекрыта и попытается работать с ней, полагая, что это результат `authenticateUser()`... Ловушка захлопнулась! Здравствуй, отладчик.
## Мощные функции!
@ -298,7 +268,9 @@ function render() {
Например, функция `validateEmail(email)` может, кроме проверки e-mail на правильность, выводить сообщение об ошибке и просить заново ввести e-mail.
**Выберите хотя бы пару дополнительных действий, кроме основного назначения функции.** Главное -- они должны быть неочевидны из названия функции. Истинный ниндзя-девелопер сделает так, что они будут неочевидны и из кода тоже.</li>
**Выберите хотя бы пару дополнительных действий, кроме основного назначения функции.**
Главное -- они должны быть неочевидны из названия функции. Истинный ниндзя-девелопер сделает так, что они будут неочевидны и из кода тоже.</li>
**Объединение нескольких смежных действий в одну функцию защитит ваш код от повторного использования.**
@ -315,9 +287,9 @@ function render() {
**Ещё одна вариация такого подхода -- возвращать нестандартное значение.**
Ведь общеизвестно, что `is..` и `check..` обычно возвращают `true/false`. Продемонстрируйте оригинальное мышление. Пусть вызов `checkPermission` возвращает не результат `true/false`, а объект -- с результатами проверки! А что, полезно.
Ведь общеизвестно, что `is..` и `check..` обычно возвращают `true/false`. Продемонстрируйте оригинальное мышление. Пусть вызов `checkPermission` возвращает не результат `true/false`, а объект с результатами проверки! А чего, полезно.
Те разработчики, кто попытается написать проверку `if (checkPermission(..))`, будут весьма удивлены результатом. Ответьте им: "надо читать документацию!". И перешлите эту статью.
Те же разработчики, кто попытается написать проверку `if (checkPermission(..))`, будут весьма удивлены результатом. Ответьте им: "надо читать документацию!". И перешлите эту статью.
## Заключение

View file

@ -8,13 +8,11 @@
При написании функции мы обычно представляем, что она должна делать, какое значение -- на каких аргументах выдавать.
В процессе разработки мы, время от времени, проверяем функцию. Самый простой способ проверки -- это запустить функцию и посмотреть результат.
В процессе разработки мы, время от времени, проверяем, правильно ли работает функция. Самый простой способ проверить -- это запустить её, например, в консоли, и посмотреть результат.
Потом написать ещё код, попробовать запустить -- опять посмотреть результат.
Если что-то не так -- поправить, опять запустить -- посмотреть результат... И так -- "до победного конца".
И так -- "до победного конца".
К сожалению, такие ручные запуски -- очень несовершенное средство проверки.
Но такие ручные запуски -- очень несовершенное средство проверки.
**Когда проверяешь работу кода вручную -- легко его "недотестировать".**
@ -28,15 +26,14 @@
BDD -- это не просто тесты. Это гораздо больше.
**Тесты BDD -- это три в одном: это И тесты И документация И примеры использования одновременно.**
**Тесты BDD -- это три в одном: И тесты И документация И примеры использования одновременно.**
Впрочем, хватит слов. Рассмотрим примеры.
## Разработка pow
## Разработка pow: спецификация
Допустим, мы хотим разработать функцию `pow(x, n)`, которая возводит `x` в целую степень `n`, для простоты `n≥0`.
### Спецификация
Ещё до разработки мы можем представить себе, что эта функция будет делать и описать это по методике BDD.
@ -61,9 +58,15 @@ describe("pow", function() {
<dt>`assert.equal(value1, value2)`</dt>
<dd>Код внутри `it`, если реализация верна, должен выполняться без ошибок.
Для того, чтобы проверить, делает ли `pow` то, что задумано, используются функции вида `assert.*`. Пока что нас интересует только одна из них -- `assert.equal`, она сравнивает свой первый аргумент со вторым и выдаёт ошибку в случае, когда они не равны. Есть и другие виды сравнений и проверок, которые мы увидим далее.</dd>
Различные функции вида `assert.*` используются, чтобы проверить, делает ли `pow` то, что задумано. Пока что нас интересует только одна из них -- `assert.equal`, она сравнивает свой первый аргумент со вторым и выдаёт ошибку в случае, когда они не равны. В данном случае она проверяет, что результат `pow(2, 3)` равен `8`.
Есть и другие виды сравнений и проверок, которые мы увидим далее.</dd>
</dl>
## Поток разработки
Как правило, поток разработки таков:
<ol>
<li>Пишется спецификация, которая описывает самый базовый функционал.</li>
@ -77,7 +80,7 @@ describe("pow", function() {
В нашем случае первый шаг уже завершён, начальная спецификация готова, хорошо бы приступить к реализации. Но перед этим проведём "нулевой" запуск спецификации, просто чтобы увидеть, что уже в таком виде, даже без реализации -- тесты работают.
### Проверка спецификации
## Пример в действии
Для запуска тестов нужны соответствующие JavaScript-библиотеки.
@ -96,22 +99,23 @@ describe("pow", function() {
<!--+ src="index.html" -->
```
Эту страницу можно условно разделить на три части:
Эту страницу можно условно разделить на четыре части:
<ol>
<li>В `<head>` подключаем библиотеки и стили.</li>
<li>Подключаем `<script>` с реализацией, в нашем случае -- с кодом для `pow`. Пока что функции нет, мы лишь готовимся её написать.</li>
<li>Далее подключаются тесты, файл `test.js` содержит `describe("pow", ...)`, который был описан выше. Методы `describe` и `it` принадлежат библиотеке Mocha, так что важно, что она была подключена выше. Их вызов добавляет тесты, для запуска которых используется команда `mocha.run()`. Она выведет результат тестов в элемент с `id="mocha"`.</li>
<li>Блок `<head>` -- в нём мы подключаем библиотеки и стили для тестирования, нашего кода там нет.</li>
<li>Блок `<script>` с реализацией спецификации, в нашем случае -- с кодом для `pow`.</li>
<li>Далее подключаются тесты, файл `test.js` содержит `describe("pow", ...)`, который был описан выше. Методы `describe` и `it` принадлежат библиотеке Mocha, так что важно, что она была подключена выше.</li>
<li>Элемент `<div id="mocha">` будет использоваться библиотекой Mocha для вывода результатов. Запуск тестов инициируется командой `mocha.run()`.</li>
</ol>
Результат срабатывания:
[iframe height=250 src="pow-1" border=1 edit]
Пока что тесты не проходят, но это логично -- вместо функции стоит "заглушка", пустой код.
Пока что у нас одна функция и одна спецификация, но на будущее заметим, что если различных функций и тестов много -- это не проблема, можно их все подключить на одной странице. Конфликта не будет, так как каждый функционал имеет свой блок `describe("что тестируем"...)`. Сами же тесты обычно пишутся так, чтобы не влиять друг на друга, не делать лишних глобальных переменных.
Посмотрели, попробовали запустить у себя что-то подобное? Если да -- идём дальше.
### Начальная реализация
## Начальная реализация
Пока что, как видно, тесты не проходят, ошибка сразу же. Давайте сделаем минимальную реализацию `pow`, которая бы работала нормально:
@ -125,7 +129,7 @@ function pow() {
[iframe height=250 src="pow-min" border=1 edit]
### Расширение спецификации
## Исправление спецификации
Функция, конечно, ещё не готова, но тесты проходят. Это ненадолго :)
@ -192,9 +196,9 @@ describe("pow", function() {
Как и следовало ожидать, второй тест не проходит. Ещё бы, ведь функция всё время возвращает `8`.
### Уточнение реализации
## Уточнение реализации
Придётся написать нечто более реальное:
Придётся написать нечто более реальное, чтобы тесты проходили:
```js
function pow(x, n) {
@ -231,7 +235,7 @@ describe("pow", function() {
[iframe height=250 src="pow-3" edit border="1"]
### Вложенный describe
## Вложенный describe
Функция `makeTest` и цикл `for`, очевидно, нужны друг другу, но не нужны для других тестов, которые мы добавим в дальнейшем. Они образуют единую группу, задача которой -- проверить возведение в `n`-ю степень.
@ -307,9 +311,9 @@ describe("Тест", function() {
Как правило, `beforeEach/afterEach` (`before/each`) используют, если необходимо произвести инициализацию, обнулить счётчики или сделать что-то ещё в таком духе между тестами (или их группами).
[/smart]
### Расширение спецификации
## Расширение спецификации
Базовый функционал описан и реализован, первая итерация разработки завершена. Теперь расширим и уточним его.
Базовый функционал `pow` описан и реализован, первая итерация разработки завершена. Теперь расширим и уточним его.
Как говорилось ранее, функция `pow(x, n)` предназначена для работы с целыми неотрицательными `n`.
@ -342,7 +346,7 @@ describe("pow", function() {
Конечно, новые тесты не проходят, так как наша реализация ещё не поддерживает их. Так и задумано: сначала написали заведомо не работающие тесты, а затем пишем реализацию под них.
### Другие assert
## Другие assert
Обратим внимание, в спецификации выше использована проверка не `assert.equal`, как раньше, а `assert(выражение)`. Такая проверка выдаёт ошибку, если значение выражения при приведении к логическому типу не `true`.
@ -352,7 +356,7 @@ describe("pow", function() {
Однако, между этими вызовами есть отличие в деталях сообщения об ошибке.
При `assert` мы видим `Unspecified AssertionError`, то есть просто "что-то пошло не так", а при `assert.equal(value1, value2)` -- будут дополнительные подробности: `expected 8 to equal 81`.
При "упавшем" `assert` в примере выше мы видим `"Unspecified AssertionError"`, то есть просто "что-то пошло не так", а при `assert.equal(value1, value2)` -- будут дополнительные подробности: `expected 8 to equal 81`.
**Поэтому рекомендуется использовать именно ту проверку, которая максимально соответствует задаче.**

View file

@ -1,7 +1,7 @@
describe("Тест", function() {
before(function() { alert("Начало тестов"); });
after(function() { alert("Конец тестов"); });
before(function() { alert("Начало всех тестов"); });
after(function() { alert("Окончание всех тестов"); });
beforeEach(function() { alert("Вход в тест"); });
afterEach(function() { alert("Выход из теста"); });

View file

@ -1,44 +0,0 @@
describe("pow", function() {
describe("возводит x в степень n", function() {
function makeTest(x) {
var expected = x*x*x;
it("при возведении "+x+" в степень 3 результат: " + expected, function() {
assert.equal( pow(x, 3), expected);
});
}
for(var x = 1; x <= 5; x++) {
makeTest(x);
}
});
it("при возведении в отрицательную степень результат NaN", function() {
assert( isNaN( pow(2, -1) ), "pow(2, -1) не NaN" );
});
it("при возведении в дробную степень результат NaN", function() {
assert( isNaN( pow(2, 1.5) ), "pow(2, -1.5) не NaN" );
});
describe("любое число, кроме нуля, в степени 0 равно 1", function() {
function makeTest(x) {
it("при возведении " + x + " в степень 0 результат: 1", function() {
assert.equal( pow(x, 0), 1);
});
}
for(var x = -5; x <= 5; x+=2) {
makeTest(x);
}
});
it("ноль в нулевой степени даёт NaN", function() {
assert( isNaN( pow(0,0) ), "0 в степени 0 не NaN");
});
});