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

View file

@ -65,13 +65,14 @@ alert(message); // <-- будет ошибка, т.к. переменная ви
```js ```js
function count() { function count() {
// переменные i,j не будут уничтожены по окончании цикла
for (*!*var*/!* i=0; i<3; i++) { for (*!*var*/!* i=0; i<3; i++) {
*!*var*/!* j = i * 2; *!*var*/!* j = i * 2;
} }
*!* *!*
alert(i); // i=3, на этом значении цикл остановился alert(i); // i=3, последнее значение i, при нём цикл перестал работать
alert(j); // j=4, последнее значение, на котором цикл сработал, было i=2 alert(j); // j=4, последнее значение j, которое вычислил цикл
*/!* */!*
} }
``` ```
@ -114,13 +115,12 @@ showMessage(); // Привет, я Вася
```js ```js
//+ run //+ run
var *!*userName*/!* = 'Вася'; var userName = 'Вася';
function showMessage() { function showMessage() {
*!*
userName = 'Петя'; // (1) присвоение во внешнюю переменную userName = 'Петя'; // (1) присвоение во внешнюю переменную
*/!*
var message = 'Привет, я ' + *!*userName*/!*; var message = 'Привет, я ' + userName;
alert(message); alert(message);
} }
@ -133,13 +133,11 @@ alert(userName); // Петя, значение внешней переменно
Конечно, если бы внутри функции, в строке `(1)`, была бы объявлена своя локальная переменная `var userName`, то все обращения использовали бы её, и внешняя переменная осталась бы неизменной. Конечно, если бы внутри функции, в строке `(1)`, была бы объявлена своя локальная переменная `var userName`, то все обращения использовали бы её, и внешняя переменная осталась бы неизменной.
[summary]
**Переменные, объявленные на уровне всего скрипта, называют *"глобальными переменными"*.** **Переменные, объявленные на уровне всего скрипта, называют *"глобальными переменными"*.**
Делайте глобальными только те переменные, которые действительно имеют общее значение для вашего проекта. В примере выше переменная `userName` -- глобальная.
Пусть каждая функция работает "в своей песочнице". Делайте глобальными только те переменные, которые действительно имеют общее значение для вашего проекта, а нужные для решения конкретной задачи -- пусть будут локальными в соответствующей функции.
[/summary]
[warn header="Внимание: неявное объявление глобальных переменных!"] [warn header="Внимание: неявное объявление глобальных переменных!"]
@ -167,7 +165,7 @@ alert(message); // Привет
Здесь опасность даже не в автоматическом создании переменной, а в том, что глобальные переменные должны использоваться тогда, когда действительно нужны "общескриптовые" параметры. Здесь опасность даже не в автоматическом создании переменной, а в том, что глобальные переменные должны использоваться тогда, когда действительно нужны "общескриптовые" параметры.
Забыли `var` в одном месте, потом в другом -- в результате одна функция неожиданно поменяла глобальную переменную, которую использует другая. Возможна ошибка и потеря времени на поиск проблемы. Забыли `var` в одном месте, потом в другом -- в результате одна функция неожиданно поменяла глобальную переменную, которую использует другая. И поди разберись, кто и когда её поменял, не самая приятная ошибка для отладки.
[/warn] [/warn]
В будущем, когда мы лучше познакомимся с основами JavaScript, в главе [](/closures), мы более детально рассмотрим внутренние механизмы работы переменных и функций. В будущем, когда мы лучше познакомимся с основами JavaScript, в главе [](/closures), мы более детально рассмотрим внутренние механизмы работы переменных и функций.
@ -201,7 +199,7 @@ showMessage('Маша', 'Как дела?');
//+ run //+ run
function showMessage(from, text) { function showMessage(from, text) {
*!* *!*
from = '**' + from + '**'; // меняем локальную переменную (1) from = '**' + from + '**'; // меняем локальную переменную from
*/!* */!*
alert(from + ': ' + text); alert(from + ': ' + text);
} }
@ -210,32 +208,9 @@ var from = "Маша";
showMessage(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 функция является значением, таким же как строка или число. В JavaScript функция является значением, таким же как строка или число.
## Функция -- это значение
Как и любое значение, объявленную функцию можно вывести, вот так: Как и любое значение, объявленную функцию можно вывести, вот так:
```js ```js
@ -41,14 +39,16 @@ sayHi(); // ошибка (4)
<li>...Однако, в любой момент значение переменной можно поменять. При этом, если оно не функция, то вызов `(4)` выдаст ошибку.</li> <li>...Однако, в любой момент значение переменной можно поменять. При этом, если оно не функция, то вызов `(4)` выдаст ошибку.</li>
</ol> </ol>
Обычные значения, такие как числа или строки, представляют собой *данные*. А функцию можно воспринимать как *действие*. Это действие, как правило, хранится в переменной, но его можно скопировать или переместить из неё. Обычные значения, такие как числа или строки, представляют собой *данные*. А функцию можно воспринимать как *действие*.
Это действие можно запустить через скобки `()`, а можно и скопировать в другую переменную, как было продемонстрировано выше.
## Объявление Function Expression [#function-expression] ## Объявление Function Expression [#function-expression]
Функцию можно создать и присвоить переменной в любом месте кода. Существует альтернативный синтаксис для объявления функции, который ещё более наглядно показывает, что функция -- это всего лишь разновидность значения переменной.
Для этого используется объявление "Function Expression" (функциональное выражение), которое выглядит так: Он называется "Function Expression" (функциональное выражение) и выглядит так:
```js ```js
//+ run //+ run
@ -73,8 +73,8 @@ sayHi('Вася');
"Классическое" объявление функции, о котором мы говорили до этого, вида `function имя(параметры) {...}`, называется в спецификации языка "Function Declaration". "Классическое" объявление функции, о котором мы говорили до этого, вида `function имя(параметры) {...}`, называется в спецификации языка "Function Declaration".
<ul> <ul>
<li>**Function Declaration** -- функция, объявленная в основном потоке кода.</li> <li>*Function Declaration* -- функция, объявленная в основном потоке кода.</li>
<li>**Function Expression** -- объявление функции в контексте какого-либо выражения, например присваивания.</li> <li>*Function Expression* -- объявление функции в контексте какого-либо выражения, например присваивания.</li>
</ul> </ul>
Несмотря на немного разный вид, по сути две эти записи делают одно и то же: Несмотря на немного разный вид, по сути две эти записи делают одно и то же:
@ -93,13 +93,7 @@ var sum = function(a, b) {
Оба этих объявления говорят интерпретатору: "объяви переменную `sum`, создай функцию с указанными параметрами и кодом и сохрани её в `sum`". Оба этих объявления говорят интерпретатору: "объяви переменную `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] ### Условное объявление функции [#bad-conditional-declaration]
@ -142,7 +137,7 @@ var sayHi = function(name) {
```js ```js
//+ run //+ run
var age = 20; var age = +prompt("Сколько вам лет?", 20);
if (age >= 18) { if (age >= 18) {
function sayHi() { alert('Прошу вас!'); } function sayHi() { alert('Прошу вас!'); }
@ -153,15 +148,7 @@ if (age >= 18) {
sayHi(); sayHi();
``` ```
[smart header="Зачем условное объявление?"] При вводе `20` в примере выше в любом браузере, кроме Firefox, мы увидим, что условное объявление не работает. Срабатывает `"До 18 нельзя"`, несмотря на то, что `age = 20`.
Конечно, можно произвести проверку условия внутри функции.
Но вынос проверки вовне даёт очевидный выигрыш в производительности в том случае, когда проверку достаточно произвести только один раз, и её результат никогда не изменится.
Например, мы можем проверить, поддерживает ли браузер определённые современные возможности, и если да -- функция будет использовать их, а если нет -- реализовать её другим способом. При этом проверка будет осуществляться один раз, на этапе объявления функции, а не при каждом запуске функции.
[/smart]
При запуске примера выше в любом браузере, кроме Firefox, мы увидим, что условное объявление не работает. Срабатывает `"До 18 нельзя"`, несмотря на то, что `age = 20`.
В чём дело? Чтобы ответить на этот вопрос -- вспомним, как работают функции. В чём дело? Чтобы ответить на этот вопрос -- вспомним, как работают функции.
@ -231,55 +218,54 @@ sayHi();
Взглянем ещё на один пример. Взглянем ещё на один пример.
Функция `test(f, yes, no)` получает три функции, вызывает первую и, в зависимости от её результата, вызывает вторую или третью: Функция `ask(question, yes, no)` предназначена для выбора действия в зависимости от результата `f`.
Она выводит вопрос на подтверждение `question` и, в зависимости от согласия пользователя, вызывает `yes` или `no`:
```js ```js
//+ run //+ run
*!* *!*
function test(f, yes, no) { function ask(question, yes, no) {
if (f()) yes() if (confirm(question)) yes()
else no(); else no();
} }
*/!* */!*
// вспомогательные функции function showOk() {
function f1() {
return confirm("OK?");
}
function f2() {
alert("Вы согласились."); alert("Вы согласились.");
} }
function f3() { function showCancel() {
alert("Вы отменили выполнение."); alert("Вы отменили выполнение.");
} }
// использование // использование
test(f1, f2, f3); ask("Вы согласны?", showOk, showCancel);
``` ```
В этом примере для нас, наверно, нет ничего нового. Подумаешь, объявили функции `f1`, `f2`, `f3`, передали их в качестве параметров другой функции (ведь функция -- обычное значение), вызвали те, которые нужны... Какой-то очень простой код, не правда ли? Зачем, вообще, может понадобиться такая `ask`?
А вот то же самое, но более коротко: ...Но при работе со страницей такие функции как раз очень востребованы, только вот спрашивают они не простым `confirm`, а выводят более красивое окно с вопросом и могут интеллектуально обработать ввод посетителя. Но это всё в своё время.
Здесь обратим внимание на то, что то же самое можно написать более коротко:
```js ```js
//+ run //+ run
function test(f, yes, no) { function ask(question, yes, no) {
if (f()) yes() if (confirm(question)) yes()
else no(); else no();
} }
*!* *!*
test( ask(
function() { return confirm("OK?"); }, "Вы согласны?",
function() { alert("Вы согласились."); }, function() { alert("Вы согласились."); },
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).** **Функциональное выражение, которое не записывается в переменную, называют [анонимной функцией](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 ## new Function
Существует ещё один способ создания функции, который используется очень редко. Существует ещё один способ создания функции, который используется очень редко, но упомянем и его для полноты картины.
Он выглядит так: Он позволяет создавать функцию полностью "на лету" из строки, вот так:
```js ```js
//+ run //+ run
@ -309,7 +295,9 @@ alert(result); // 3
<dd>Код функции в виде строки.</dd> <dd>Код функции в виде строки.</dd>
</dl> </dl>
Этот способ позволяет конструировать строку с кодом функции динамически, во время выполнения программы. Это, скорее, исключение, чем правило, но также бывает востребовано. Пример использования -- динамическая компиляция шаблонов на JavaScript, мы встретимся с ней позже, при работе с интерфейсами. Таким образом можно конструировать функцию, код которой неизвестен на момент написания программы, но строка с ним генерируется или подгружается динамически во время её выполнения.
Пример использования -- динамическая компиляция шаблонов на JavaScript, мы встретимся с ней позже, при работе с интерфейсами.
## Итого ## Итого

View file

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

View file

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

View file

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

View file

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

@ -27,11 +27,13 @@ else // <- можно на одной строке } else {
Исправленный вариант: Исправленный вариант:
```js ```js
function pow(x,n) { function pow(x, n) {
var result = 1; var result = 1;
for(var i = 0; i < n; i++) { for(var i = 0; i < n; i++) {
result *=x; result *=x;
} }
return result; return result;
} }

View file

@ -5,27 +5,70 @@
[cut] [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` символов, в зависимости от того, какие мониторы у разработчиков. Максимальную длину строки согласовывают в команде. Как правило, это либо `80`, либо `120` символов, в зависимости от того, какие мониторы у разработчиков.
Более длинные строки необходимо разбивать. Если этого не сделать, то перевод очень длинной строки сделает редактор, и это может быть менее красиво и читаемо. Более длинные строки необходимо разбивать для улучшения читаемости.
### Отступы ### Отступы
@ -36,42 +79,29 @@
Как правило, используются именно пробелы, т.к. они позволяют сделать более гибкие "конфигурации отступов", чем символ "Tab". Как правило, используются именно пробелы, т.к. они позволяют сделать более гибкие "конфигурации отступов", чем символ "Tab".
Например: Например, выровнять аргументы относительно открывающей скобки:
```js ```js
function fib(n) { show("Строки" +
*!* " выровнены" +
var a = 1; " строго" +
var b = 1; " одна под другой");
*/!*
for (var i = 3; i <= n; i++) {
var c = a + b;
a = b;
b = c;
}
return b;
}
``` ```
Кстати, обратите внимание, переменные в выделенном фрагменте объявлены по вертикали, а не в строку `var a=1, b=1`. Так более наглядно, человеческий глаз лучше воспринимает ("сканирует") вертикально выравненную информацию, нежели по горизонтали. Это известный факт среди дизайнеров и нам, программистам, он тоже будет полезен для лучшей организации кода.
</li> </li>
<li>**Вертикальный отступ, для лучшей разбивки кода -- перевод строки.** <li>**Вертикальный отступ, для лучшей разбивки кода -- перевод строки.**
Используется, чтобы разделить логические блоки внутри одной функции. В примере ниже разделены функция `pow`, получение данных `x,n` и их обработка `if`. Используется, чтобы разделить логические блоки внутри одной функции. В примере разделены инициализация переменных, главный цикл и возвращение результата:
```js ```js
function pow(x, n) { function pow(x, n) {
return (n != 1) ? pow(x, n-1) : x; var result = 1;
} // <--
// <-- for (var i = 0; i < n; i++) {
x = prompt(...); result *=x;
n = prompt(...); }
// <-- // <--
if (n >= 1) { return result;
var result = pow(x, n);
alert(result);
} }
``` ```
Вставляйте дополнительный перевод строки туда, где это сделает код более читаемым. Не должно быть более 9 строк кода подряд без вертикального отступа. Вставляйте дополнительный перевод строки туда, где это сделает код более читаемым. Не должно быть более 9 строк кода подряд без вертикального отступа.
@ -82,9 +112,7 @@ if (n >= 1) {
Точки с запятой нужно ставить, даже если их, казалось бы, можно пропустить. Точки с запятой нужно ставить, даже если их, казалось бы, можно пропустить.
Есть языки, в которых точка с запятой не обязательна, и её там никто не ставит. В JavaScript она тоже не обязательна, но ставить нужно. В чём же разница? Есть языки, в которых точка с запятой не обязательна, и её там никто не ставит. В JavaScript перевод строки её заменяет, но лишь частично, поэтому лучше её ставить, как обсуждалось [ранее](#semicolon).
Она в том, что **в JavaScript без точки с запятой возможны трудноуловимые ошибки.** С некоторыми примерами вы встретитесь дальше в учебнике. Такая вот особенность синтаксиса. И поэтому рекомендуется её всегда ставить.
## Именование ## Именование
@ -102,7 +130,7 @@ if (n >= 1) {
Уровней вложенности должно быть немного. Уровней вложенности должно быть немного.
Например, [проверки в циклах лучше делать через "continue"](#continue), чтобы не было дополнительного уровня `if(..) { ... }`: Например, [проверки в циклах можно делать через "continue"](#continue), чтобы не было дополнительного уровня `if(..) { ... }`:
Вместо: Вместо:
@ -159,21 +187,15 @@ function isEven(n) { // проверка чётности
В случае с функцией `isEven` можно было бы поступить и проще: В случае с функцией `isEven` можно было бы поступить и проще:
```js
function isEven(n) { // проверка чётности
return n % 2 == 0;
}
```
..Казалось бы, можно пойти дальше, есть ещё более короткий вариант:
```js ```js
function isEven(n) { // проверка чётности function isEven(n) { // проверка чётности
return !(n % 2); return !(n % 2);
} }
``` ```
...Однако, код `!(n % 2)` менее очевиден чем `n % 2 == 0`. Поэтому, на самом деле, последний вариант хуже. **Главное для нас -- не краткость кода, а его простота и читаемость.** ...Однако, если код `!(n % 2)` для вас менее очевиден чем предыдущий вариант, то стоит использовать предыдущий.
Главное для нас -- не краткость кода, а его простота и читаемость. Совсем не всегда более короткий код проще для понимания, чем более развёрнутый.
## Функции = Комментарии ## Функции = Комментарии
@ -185,7 +207,7 @@ function isEven(n) { // проверка чётности
Сравните, например, две функции `showPrimes(n)` для вывода простых чисел до `n`. Сравните, например, две функции `showPrimes(n)` для вывода простых чисел до `n`.
Первый вариант: Первый вариант использует метку:
```js ```js
function showPrimes(n) { function showPrimes(n) {
@ -201,7 +223,7 @@ function showPrimes(n) {
} }
``` ```
Второй вариант, вынесена подфункция `isPrime(n)` для проверки на простоту: Второй вариант -- дополнительную функцию `isPrime(n)` для проверки на простоту:
```js ```js
function showPrimes(n) { function showPrimes(n) {
@ -277,28 +299,41 @@ function walkAround() {
</li> </li>
</ol> </ol>
...На самом деле существует еще третий "стиль", при котором функции хаотично разбросаны по коду ;), но это ведь не наш метод, да? ...На самом деле существует еще третий "стиль", при котором функции хаотично разбросаны по коду, но это ведь не наш метод, да?
**Как правило, лучше располагать функции под кодом, который их использует.** То есть, это 2й способ. **Как правило, лучше располагать функции под кодом, который их использует.**
Дело в том, что при чтении такого кода мы хотим знать в первую очередь, *что он делает*, а уже затем *какие функции ему помогают.* Если первым идёт код, то это как раз дает необходимую информацию. Что же касается функций, то вполне возможно нам и не понадобится их читать, особенно если они названы адекватно и то, что они делают, понятно. То есть, предпочтителен 2й способ.
У первого способа, впрочем, есть то преимущество, что на момент чтения мы уже знаем, какие функции существуют. Дело в том, что при чтении такого кода мы хотим знать в первую очередь, *что он делает*, а уже затем *какие функции ему помогают.* Если первым идёт код, то это как раз дает необходимую информацию. Что же касается функций, то вполне возможно нам и не понадобится их читать, особенно если они названы адекватно и то, что они делают, понятно из названия.
Таким образом, если над названиями функций никто не думает -- может быть, это будет лучшим выбором :). Попробуйте оба варианта, но по моей практике предпочтителен всё же второй. ## Плохие комментарии
## Комментарии
В коде нужны комментарии. В коде нужны комментарии.
**Как правило, комментарии отвечают на вопрос "что происходит в коде?"** Сразу начну с того, каких комментариев быть почти не должно.
**Должен быть минимум комментариев, которые отвечают на вопрос "что происходит в коде?"**
Что интересно, в коде начинающих разработчиков обычно комментариев либо нет, либо они как раз такого типа: "что делается в этих строках".
Серьёзно, хороший код и так понятен.
Об этом замечательно выразился Р.Мартин в книге ["Чистый код"](http://www.ozon.ru/context/detail/id/21916535/): "Если вам кажется, что нужно добавить комментарий для улучшения понимания, это значит, что ваш код недостаточно прост, и, может, стоит переписать его".
Если у вас образовалась длинная "простыня", то, возможно, стоит разбить её на отдельные функции, и тогда из их названий будет понятно, что делает тот или иной фрагмент.
Да, конечно, бывают сложные алгоритмы, хитрые решения для оптимизации, поэтому нельзя такие комментарии просто запретить. Но перед тем, как писать подобное -- подумайте: "Нельзя ли сделать код понятным и без них?"
## Хорошие комментарии
А какие комментарии полезны и приветствуются?
Например:
<ul> <ul>
<li>**Архитектурный комментарий -- "как оно, вообще, устроено".** <li>**Архитектурный комментарий -- "как оно, вообще, устроено".**
Какие компоненты есть, какие технологии использованы, поток взаимодействия. О чём и зачем этот скрипт. Эти комментарии особенно нужны, если вы не один. Какие компоненты есть, какие технологии использованы, поток взаимодействия. О чём и зачем этот скрипт. Взгляд с высоты птичьего полёта. Эти комментарии особенно нужны, если вы не один, а проект большой.
Для описания архитектуры, кстати, создан специальный язык [UML](http://ru.wikipedia.org/wiki/Unified_Modeling_Language), красивые диаграммы, но можно и без этого. Главное -- чтобы понятно. Для описания архитектуры, кстати, создан специальный язык [UML](http://ru.wikipedia.org/wiki/Unified_Modeling_Language), красивые диаграммы, но можно и без этого. Главное -- чтобы понятно.
</li> </li>
@ -319,18 +354,11 @@ function pow(x, n) {
} }
``` ```
Такие комментарии позволяют сразу понять, что принимает и что делает функция, не вникая в код. Такие комментарии позволяют сразу понять, что принимает и что делает функция, не вникая в код.
Кстати, они автоматически обрабатываются многими редакторами, например [Aptana](http://aptana.com) и редакторами от [JetBrains](http://www.jetbrains.com/), которые учитывают их при автодополнении. Кстати, они автоматически обрабатываются многими редакторами, например [Aptana](http://aptana.com) и редакторами от [JetBrains](http://www.jetbrains.com/), которые учитывают их при автодополнении, а также выводят их в автоподсказках при наборе кода.
</li>
<li>**Краткий комментарий, что именно происходит в данном блоке кода.**
Что интересно, в коде начинающих разработчиков обычно комментариев либо нет, либо они как раз такого типа: "что делается в этих строках кода".
На самом деле именно эти комментарии, как правило, являются самыми ненужными. Хороший код и так самоочевиден, если не используются особо сложные алгоритмы.
Об этом замечательно выразился Р. Мартин в книге ["Чистый код"](http://www.ozon.ru/context/detail/id/21916535/): "Если вам кажется, что нужно добавить комментарий для улучшения понимания, это значит, что ваш код не достаточно прост, и, может, стоит переписать его".
Кроме того, есть инструменты, например [JSDoc 3](https://github.com/jsdoc3/jsdoc), которые умеют генерировать по таким комментариям документацию в формате HTML. Более подробную информацию об этом можно также найти на сайте [](http://usejsdoc.org/).
</li> </li>
</ul> </ul>
@ -342,9 +370,9 @@ function pow(x, n) {
Например: Например:
<ul> <dl>
<li>**Есть несколько способов решения задачи. Почему выбран именно этот?** <dt>Есть несколько способов решения задачи. Почему выбран именно этот?</dt>
<dd>
Например, пробовали решить задачу по-другому, но не получилось -- напишите об этом. Почему вы выбрали именно этот способ решения? Особенно это важно в тех случаях, когда используется не первый приходящий в голову способ, а какой-то другой. Например, пробовали решить задачу по-другому, но не получилось -- напишите об этом. Почему вы выбрали именно этот способ решения? Особенно это важно в тех случаях, когда используется не первый приходящий в голову способ, а какой-то другой.
Без этого возможна, например, такая ситуация: Без этого возможна, например, такая ситуация:
@ -354,18 +382,18 @@ function pow(x, n) {
<li>...Порыв, конечно, хороший, да только этот вариант вы уже обдумали раньше. И отказались, а почему -- забыли. В процессе переписывания вспомнили, конечно (к счастью), но результат - потеря времени на повторное обдумывание.</li> <li>...Порыв, конечно, хороший, да только этот вариант вы уже обдумали раньше. И отказались, а почему -- забыли. В процессе переписывания вспомнили, конечно (к счастью), но результат - потеря времени на повторное обдумывание.</li>
</ul> </ul>
Комментарии, которые объясняют поведение кода, очень важны. Они помогают понять происходящее и принять правильное решение о развитии кода. Комментарии, которые объясняют выбор решения, очень важны. Они помогают понять происходящее и предпринять правильные шаги при развитии кода.
</dd>
</li> <dt>Какие неочевидные возможности обеспечивает этот код? Где ещё они используются?</dt>
<li>**Какие неочевидные возможности обеспечивает этот код?** Где в другом месте кода они используются? <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> <li>[Dojo Style Guide](http://dojotoolkit.org/community/styleGuide)</li>
</ul> </ul>
Для того, чтобы начать разработку, вполне хватит элементов стилей, обозначенных в этой главе. В дальнейшем, посмотрите на эти руководства, найдите "свой" стиль ;) Для того, чтобы начать разработку, вполне хватит элементов стилей, обозначенных в этой главе. В дальнейшем, посмотрев эти руководства, вы можете выработать и свой стиль, но лучше не делать его особенно "уникальным и неповторимым", себе дороже потом будет с людьми сотрудничать.
### Автоматизированные средства проверки ### Автоматизированные средства проверки
Существуют онлайн-сервисы, проверяющие стиль кода. Существуют средства, проверяющие стиль кода.
Самые известные -- это: Самые известные -- это:
<ul> <ul>
<li>[JSLint](http://www.jslint.com/) -- проверяет код на соответствие [стилю JSLint](http://www.jslint.com/lint.html), в онлайн-интерфейсе вверху можно ввести код, а внизу различные настройки проверки, чтобы сделать её более мягкой. </li> <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> <li>[Closure Linter](https://developers.google.com/closure/utilities/) -- проверка на соответствие [Google JavaScript Style Guide](http://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml).</li>
</ul> </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-разработчиков.
Если вы будете им следовать, то ваш код будет так сложен в поддержке, что у JavaScript'еров, которые придут после вас, даже простейшее изменение займет годы *оплачиваемого* труда! А сложные задачи оплачиваются хорошо, так что они, определённо, скажут вам "Спасибо". Если вы будете им следовать, то ваш код будет так сложен в поддержке, что у JavaScript'еров, которые придут после вас, даже простейшее изменение займет годы *оплачиваемого* труда! А сложные задачи оплачиваются хорошо, так что они, определённо, скажут вам "Спасибо".
Более того, *внимательно* следуя этим правилам, вы сохраните и своё рабочее место, так как все будут бояться вашего кода и бежать от него... Более того, *внимательно* следуя этим правилам, вы сохраните и своё рабочее место, так как все будут бояться вашего кода и бежать от него...
...Впрочем, всему своя мера. При написании такого кода он не должен *выглядеть* сложным в поддержке, код должен *быть* таковым. Явно кривой код может написать любой дурак. Это заметят, и вас уволят, а код будет переписан с нуля. Вы не можете такого допустить. Эти советы учитывают такую возможность. Да здравствует дзен. ...Впрочем, всему своя мера. При написании такого кода он не должен *выглядеть* сложным в поддержке, код должен *быть* таковым.
Явно кривой код может написать любой дурак. Это заметят, и вас уволят, а код будет переписан с нуля. Вы не можете такого допустить. Эти советы учитывают такую возможность. Да здравствует дзен.
Статья представляет собой мой вольный перевод [How To Write Unmaintainable Code](http://mindprod.com/jgloss/unmain.html) с дополнениями, актуальными для JavaScript.
[cut] [cut]
## Соглашения ## Соглашения -- по настроению
[quote author="Сериал \"Симпсоны\", серия Helter Shelter"] [quote author="Сериал \"Симпсоны\", серия Helter Shelter"]
Рабочий-чистильщик осматривает дом:<br> Рабочий-чистильщик осматривает дом:<br>
@ -31,22 +39,26 @@
Как затруднить задачу? Можно везде нарушать соглашения -- это помешает ему, но такое могут заметить, и код будет переписан. Как поступил бы ниндзя на вашем месте? Как затруднить задачу? Можно везде нарушать соглашения -- это помешает ему, но такое могут заметить, и код будет переписан. Как поступил бы ниндзя на вашем месте?
**...Правильно! Следуйте соглашениям "в общем", но иногда -- нарушайте их.** Тщательно разбросанные по коду нарушения соглашений с одной стороны не делают код явно плохим при первом взгляде, а с другой -- имеют в точности тот же, и даже лучший эффект, чем явное неследование им! **...Правильно! Следуйте соглашениям "в общем", но иногда -- нарушайте их.**
Если пример, который я приведу ниже, пока сложноват -- пропустите его, но обязательно вернитесь к нему позже. Поверьте, это стоит того. Тщательно разбросанные по коду нарушения соглашений с одной стороны не делают код явно плохим при первом взгляде, а с другой -- имеют в точности тот же, и даже лучший эффект, чем явное неследование им!
### Пример из jQuery
[warn header="jQuery / DOM"]
Этот пример требует знаний jQuery/DOM, если пока их у вас нет -- пропустите его, ничего страшного, но обязательно вернитесь к нему позже. Подобное стоит многих часов отладки.
[/warn]
Во фреймворке jQuery есть метод [wrap](http://api.jquery.com/wrap/), который обёртывает один элемент вокруг другого: Во фреймворке jQuery есть метод [wrap](http://api.jquery.com/wrap/), который обёртывает один элемент вокруг другого:
```js ```js
var img = $('<img/>'); // создали новые элементы (jQuery-синтаксис) var img = $('<img/>'); // создали новые элементы (jQuery-синтаксис)
var div = $('<div/>'); // и поместили в переменную var div = $('<div/>'); // и поместили в переменную
*!*
img.wrap(div); // обернуть img в div img.wrap(div); // обернуть img в div
*/!* div.append('<span/>');
``` ```
Результат кода выше -- два элемента, один вложен в другой: Результат кода после операции `wrap` -- два элемента, один вложен в другой:
```html ```html
<div> <div>
@ -54,66 +66,24 @@ img.wrap(div); // обернуть img в div
</div> </div>
``` ```
(`div` обернулся вокруг `img`) А что же после `append`?
А теперь, когда все расслабились и насладились этим замечательным методом... Можно предположить, что `<span/>` добавится в конец `div`, сразу после `img`... Но ничего подобного!
...Самое время ниндзя нанести свой удар! Искусный ниндзя уже нанёс свой удар и поведение кода стало неправильным, хотя разработчик об этом даже не подозревает.
**Как вы думаете, что будет, если добавить к коду выше строку:** Как правило, методы jQuery работают с теми элементами, которые им переданы. Но не здесь!
```js Внутри вызова `img.wrap(div)` происходит клонирование `div` и вокруг `img` оборачивается не сам `div`, а его клон. При этом исходная переменная `div` не меняется, в ней как был пустой `div`, так и остался.
//+ lines first-line=5
div.append('<span/>');
```
[smart header="jQuery-справка"] В итоге, после вызова получается два независимых `div'а`: первый содержит `img` (этот неявный клон никуда не присвоен), а второй -- наш `span`.
Вызов `elemA.append(elemB)` добавляет `elemB` в конец содержимого элемента `elemA`.
[/smart]
**Возможно, вы полагаете, что `<span/>` добавится в конец `div`, сразу после `img`?** Злая магия? Плохой феншуй?
А вот и нет! А вот и нет!.. Ничего подобного, просто избирательное следование соглашениям. Вызов `wrap` -- неявно клонирует элемент.
Оказывается, внутри вызова `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>
```html
<div>
<img/>
</div>
```
</td>
</tr>
</table>
Странно? Неочевидно? Да, и не только вам :)
Соглашение в данном случае -- в том, что большинство методов jQuery не клонируют элементы. А вызов `wrap` -- клонирует.
Код его истинный ниндзя писал!
## Краткость -- сестра таланта! ## Краткость -- сестра таланта!
@ -148,7 +118,7 @@ i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0;
Остановите свой взыскательный взгляд на чём-нибудь более экзотическом. Например, `x` или `y`. Остановите свой взыскательный взгляд на чём-нибудь более экзотическом. Например, `x` или `y`.
Эффективность этого подхода особенно заметна, если тело цикла занимает одну-две страницы. Эффективность этого подхода особенно заметна, если тело цикла занимает одну-две страницы (чем длиннее -- тем лучше).
В этом случае заметить, что переменная -- счетчик цикла, без пролистывания вверх, невозможно. В этом случае заметить, что переменная -- счетчик цикла, без пролистывания вверх, невозможно.
@ -281,7 +251,7 @@ function ninjaFunction(elem) {
var *!*user*/!* = authenticateUser(); var *!*user*/!* = authenticateUser();
function render() { 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. Например, функция `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 -- это три в одном: это И тесты И документация И примеры использования одновременно.** **Тесты BDD -- это три в одном: И тесты И документация И примеры использования одновременно.**
Впрочем, хватит слов. Рассмотрим примеры. Впрочем, хватит слов. Рассмотрим примеры.
## Разработка pow ## Разработка pow: спецификация
Допустим, мы хотим разработать функцию `pow(x, n)`, которая возводит `x` в целую степень `n`, для простоты `n≥0`. Допустим, мы хотим разработать функцию `pow(x, n)`, которая возводит `x` в целую степень `n`, для простоты `n≥0`.
### Спецификация
Ещё до разработки мы можем представить себе, что эта функция будет делать и описать это по методике BDD. Ещё до разработки мы можем представить себе, что эта функция будет делать и описать это по методике BDD.
@ -61,9 +58,15 @@ describe("pow", function() {
<dt>`assert.equal(value1, value2)`</dt> <dt>`assert.equal(value1, value2)`</dt>
<dd>Код внутри `it`, если реализация верна, должен выполняться без ошибок. <dd>Код внутри `it`, если реализация верна, должен выполняться без ошибок.
Для того, чтобы проверить, делает ли `pow` то, что задумано, используются функции вида `assert.*`. Пока что нас интересует только одна из них -- `assert.equal`, она сравнивает свой первый аргумент со вторым и выдаёт ошибку в случае, когда они не равны. Есть и другие виды сравнений и проверок, которые мы увидим далее.</dd> Различные функции вида `assert.*` используются, чтобы проверить, делает ли `pow` то, что задумано. Пока что нас интересует только одна из них -- `assert.equal`, она сравнивает свой первый аргумент со вторым и выдаёт ошибку в случае, когда они не равны. В данном случае она проверяет, что результат `pow(2, 3)` равен `8`.
Есть и другие виды сравнений и проверок, которые мы увидим далее.</dd>
</dl> </dl>
## Поток разработки
Как правило, поток разработки таков: Как правило, поток разработки таков:
<ol> <ol>
<li>Пишется спецификация, которая описывает самый базовый функционал.</li> <li>Пишется спецификация, которая описывает самый базовый функционал.</li>
@ -77,7 +80,7 @@ describe("pow", function() {
В нашем случае первый шаг уже завершён, начальная спецификация готова, хорошо бы приступить к реализации. Но перед этим проведём "нулевой" запуск спецификации, просто чтобы увидеть, что уже в таком виде, даже без реализации -- тесты работают. В нашем случае первый шаг уже завершён, начальная спецификация готова, хорошо бы приступить к реализации. Но перед этим проведём "нулевой" запуск спецификации, просто чтобы увидеть, что уже в таком виде, даже без реализации -- тесты работают.
### Проверка спецификации ## Пример в действии
Для запуска тестов нужны соответствующие JavaScript-библиотеки. Для запуска тестов нужны соответствующие JavaScript-библиотеки.
@ -96,22 +99,23 @@ describe("pow", function() {
<!--+ src="index.html" --> <!--+ src="index.html" -->
``` ```
Эту страницу можно условно разделить на три части: Эту страницу можно условно разделить на четыре части:
<ol> <ol>
<li>В `<head>` подключаем библиотеки и стили.</li> <li>Блок `<head>` -- в нём мы подключаем библиотеки и стили для тестирования, нашего кода там нет.</li>
<li>Подключаем `<script>` с реализацией, в нашем случае -- с кодом для `pow`. Пока что функции нет, мы лишь готовимся её написать.</li> <li>Блок `<script>` с реализацией спецификации, в нашем случае -- с кодом для `pow`.</li>
<li>Далее подключаются тесты, файл `test.js` содержит `describe("pow", ...)`, который был описан выше. Методы `describe` и `it` принадлежат библиотеке Mocha, так что важно, что она была подключена выше. Их вызов добавляет тесты, для запуска которых используется команда `mocha.run()`. Она выведет результат тестов в элемент с `id="mocha"`.</li> <li>Далее подключаются тесты, файл `test.js` содержит `describe("pow", ...)`, который был описан выше. Методы `describe` и `it` принадлежат библиотеке Mocha, так что важно, что она была подключена выше.</li>
<li>Элемент `<div id="mocha">` будет использоваться библиотекой Mocha для вывода результатов. Запуск тестов инициируется командой `mocha.run()`.</li>
</ol> </ol>
Результат срабатывания: Результат срабатывания:
[iframe height=250 src="pow-1" border=1 edit] [iframe height=250 src="pow-1" border=1 edit]
Пока что тесты не проходят, но это логично -- вместо функции стоит "заглушка", пустой код.
Пока что у нас одна функция и одна спецификация, но на будущее заметим, что если различных функций и тестов много -- это не проблема, можно их все подключить на одной странице. Конфликта не будет, так как каждый функционал имеет свой блок `describe("что тестируем"...)`. Сами же тесты обычно пишутся так, чтобы не влиять друг на друга, не делать лишних глобальных переменных. Пока что у нас одна функция и одна спецификация, но на будущее заметим, что если различных функций и тестов много -- это не проблема, можно их все подключить на одной странице. Конфликта не будет, так как каждый функционал имеет свой блок `describe("что тестируем"...)`. Сами же тесты обычно пишутся так, чтобы не влиять друг на друга, не делать лишних глобальных переменных.
Посмотрели, попробовали запустить у себя что-то подобное? Если да -- идём дальше. ## Начальная реализация
### Начальная реализация
Пока что, как видно, тесты не проходят, ошибка сразу же. Давайте сделаем минимальную реализацию `pow`, которая бы работала нормально: Пока что, как видно, тесты не проходят, ошибка сразу же. Давайте сделаем минимальную реализацию `pow`, которая бы работала нормально:
@ -125,7 +129,7 @@ function pow() {
[iframe height=250 src="pow-min" border=1 edit] [iframe height=250 src="pow-min" border=1 edit]
### Расширение спецификации ## Исправление спецификации
Функция, конечно, ещё не готова, но тесты проходят. Это ненадолго :) Функция, конечно, ещё не готова, но тесты проходят. Это ненадолго :)
@ -192,9 +196,9 @@ describe("pow", function() {
Как и следовало ожидать, второй тест не проходит. Ещё бы, ведь функция всё время возвращает `8`. Как и следовало ожидать, второй тест не проходит. Ещё бы, ведь функция всё время возвращает `8`.
### Уточнение реализации ## Уточнение реализации
Придётся написать нечто более реальное: Придётся написать нечто более реальное, чтобы тесты проходили:
```js ```js
function pow(x, n) { function pow(x, n) {
@ -231,7 +235,7 @@ describe("pow", function() {
[iframe height=250 src="pow-3" edit border="1"] [iframe height=250 src="pow-3" edit border="1"]
### Вложенный describe ## Вложенный describe
Функция `makeTest` и цикл `for`, очевидно, нужны друг другу, но не нужны для других тестов, которые мы добавим в дальнейшем. Они образуют единую группу, задача которой -- проверить возведение в `n`-ю степень. Функция `makeTest` и цикл `for`, очевидно, нужны друг другу, но не нужны для других тестов, которые мы добавим в дальнейшем. Они образуют единую группу, задача которой -- проверить возведение в `n`-ю степень.
@ -307,9 +311,9 @@ describe("Тест", function() {
Как правило, `beforeEach/afterEach` (`before/each`) используют, если необходимо произвести инициализацию, обнулить счётчики или сделать что-то ещё в таком духе между тестами (или их группами). Как правило, `beforeEach/afterEach` (`before/each`) используют, если необходимо произвести инициализацию, обнулить счётчики или сделать что-то ещё в таком духе между тестами (или их группами).
[/smart] [/smart]
### Расширение спецификации ## Расширение спецификации
Базовый функционал описан и реализован, первая итерация разработки завершена. Теперь расширим и уточним его. Базовый функционал `pow` описан и реализован, первая итерация разработки завершена. Теперь расширим и уточним его.
Как говорилось ранее, функция `pow(x, n)` предназначена для работы с целыми неотрицательными `n`. Как говорилось ранее, функция `pow(x, n)` предназначена для работы с целыми неотрицательными `n`.
@ -342,7 +346,7 @@ describe("pow", function() {
Конечно, новые тесты не проходят, так как наша реализация ещё не поддерживает их. Так и задумано: сначала написали заведомо не работающие тесты, а затем пишем реализацию под них. Конечно, новые тесты не проходят, так как наша реализация ещё не поддерживает их. Так и задумано: сначала написали заведомо не работающие тесты, а затем пишем реализацию под них.
### Другие assert ## Другие assert
Обратим внимание, в спецификации выше использована проверка не `assert.equal`, как раньше, а `assert(выражение)`. Такая проверка выдаёт ошибку, если значение выражения при приведении к логическому типу не `true`. Обратим внимание, в спецификации выше использована проверка не `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() { describe("Тест", function() {
before(function() { alert("Начало тестов"); }); before(function() { alert("Начало всех тестов"); });
after(function() { alert("Конец тестов"); }); after(function() { alert("Окончание всех тестов"); });
beforeEach(function() { alert("Вход в тест"); }); beforeEach(function() { alert("Вход в тест"); });
afterEach(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");
});
});