renovations
|
@ -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>
|
||||
|
||||
|
|
|
@ -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-фреймворков.
|
||||
## Аргументы по умолчанию
|
||||
|
||||
Функцию можно вызвать с любым количеством аргументов.
|
||||
|
|
|
@ -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, мы встретимся с ней позже, при работе с интерфейсами.
|
||||
|
||||
## Итого
|
||||
|
|
@ -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]
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -23,6 +23,8 @@ alert('Привет');
|
|||
alert('Мир');
|
||||
```
|
||||
|
||||
## Точка с запятой [#semicolon]
|
||||
|
||||
Точку с запятой *во многих случаях* можно не ставить, если есть переход на новую строку.
|
||||
|
||||
Так тоже будет работать:
|
||||
|
|
|
@ -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`:
|
||||
|
||||
|
|
|
@ -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]).
|
||||
|
||||
|
|
Before Width: | Height: | Size: 84 KiB |
Before Width: | Height: | Size: 81 KiB |
Before Width: | Height: | Size: 82 KiB |
Before Width: | Height: | Size: 68 KiB |
Before Width: | Height: | Size: 70 KiB |
Before Width: | Height: | Size: 25 KiB |
19
1-js/3-writing-js/1-debugging-chrome/debugging.view/index.html
Executable 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>
|
8
1-js/3-writing-js/1-debugging-chrome/debugging.view/pow.js
Executable file
|
@ -0,0 +1,8 @@
|
|||
function pow(x, n) {
|
||||
if (n == 1) {
|
||||
return x;
|
||||
}
|
||||
|
||||
var result = x * pow(x, n-1);
|
||||
return result;
|
||||
}
|
19
1-js/3-writing-js/1-debugging-chrome/error.view/index.html
Executable 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>
|
8
1-js/3-writing-js/1-debugging-chrome/error.view/pow.js
Executable file
|
@ -0,0 +1,8 @@
|
|||
function pow(x, n) {
|
||||
if (n == 1) {
|
||||
return y;
|
||||
}
|
||||
|
||||
var result = x * pow(x, n-1);
|
||||
return result;
|
||||
}
|
Before Width: | Height: | Size: 717 B |
Before Width: | Height: | Size: 504 B |
Before Width: | Height: | Size: 431 B |
Before Width: | Height: | Size: 418 B |
Before Width: | Height: | Size: 554 B |
Before Width: | Height: | Size: 463 B |
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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/).
|
||||
|
||||
## Итого
|
||||
|
||||
|
|
Before Width: | Height: | Size: 38 KiB |
94
1-js/3-writing-js/2-coding-style/code-style.svg
Normal file
After Width: | Height: | Size: 125 KiB |
32
1-js/3-writing-js/2-coding-style/figure-bracket-style.svg
Normal file
After Width: | Height: | Size: 74 KiB |
Before Width: | Height: | Size: 17 KiB |
|
@ -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(..))`, будут весьма удивлены результатом. Ответьте им: "надо читать документацию!". И перешлите эту статью.
|
||||
|
||||
## Заключение
|
||||
|
||||
|
|
|
@ -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`.
|
||||
|
||||
**Поэтому рекомендуется использовать именно ту проверку, которая максимально соответствует задаче.**
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
describe("Тест", function() {
|
||||
|
||||
before(function() { alert("Начало тестов"); });
|
||||
after(function() { alert("Конец тестов"); });
|
||||
before(function() { alert("Начало всех тестов"); });
|
||||
after(function() { alert("Окончание всех тестов"); });
|
||||
|
||||
beforeEach(function() { alert("Вход в тест"); });
|
||||
afterEach(function() { alert("Выход из теста"); });
|
||||
|
|
|
@ -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");
|
||||
});
|
||||
|
||||
});
|