diff --git a/1-js/2-first-steps/15-switch/article.md b/1-js/2-first-steps/15-switch/article.md index b94a6f9e..023f542c 100644 --- a/1-js/2-first-steps/15-switch/article.md +++ b/1-js/2-first-steps/15-switch/article.md @@ -2,7 +2,7 @@ Конструкция `switch` заменяет собой сразу несколько `if`. -Это -- более наглядный способ сравнить выражение сразу с несколькими вариантами. +Она представляет собой более наглядный способ сравнить выражение сразу с несколькими вариантами. [cut] ## Синтаксис @@ -44,7 +44,7 @@ switch(x) { ```js //+ run -var a = 2+2; +var a = 2 + 2; switch (a) { case 3: @@ -63,11 +63,13 @@ switch (a) { } ``` -Будет выведено только одно значение, соответствующее `4`. После чего `break` прервёт выполнение. +Здесь оператор `switch` последовательно сравнит `a` со всеми вариантами из `case`. -**Если его не прервать -- оно пойдёт далее, при этом остальные проверки игнорируются.** +Сначала `3`, затем -- так как нет совпадения -- `4`. Совпадение найдено, будет выполнен этот вариант, со строки `alert('В точку!')` и далее, до ближайшего `break`, который прервёт выполнение. -Например: +**Если `break` нет, то выполнение пойдёт ниже по следующим `case`, при этом остальные проверки игнорируются.** + +Пример без `break`: ```js //+ run @@ -87,7 +89,7 @@ switch (a) { } ``` -В примере выше последовательно выполнятся три `alert`. +В примере выше последовательно выполнятся три `alert`: ```js alert('В точку!'); @@ -95,7 +97,7 @@ alert('Перебор'); alert('Я таких значений не знаю'); ``` -**В `case` могут быть любые выражения**, в том числе включающие в себя переменные и функции. +В `case` могут быть любые выражения, в том числе включающие в себя переменные и функции. Например: @@ -165,21 +167,19 @@ switch(arg) { case 3: alert('Никогда не выполнится'); - case null: - alert('Отмена'); - break; - default: alert('Неизвестное значение: ' + arg) } ``` -Что оно выведет при вводе чисел 0, 1, 2, 3? Подумайте и *потом* читайте дальше... +Что оно выведет при вводе числа 0? Числа 1? 2? 3? + +Подумайте, выпишите свои ответы, исходя из текущего понимания работы `switch` и *потом* читайте дальше... diff --git a/1-js/2-first-steps/16-function-basics/article.md b/1-js/2-first-steps/16-function-basics/article.md index c1322276..6b85b993 100644 --- a/1-js/2-first-steps/16-function-basics/article.md +++ b/1-js/2-first-steps/16-function-basics/article.md @@ -65,13 +65,14 @@ alert(message); // <-- будет ошибка, т.к. переменная ви ```js function count() { + // переменные i,j не будут уничтожены по окончании цикла for (*!*var*/!* i=0; i<3; i++) { *!*var*/!* j = i * 2; } *!* - alert(i); // i=3, на этом значении цикл остановился - alert(j); // j=4, последнее значение, на котором цикл сработал, было i=2 + alert(i); // i=3, последнее значение i, при нём цикл перестал работать + alert(j); // j=4, последнее значение j, которое вычислил цикл */!* } ``` @@ -114,13 +115,12 @@ showMessage(); // Привет, я Вася ```js //+ run -var *!*userName*/!* = 'Вася'; +var userName = 'Вася'; function showMessage() { -*!* userName = 'Петя'; // (1) присвоение во внешнюю переменную -*/!* - var message = 'Привет, я ' + *!*userName*/!*; + + var message = 'Привет, я ' + userName; alert(message); } @@ -133,13 +133,11 @@ alert(userName); // Петя, значение внешней переменно Конечно, если бы внутри функции, в строке `(1)`, была бы объявлена своя локальная переменная `var userName`, то все обращения использовали бы её, и внешняя переменная осталась бы неизменной. -[summary] **Переменные, объявленные на уровне всего скрипта, называют *"глобальными переменными"*.** -Делайте глобальными только те переменные, которые действительно имеют общее значение для вашего проекта. +В примере выше переменная `userName` -- глобальная. -Пусть каждая функция работает "в своей песочнице". -[/summary] +Делайте глобальными только те переменные, которые действительно имеют общее значение для вашего проекта, а нужные для решения конкретной задачи -- пусть будут локальными в соответствующей функции. [warn header="Внимание: неявное объявление глобальных переменных!"] @@ -167,7 +165,7 @@ alert(message); // Привет Здесь опасность даже не в автоматическом создании переменной, а в том, что глобальные переменные должны использоваться тогда, когда действительно нужны "общескриптовые" параметры. -Забыли `var` в одном месте, потом в другом -- в результате одна функция неожиданно поменяла глобальную переменную, которую использует другая. Возможна ошибка и потеря времени на поиск проблемы. +Забыли `var` в одном месте, потом в другом -- в результате одна функция неожиданно поменяла глобальную переменную, которую использует другая. И поди разберись, кто и когда её поменял, не самая приятная ошибка для отладки. [/warn] В будущем, когда мы лучше познакомимся с основами JavaScript, в главе [](/closures), мы более детально рассмотрим внутренние механизмы работы переменных и функций. @@ -201,7 +199,7 @@ showMessage('Маша', 'Как дела?'); //+ run function showMessage(from, text) { *!* - from = '**' + from + '**'; // меняем локальную переменную (1) + from = '**' + from + '**'; // меняем локальную переменную from */!* alert(from + ': ' + text); } @@ -210,32 +208,9 @@ var from = "Маша"; showMessage(from, "Привет"); -alert(from); // "Маша", без изменений, так как в строке (1) была изменена копия значения +alert(from); // старое значение from без изменений, в функции была изменена копия ``` -Здесь есть небольшая тонкость при работе с объектами. Как мы помним, в переменной хранится ссылка на объект. Поэтому функция, получив параметр-объект, работает с самим этим объектом: - -Например, в коде ниже функция по ссылке меняет содержимое объекта `user`: - -```js -//+ run -function makeAdmin(user) { - user.isAdmin = true; -} - -var user = { name: "Вася" }; - -makeAdmin(user); -alert(user.isAdmin); // true -``` - -## Стиль объявления функций - -В объявлении функции есть правила для расстановки пробелов. Они отмечены стрелочками: - - - -Конечно, вы можете ставить пробелы и по-другому, но эти правила используются в большинстве JavaScript-фреймворков. ## Аргументы по умолчанию Функцию можно вызвать с любым количеством аргументов. diff --git a/1-js/2-first-steps/18-function-declaration-expression/article.md b/1-js/2-first-steps/17-function-declaration-expression/article.md similarity index 67% rename from 1-js/2-first-steps/18-function-declaration-expression/article.md rename to 1-js/2-first-steps/17-function-declaration-expression/article.md index 564b90aa..18db5aeb 100644 --- a/1-js/2-first-steps/18-function-declaration-expression/article.md +++ b/1-js/2-first-steps/17-function-declaration-expression/article.md @@ -2,8 +2,6 @@ В JavaScript функция является значением, таким же как строка или число. -## Функция -- это значение - Как и любое значение, объявленную функцию можно вывести, вот так: ```js @@ -41,14 +39,16 @@ sayHi(); // ошибка (4)
  • ...Однако, в любой момент значение переменной можно поменять. При этом, если оно не функция, то вызов `(4)` выдаст ошибку.
  • -Обычные значения, такие как числа или строки, представляют собой *данные*. А функцию можно воспринимать как *действие*. Это действие, как правило, хранится в переменной, но его можно скопировать или переместить из неё. +Обычные значения, такие как числа или строки, представляют собой *данные*. А функцию можно воспринимать как *действие*. + +Это действие можно запустить через скобки `()`, а можно и скопировать в другую переменную, как было продемонстрировано выше. ## Объявление Function Expression [#function-expression] -Функцию можно создать и присвоить переменной в любом месте кода. +Существует альтернативный синтаксис для объявления функции, который ещё более наглядно показывает, что функция -- это всего лишь разновидность значения переменной. -Для этого используется объявление "Function Expression" (функциональное выражение), которое выглядит так: +Он называется "Function Expression" (функциональное выражение) и выглядит так: ```js //+ run @@ -73,8 +73,8 @@ sayHi('Вася'); "Классическое" объявление функции, о котором мы говорили до этого, вида `function имя(параметры) {...}`, называется в спецификации языка "Function Declaration". Несмотря на немного разный вид, по сути две эти записи делают одно и то же: @@ -93,13 +93,7 @@ var sum = function(a, b) { Оба этих объявления говорят интерпретатору: "объяви переменную `sum`, создай функцию с указанными параметрами и кодом и сохрани её в `sum`". -**При этом название переменной, в которую записана функция, обычно называют "именем функции". Говорят: "функция sum". Но при этом имя к функции никак не привязано.** - -Это всего лишь имя переменной, в которой *в данный момент* находится функция. - -Функция может быть в процессе работы скрипта скопирована в другую переменную, а из этой удалена, передана в другое место кода, и так далее, как мы видели выше. - -**Основное отличие между ними: функции, объявленные как Function Declaration, создаются интерпретатором *до выполнения кода*.** +**Основное отличие между ними: функции, объявленные как Function Declaration, создаются интерпретатором до выполнения кода.** Поэтому их можно вызвать *до* объявления, например: @@ -127,12 +121,13 @@ var sayHi = function(name) { } ``` -Это из-за того, что JavaScript перед запуском кода ищет в нём Function Declaration (они начинаются в основном потоке с `function`) и обрабатывает их. +Это из-за того, что JavaScript перед запуском кода ищет в нём Function Declaration (их легко найти: они не являются частью выражений и начинаются со слова `function`) и обрабатывает их. -А Function Expression создаются при выполнении выражения, в котором созданы, в данном случае -- функция будет создана при операции присваивания `sayHi = ...`. +А Function Expression создаются в процессе выполнении выражения, в котором созданы, в данном случае -- функция будет создана при операции присваивания `sayHi = function...` -**Как правило, возможность Function Declaration вызвать функцию до объявления -- это удобно, так как даёт больше свободы в том, как организовать свой код.** +Как правило, возможность Function Declaration вызвать функцию до объявления -- это удобно, так как даёт больше свободы в том, как организовать свой код. +Можно расположить функции внизу, а их вызов -- сверху или наоборот. ### Условное объявление функции [#bad-conditional-declaration] @@ -142,7 +137,7 @@ var sayHi = function(name) { ```js //+ run -var age = 20; +var age = +prompt("Сколько вам лет?", 20); if (age >= 18) { function sayHi() { alert('Прошу вас!'); } @@ -153,15 +148,7 @@ if (age >= 18) { sayHi(); ``` -[smart header="Зачем условное объявление?"] -Конечно, можно произвести проверку условия внутри функции. - -Но вынос проверки вовне даёт очевидный выигрыш в производительности в том случае, когда проверку достаточно произвести только один раз, и её результат никогда не изменится. - -Например, мы можем проверить, поддерживает ли браузер определённые современные возможности, и если да -- функция будет использовать их, а если нет -- реализовать её другим способом. При этом проверка будет осуществляться один раз, на этапе объявления функции, а не при каждом запуске функции. -[/smart] - -При запуске примера выше в любом браузере, кроме Firefox, мы увидим, что условное объявление не работает. Срабатывает `"До 18 нельзя"`, несмотря на то, что `age = 20`. +При вводе `20` в примере выше в любом браузере, кроме Firefox, мы увидим, что условное объявление не работает. Срабатывает `"До 18 нельзя"`, несмотря на то, что `age = 20`. В чём дело? Чтобы ответить на этот вопрос -- вспомним, как работают функции. @@ -231,55 +218,54 @@ sayHi(); Взглянем ещё на один пример. -Функция `test(f, yes, no)` получает три функции, вызывает первую и, в зависимости от её результата, вызывает вторую или третью: +Функция `ask(question, yes, no)` предназначена для выбора действия в зависимости от результата `f`. + +Она выводит вопрос на подтверждение `question` и, в зависимости от согласия пользователя, вызывает `yes` или `no`: ```js //+ run *!* -function test(f, yes, no) { - if (f()) yes() +function ask(question, yes, no) { + if (confirm(question)) yes() else no(); } */!* -// вспомогательные функции -function f1() { - return confirm("OK?"); -} - -function f2() { +function showOk() { alert("Вы согласились."); } -function f3() { +function showCancel() { alert("Вы отменили выполнение."); } // использование -test(f1, f2, f3); +ask("Вы согласны?", showOk, showCancel); ``` -В этом примере для нас, наверно, нет ничего нового. Подумаешь, объявили функции `f1`, `f2`, `f3`, передали их в качестве параметров другой функции (ведь функция -- обычное значение), вызвали те, которые нужны... +Какой-то очень простой код, не правда ли? Зачем, вообще, может понадобиться такая `ask`? -А вот то же самое, но более коротко: +...Но при работе со страницей такие функции как раз очень востребованы, только вот спрашивают они не простым `confirm`, а выводят более красивое окно с вопросом и могут интеллектуально обработать ввод посетителя. Но это всё в своё время. + +Здесь обратим внимание на то, что то же самое можно написать более коротко: ```js //+ run -function test(f, yes, no) { - if (f()) yes() +function ask(question, yes, no) { + if (confirm(question)) yes() else no(); } *!* -test( - function() { return confirm("OK?"); }, +ask( + "Вы согласны?", function() { alert("Вы согласились."); }, function() { alert("Вы отменили выполнение."); } ); */!* ``` -Здесь функции объявлены прямо внутри вызова `test(...)`, даже без присвоения им имени. +Здесь функции объявлены прямо внутри вызова `ask(...)`, даже без присвоения им имени. **Функциональное выражение, которое не записывается в переменную, называют [анонимной функцией](http://ru.wikipedia.org/wiki/%D0%90%D0%BD%D0%BE%D0%BD%D0%B8%D0%BC%D0%BD%D0%B0%D1%8F_%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D1%8F).** @@ -289,9 +275,9 @@ test( ## new Function -Существует ещё один способ создания функции, который используется очень редко. +Существует ещё один способ создания функции, который используется очень редко, но упомянем и его для полноты картины. -Он выглядит так: +Он позволяет создавать функцию полностью "на лету" из строки, вот так: ```js //+ run @@ -309,7 +295,9 @@ alert(result); // 3
    Код функции в виде строки.
    -Этот способ позволяет конструировать строку с кодом функции динамически, во время выполнения программы. Это, скорее, исключение, чем правило, но также бывает востребовано. Пример использования -- динамическая компиляция шаблонов на JavaScript, мы встретимся с ней позже, при работе с интерфейсами. +Таким образом можно конструировать функцию, код которой неизвестен на момент написания программы, но строка с ним генерируется или подгружается динамически во время её выполнения. + +Пример использования -- динамическая компиляция шаблонов на JavaScript, мы встретимся с ней позже, при работе с интерфейсами. ## Итого diff --git a/1-js/2-first-steps/17-recursion/1-sum-to/solution.md b/1-js/2-first-steps/18-recursion/1-sum-to/solution.md similarity index 100% rename from 1-js/2-first-steps/17-recursion/1-sum-to/solution.md rename to 1-js/2-first-steps/18-recursion/1-sum-to/solution.md diff --git a/1-js/2-first-steps/17-recursion/1-sum-to/task.md b/1-js/2-first-steps/18-recursion/1-sum-to/task.md similarity index 100% rename from 1-js/2-first-steps/17-recursion/1-sum-to/task.md rename to 1-js/2-first-steps/18-recursion/1-sum-to/task.md diff --git a/1-js/2-first-steps/17-recursion/2-factorial/solution.md b/1-js/2-first-steps/18-recursion/2-factorial/solution.md similarity index 100% rename from 1-js/2-first-steps/17-recursion/2-factorial/solution.md rename to 1-js/2-first-steps/18-recursion/2-factorial/solution.md diff --git a/1-js/2-first-steps/17-recursion/2-factorial/task.md b/1-js/2-first-steps/18-recursion/2-factorial/task.md similarity index 100% rename from 1-js/2-first-steps/17-recursion/2-factorial/task.md rename to 1-js/2-first-steps/18-recursion/2-factorial/task.md diff --git a/1-js/2-first-steps/17-recursion/3-fibonacci-numbers/solution.md b/1-js/2-first-steps/18-recursion/3-fibonacci-numbers/solution.md similarity index 100% rename from 1-js/2-first-steps/17-recursion/3-fibonacci-numbers/solution.md rename to 1-js/2-first-steps/18-recursion/3-fibonacci-numbers/solution.md diff --git a/1-js/2-first-steps/17-recursion/3-fibonacci-numbers/task.md b/1-js/2-first-steps/18-recursion/3-fibonacci-numbers/task.md similarity index 100% rename from 1-js/2-first-steps/17-recursion/3-fibonacci-numbers/task.md rename to 1-js/2-first-steps/18-recursion/3-fibonacci-numbers/task.md diff --git a/1-js/2-first-steps/17-recursion/article.md b/1-js/2-first-steps/18-recursion/article.md similarity index 51% rename from 1-js/2-first-steps/17-recursion/article.md rename to 1-js/2-first-steps/18-recursion/article.md index 7225e526..89ec2e62 100644 --- a/1-js/2-first-steps/17-recursion/article.md +++ b/1-js/2-first-steps/18-recursion/article.md @@ -1,44 +1,51 @@ # Рекурсия, стек -В коде функции могут вызывать другие функции для выполнения подзадач. Частный случай подвызова -- когда функция вызывает сама себя. Это называется *рекурсией*. +В коде функции могут вызывать другие функции для выполнения подзадач. + +Частный случай подвызова -- когда функция вызывает сама себя. Это называется *рекурсией*. + +Рекурсия используется для ситуаций, когда выполнение одной сложной задачи можно представить как некое действие в совокупности с решением той же задачи в более простом варианте. + +Сейчас мы посмотрим примеры. + +Рекурсия -- общая тема программирования, не относящаяся напрямую к JavaScript. Если вы разрабатывали на других языках или изучали программирование раньше в ВУЗе, то наверняка уже знаете, что это такое. + +Эта глава предназначена для читателей, которые пока с этой темой незнакомы и хотят лучше разобраться в том, как работают функции. -В этой главе мы рассмотрим, как рекурсия устроена изнутри, и как её можно использовать. [cut] -## Реализация pow(x, n) через рекурсию +## Степень pow(x, n) через рекурсию -Чтобы возвести `x` в натуральную степень `n` -- можно умножить его на себя `n` раз в цикле: +В качестве первого примера использования рекурсивных вызовов -- рассмотрим задачу возведения числа `x` в натуральную степень `n`. + +Её можно представить как совокупность более простого действия и более простой задачи того же типа вот так: ```js -function pow(x, n) { - var result = x; - for(var i=1; ixn = x * xn-1. -Ведь xn = x * xn-1, т.е. можно вынести один `x` из-под степени. Иначе говоря, значение функции `pow(x,n)` получается из `pow(x, n-1)` умножением на `x`. +Например, вычислим `pow(2, 4)`, последовательно переходя к более простой задаче: -Этот процесс можно продолжить. Например, вычислим `pow(2, 4)`: +
      +
    1. `pow(2, 4) = 2 * pow(2, 3)`
    2. +
    3. `pow(2, 3) = 2 * pow(2, 2)`
    4. +
    5. `pow(2, 2) = 2 * pow(2, 1)`
    6. +
    7. `pow(2, 1) = 2`
    8. +
    -```js -pow(2, 4) = 2 * pow(2, 3) = 2 * 2 * pow(2, 2) = 2 * 2 * 2 * pow(2, 1) = 2 * 2 * 2 * 2; -``` +На шаге 1 нам нужно вычислить `pow(2,3)`, поэтому мы делаем шаг 2, дальше нам нужно `pow(2,2)`, мы делаем шаг 3, затем шаг 4, и на нём уже можно остановиться, ведь очевидно, что результат возведения числа в степень 1 -- равен самому числу. -Процесс перехода от `n` к `n-1` останавливается на `n==1`, так как очевидно, что `pow(x,1) == x`. +Далее, имея результат на шаге 4, он подставляется обратно в шаг 3, затем имеем `pow(2,2)` -- подставляем в шаг 2 и на шаге 1 уже получаем результат. -Код для такого вычисления: +Этот алгоритм на JavaScript: ```js //+ run function pow(x, n) { - if (n != 1) { // пока n!=1 сводить вычисление pow(..n) к pow(..n-1) + if (n != 1) { // пока n != 1, сводить вычисление pow(x,n) к pow(x,n-1) return x * pow(x, n-1); } else { return x; @@ -48,13 +55,15 @@ function pow(x, n) { alert( pow(2, 3) ); // 8 ``` -Говорят, что "функция `pow` *рекурсивно вызывает сама себя*" при `n != 1`. +Говорят, что "функция `pow` *рекурсивно вызывает сама себя*" до `n == 1`. Значение, на котором рекурсия заканчивается называют *базисом рекурсии*. В примере выше базисом является `1`. -Общее количество вложенных вызовов называют *глубиной рекурсии*. В случае со степенью, всего будет `n` вызовов. Максимальная глубина рекурсии ограничена и составляет около `10000`, но это число зависит от конкретного интерпретатора JavaScript и может быть в 10 раз меньше. +Общее количество вложенных вызовов называют *глубиной рекурсии*. В случае со степенью, всего будет `n` вызовов. -**Рекурсию используют, когда вычисление функции можно свести к её более простому вызову, а его -- еще к более простому, и так далее, пока значение не станет очевидно.** +Максимальная глубина рекурсии в браузерах ограничена, точно можно рассчитывать на `10000` вложенных вызовов, но некоторые интерпретаторы допускают и больше. + +Итак, рекурсию используют, когда вычисление функции можно свести к её более простому вызову, а его -- еще к более простому, и так далее, пока значение не станет очевидно. ## Контекст выполнения, стек @@ -62,60 +71,39 @@ alert( pow(2, 3) ); // 8 **У каждого вызова функции есть свой "контекст выполнения" (execution context).** -Контекст выполнения -- это служебная информация, которая соответствует текущему запуску функции. Она включает в себя локальные переменные функции. +Контекст выполнения -- это служебная информация, которая соответствует текущему запуску функции. Она включает в себя локальные переменные функции и конкретное место в коде, на котором находится интерпретатор. -Например, для вызова: - -```js -//+ run -function pow(x, n) { - if (n != 1) { // пока n!=1 сводить вычисление pow(..n) к pow(..n-1) - return x * pow(x, n-1); - } else { - return x; - } -} - -*!* -alert( pow(2, 3) ); // (*) -*/!* -``` - -При запуске функции `pow` в строке `(*)` будет создан контекст выполнения, который будет хранить переменные `x = 2, n = 3`. Мы схематично обозначим его так: +Например, для вызова `pow(2, 3)` из примера выше будет создан контекст выполнения, который будет хранить переменные `x = 2, n = 3`. Мы схематично обозначим его так: Далее функция `pow` начинает выполняться. Вычисляется выражение `n != 1` -- оно равно `true`, ведь в текущем контексте `n=3`. Поэтому задействуется первая ветвь `if` : ```js - if (n != 1) { // пока n!=1 сводить вычисление pow(..n) к pow(..n-1) +function pow(x, n) { + if (n != 1) { // пока n != 1 сводить вычисление pow(x,n) к pow(x,n-1) *!* return x * pow(x, n-1); */!* } else { return x; } +} ``` Чтобы вычислить выражение `x * pow(x, n-1)`, требуется произвести запуск `pow` с новыми аргументами. -**При любом вложенном вызове JavaScript запоминает место, где он остановился в текущей функции в специальной внутренней структуре данных -- "стеке контекстов".** +**При любом вложенном вызове JavaScript запоминает текущий контекст выполнения в специальной внутренней структуре данных -- "стеке контекстов".** -Это как если бы мы куда-то ехали, но очень захотелось поесть. Можно остановиться у кафе, оставить машину, отойти, а потом, через некоторое время, вернуться к ней и продолжить дорогу. +Затем интерпретатор приступает к выполнению вложенного вызова. -Так и здесь -- мы запомним, где остановились в этой функции, пойдём выполним вложенный вызов, затем вернёмся и продолжим дорогу. +В данном случае вызывается та же `pow`, однако это абсолютно неважно. Для любых функций процесс одинаков. -**После того, как текущий контекст выполнения сохранён в стеке контекстов, JavaScript приступает к выполнению вложенного вызова.** +Для нового вызова создаётся свой контекст выполнения, и управление переходит в него, а когда он завершён -- старый контекст достаётся из стека и выполнение внешней функции возобновляется. -В данном случае вызывается та же `pow`, однако, это абсолютно неважно. Для любых функций процесс одинаков. - -**Создаётся новый контекст выполнения, и управление переходит в подвызов, а когда он завершён -- старый контекст достаётся из стека и выполнение внешней функции возобновляется.** - -## Разбор примера - -Разберём происходящее более подробно, начиная с вызова `(*)`: +Разберём происходящее с контекстами более подробно, начиная с вызова `(*)`: ```js //+ run @@ -137,26 +125,29 @@ alert( pow(2, 3) ); // (*)
    Запускается функция `pow`, с аргументами `x=2`, `n=3`. Эти переменные хранятся в контексте выполнения, схематично изображённом ниже: Выполнение в этом контексте продолжается, пока не встретит вложенный вызов в строке 3.
    `pow(2, 2)`
    -
    В строке `3` происходит вложенный вызов `pow` с аргументами `x=2`, `n=2`. Для этой функции создаётся новый текущий контекст (выделен красным), а предыдущий сохраняется в "стеке": +
    В строке `3` происходит вложенный вызов `pow` с аргументами `x=2`, `n=2`. Текущий контекст сохраняется в стеке, а для вложеннного вызова создаётся новый контекст (выделен жирным ниже): +Обратим внимание, что контекст включает в себя не только переменные, но и место в коде, так что когда вложенный вызов завершится -- можно будет легко вернуться назад. +Слово "строка" здесь условно, на самом деле, конечно, запомнено более точное место в цепочке команд.
    `pow(2, 1)`
    Опять вложенный вызов в строке `3`, на этот раз -- с аргументами `x=2`, `n=1`. Создаётся новый текущий контекст, предыдущий добавляется в стек: +На текущий момент в стеке уже два старых контекста.
    Выход из `pow(2, 1)`.
    При выполнении `pow(2, 1)`, в отличие от предыдущих запусков, выражение `n != 1` будет равно `false`, поэтому сработает вторая ветка `if..else`: @@ -176,36 +167,29 @@ function pow(x, n) { Здесь вложенных вызовов нет, так что функция заканчивает свою работу, возвращая `2`. Текущий контекст больше не нужен и удаляется из памяти, из стека восстанавливается предыдущий: Возобновляется обработка внешнего вызова `pow(2, 2)`.
    Выход из `pow(2, 2)`.
    ...И теперь уже `pow(2, 2)` может закончить свою работу, вернув `4`. Восстанавливается контекст предыдущего вызова: Возобновляется обработка внешнего вызова `pow(2, 3)`.
    -
    Выход из `pow(2, 3)`.
    Самый внешний вызов заканчивает свою работу, его результат: `pow(2, 3) = 8`.
    -Глубина рекурсии в данном случае составила: **3**. +Глубина рекурсии в данном случае составила: **3**. Как видно из иллюстраций выше, глубина рекурсии равна максимальному числу контекстов, одновременно хранимых в стеке. -[smart] -В самом конце, как и в самом начале, выполнение попадает во внешний код, который находится вне любых функций. - -Контекст, который соответствует самому внешнему коду, называют *"глобальный контекст"*. Естественно, он является начальной и конечной точкой любых вложенных подвызовов. -[/smart] - Обратим внимание на требования к памяти. Рекурсия приводит к хранению всех данных для неоконченных внешних вызовов в стеке, в данном случае это приводит к тому, что возведение в степень `n` хранит в памяти `n` различных контекстов. -Реализация степени через цикл гораздо более экономна: +Реализация возведения в степень через цикл гораздо более экономна: ```js function pow(x, n) { @@ -221,31 +205,17 @@ function pow(x, n) { **Любая рекурсия может быть переделана в цикл. Как правило, вариант с циклом будет эффективнее.** -...Но зачем тогда нужна рекурсия? Да просто затем, что рекурсивный код может быть гораздо проще и понятнее! - -Переделка в цикл может быть нетривиальной, особенно когда в функции, в зависимости от условий, используются разные рекурсивные подвызовы. - -В программировании мы в первую очередь стремимся сделать сложное простым, а повышенная производительность нужна... Лишь там, где она действительно нужна. Поэтому красивое рекурсивное решение во многих случаях лучше. - -Недостатки и преимущества рекурсии: - -[compare] --Требования к памяти. --Ограничена максимальная глубина стека. -+Краткость и простота кода. -[/compare] - +Но переделка рекурсии в цикл может быть нетривиальной, особенно когда в функции, в зависимости от условий, используются различные рекурсивные подвызовы, когда ветвление более сложное. ## Итого -Рекурсия -- это когда функция вызывает сама себя, с другими аргументами. +Рекурсия -- это когда функция вызывает сама себя, как правило, с другими аргументами. Существуют много областей применения рекурсивных вызовов. Здесь мы посмотрели на один из них -- решение задачи путём сведения её к более простой (с меньшими аргументами), но также рекурсия используется для работы с "естественно рекурсивными" структурами данных, такими как HTML-документы, для "глубокого" копирования сложных объектов. Есть и другие применения, с которыми мы встретимся по мере изучения JavaScript. -Здесь мы постарались рассмотреть происходящее достаточно подробно, однако, если пожелаете, допустимо временно забежать вперёд и открыть главу [](/debugging-chrome), с тем чтобы при помощи отладчика построчно пробежаться по коду и посмотреть стек. - +Здесь мы постарались рассмотреть происходящее достаточно подробно, однако, если пожелаете, допустимо временно забежать вперёд и открыть главу [](/debugging-chrome), с тем чтобы при помощи отладчика построчно пробежаться по коду и посмотреть стек на каждом шаге. Отладчик даёт к нему доступ. @@ -261,13 +231,13 @@ function pow(x, n) { float: left; clear: both; border: 1px solid black; - font-family: "PT Mono", monospace; + font-family: "Consolas", monospace; padding: 3px 5px; } .function-execution-context li:last-child { - color: red; + font-weight: bold; } [/head] \ No newline at end of file diff --git a/1-js/2-first-steps/19-named-function-expression/article.md b/1-js/2-first-steps/19-named-function-expression/article.md index 241c5bef..9d4de04e 100644 --- a/1-js/2-first-steps/19-named-function-expression/article.md +++ b/1-js/2-first-steps/19-named-function-expression/article.md @@ -1,26 +1,28 @@ # Именованные функциональные выражения -Обычно то, что называют "именем функции", на самом деле -- всего лишь имя переменной, в которую присвоена функция. К самой функции это "имя" никак не привязано. - -Однако, есть в JavaScript способ указать имя, действительно привязанное к функции. Оно называется "Named Function Expression" (сокращённо NFE) или, по-русски, *"именованное функциональное выражение"*. +Специально для работы с рекурсией в JavaScript существует особое расширение функциональных выражений, которое называется "Named Function Expression" (сокращённо NFE) или, по-русски, *"именованное функциональное выражение"*. + [cut] ## Named Function Expression [#functions-nfe] -Простейший пример NFE выглядит так: +Обычное функциональное выражение: +```js +var f = function (...) { /* тело функции */ }; +``` + +Именованное с именем `sayHi`: ```js var f = function *!*sayHi*/!*(...) { /* тело функции */ }; ``` -Проще говоря, NFE -- это `Function Expression` с дополнительным именем (в примере выше `sayHi`). +Что же это за имя, которое идёт в дополнение к `f`, и зачем оно? -Что же это за имя, которое идёт в дополнение к переменной `f`, и зачем оно? +Имя функционального выражения (`sayHi`) имеет особый смысл. Оно доступно только изнутри самой функции. -**Имя функционального выражения (`sayHi`) имеет особый смысл. Оно доступно только изнутри самой функции.** - -**Это ограничение видимости входит в стандарт JavaScript и поддерживается всеми браузерами, кроме IE8-.** +Это ограничение видимости входит в стандарт JavaScript и поддерживается всеми браузерами, кроме IE8-. Например: @@ -33,13 +35,13 @@ var f = function sayHi(name) { alert(sayHi); // снаружи - не видно (ошибка: undefined variable 'sayHi') ``` -**Кроме того, имя NFE нельзя перезаписать:** +Кроме того, имя NFE нельзя перезаписать: ```js //+ run var test = function sayHi(name) { *!* - sayHi = "тест"; // перезапись + sayHi = "тест"; // попытка перезаписи */!* alert(sayHi); // function... (перезапись не удалась) }; @@ -49,15 +51,7 @@ test(); В режиме `use strict` код выше выдал бы ошибку. -**Как правило, имя NFE используется для единственной цели -- позволить изнутри функции вызвать саму себя.** - -[smart header="Устаревшее специальное значение `arguments.callee`"] -Если вы работали с JavaScript, то, возможно, знаете, что для этой цели также служило специальное значение `arguments.callee`. - -Если это название вам ни о чём не говорит -- всё в порядке, читайте дальше, мы обязательно обсудим его [в отдельной главе](#arguments-callee). - -Если же вы в курсе, то стоит иметь в виду, что оно официально исключено из современного стандарта. А NFE -- это наше настоящее. -[/smart] +Как правило, имя NFE используется для единственной цели -- позволить изнутри функции вызвать саму себя. ## Пример использования @@ -130,7 +124,23 @@ alert(f === factorial); Все остальные браузеры полностью поддерживают именованные функциональные выражения. [/warn] + +[smart header="Устаревшее специальное значение `arguments.callee`"] +Если вы давно работаете с JavaScript, то, возможно, знаете, что раньше для этой цели также служило специальное значение `arguments.callee`. + +Если это название вам ни о чём не говорит -- всё в порядке, читайте дальше, мы обязательно обсудим его [в отдельной главе](#arguments-callee). + +Если же вы в курсе, то стоит иметь в виду, что оно официально исключено из современного стандарта. А NFE -- это наше настоящее. +[/smart] + + ## Итого -Если функция задана как Function Expression, её можно дать имя. Оно будет доступно только внутри функции (кроме IE8-) и предназначено для надёжного рекурсивного вызова функции, даже если она записана в другую переменную. +Если функция задана как Function Expression, ей можно дать имя. + +Оно будет доступно только внутри функции (кроме IE8-). + +Это имя предназначено для надёжного рекурсивного вызова функции, даже если она записана в другую переменную. + +Обратим внимание, что с Function Declaration так поступить нельзя. Такое "специальное" внутреннее имя функции задаётся только в синтаксисе Function Expression. diff --git a/1-js/2-first-steps/2-structure/article.md b/1-js/2-first-steps/2-structure/article.md index 2998d281..c0376ffc 100644 --- a/1-js/2-first-steps/2-structure/article.md +++ b/1-js/2-first-steps/2-structure/article.md @@ -23,6 +23,8 @@ alert('Привет'); alert('Мир'); ``` +## Точка с запятой [#semicolon] + Точку с запятой *во многих случаях* можно не ставить, если есть переход на новую строку. Так тоже будет работать: diff --git a/1-js/2-first-steps/20-javascript-specials/article.md b/1-js/2-first-steps/20-javascript-specials/article.md index 859e24da..3b875424 100644 --- a/1-js/2-first-steps/20-javascript-specials/article.md +++ b/1-js/2-first-steps/20-javascript-specials/article.md @@ -25,7 +25,7 @@ alert('Привет') alert('Мир') ``` -..Однако, иногда JavaScript не вставляет точку с запятой. Например: +...Однако, иногда JavaScript не вставляет точку с запятой. Например: ```js //+ run @@ -35,7 +35,16 @@ var a = 2 alert(a); // 5 ``` -Бывают случаи, когда это ведёт к ошибкам, которые достаточно трудно найти и исправить. Правила, когда точка с запятой ставится, а когда нет -- конечно, есть в спецификации языка, но запомнить их поначалу сложно, так как они неочевидны, придуманы "не для людей". +Бывают случаи, когда это ведёт к ошибкам, которые достаточно трудно найти и исправить, например: + +```js +//+ run +alert("После этого сообщения будет ошибка") + +[1, 2].forEach(alert) +``` + +Детали того, как работает код выше (массивы `[...]` и `forEach`) мы скоро изучим, здесь важно то, что при установке точки с запятой после `alert` он будет работать корректно. **Поэтому в JavaScript рекомендуется точки с запятой ставить. Сейчас это, фактически, общепринятый стандарт.** @@ -97,34 +106,6 @@ alert( x ); // undefined Подробнее: [](/variables), [](/types-intro). - -## Методы и свойства - -Все значения в JavaScript, за исключением `null` и `undefined`, содержат набор вспомогательных функций и значений, доступных "через точку". - -Такие функции называют "методами", а значения -- "свойствами". - -Например: - -```js -//+ run -alert( "Привет, мир!".length ); // 12 -``` - -Еще у строк есть *метод* `toUpperCase()`, который возвращает строку в верхнем регистре: - -```js -//+ run -var hello = "Привет, мир!"; - -*!* -alert( hello.toUpperCase() ); // "ПРИВЕТ, МИР!" -*/!* -``` - -Подробнее: [](/properties-and-methods). - - ## Строгий режим Для того, чтобы интерпретатор работал в режиме максимального соответствия современному стандарту, нужно начинать скрипт директивой `'use strict';` @@ -300,7 +281,7 @@ for(;;) { -Подробнее: [](/break-continue). +Подробнее: [](/while-for). ## Конструкция switch @@ -347,7 +328,7 @@ alert( sum(1, 2) ); // 3 +[smart header="Комментарии -- это важно"] Один из показателей хорошего разработчика -- качество комментариев, которые позволяют эффективно поддерживать код, возвращаться к нему после любой паузы и легко вносить изменения. - +[/smart] ## Руководства по стилю @@ -382,21 +410,23 @@ function pow(x, n) {
  • [Dojo Style Guide](http://dojotoolkit.org/community/styleGuide)
  • -Для того, чтобы начать разработку, вполне хватит элементов стилей, обозначенных в этой главе. В дальнейшем, посмотрите на эти руководства, найдите "свой" стиль ;) +Для того, чтобы начать разработку, вполне хватит элементов стилей, обозначенных в этой главе. В дальнейшем, посмотрев эти руководства, вы можете выработать и свой стиль, но лучше не делать его особенно "уникальным и неповторимым", себе дороже потом будет с людьми сотрудничать. ### Автоматизированные средства проверки -Существуют онлайн-сервисы, проверяющие стиль кода. +Существуют средства, проверяющие стиль кода. Самые известные -- это: -Все они также доступны в виде программ, которые можно скачать. +В частности, JSLint и JSHint интегрированы с большинством редакторов, они гибко настраиваются под нужный стиль и совершенно незаметно улучшают разработку, подсказывая, где и что поправить. + +Побочный эффект -- они видят некоторые ошибки, например необъявленные переменные. У меня это обычно результат опечатки, которые таким образом сразу отлавливаются. Очень рекомендую поставить что-то из этого. Я использую [JSHint](http://www.jshint.com/). ## Итого diff --git a/1-js/3-writing-js/2-coding-style/cheatsheet.png b/1-js/3-writing-js/2-coding-style/cheatsheet.png deleted file mode 100755 index ea150473..00000000 Binary files a/1-js/3-writing-js/2-coding-style/cheatsheet.png and /dev/null differ diff --git a/1-js/3-writing-js/2-coding-style/code-style.svg b/1-js/3-writing-js/2-coding-style/code-style.svg new file mode 100644 index 00000000..d59db930 --- /dev/null +++ b/1-js/3-writing-js/2-coding-style/code-style.svg @@ -0,0 +1,94 @@ + + + + Slice 1 + Created with Sketch. + + + + + + + + + + 2 + + + + + + Между именем функции + и скобкой + ( + нет пробела + + + + + Отступ + 2 пробела + + + + + Пробел после + for + + + + + } else { + без перевода строки + + + + + Пробелы вокруг + вложенного вызова + + + + + + + пустая строка + между + логическими блоками + + + + + длина строки + не более 80 символов + + + + + точка с запятой ; + обязательна + + + + + Фигурная скобка + { + + на той же строке, через пробел + + + + + Пробел между + параметрами + + + + + Пробел между + параметрами + + + + + \ No newline at end of file diff --git a/1-js/3-writing-js/2-coding-style/figure-bracket-style.svg b/1-js/3-writing-js/2-coding-style/figure-bracket-style.svg new file mode 100644 index 00000000..770d697d --- /dev/null +++ b/1-js/3-writing-js/2-coding-style/figure-bracket-style.svg @@ -0,0 +1,32 @@ + + + + Slice 1 + Created with Sketch. + + + + + + Плохо! + Фигурные скобки не имеют смысла + + + + + + + + + В одну строку без скобок - приемлемо, + если эта строка короткая + + + Самый лучший вариант + + + + + + + \ No newline at end of file diff --git a/1-js/3-writing-js/2-coding-style/figure.png b/1-js/3-writing-js/2-coding-style/figure.png deleted file mode 100755 index 5d2f1167..00000000 Binary files a/1-js/3-writing-js/2-coding-style/figure.png and /dev/null differ diff --git a/1-js/3-writing-js/3-write-unmain-code/article.md b/1-js/3-writing-js/3-write-unmain-code/article.md index 9bee2b50..06fa507c 100644 --- a/1-js/3-writing-js/3-write-unmain-code/article.md +++ b/1-js/3-writing-js/3-write-unmain-code/article.md @@ -1,18 +1,26 @@ # Как писать неподдерживаемый код? +[warn header="Познай свой код"] +Эта статья представляет собой мой вольный перевод [How To Write Unmaintainable Code](http://mindprod.com/jgloss/unmain.html) ("как писать неподдерживаемый код") с дополнениями, актуальными для JavaScript. + +Возможно, в каких-то из этих советов вам даже удастся узнать "этого парня в зеркале". +[/warn] + + Предлагаю вашему вниманию советы мастеров древности, следование которым создаст дополнительные рабочие места для JavaScript-разработчиков. Если вы будете им следовать, то ваш код будет так сложен в поддержке, что у JavaScript'еров, которые придут после вас, даже простейшее изменение займет годы *оплачиваемого* труда! А сложные задачи оплачиваются хорошо, так что они, определённо, скажут вам "Спасибо". Более того, *внимательно* следуя этим правилам, вы сохраните и своё рабочее место, так как все будут бояться вашего кода и бежать от него... -...Впрочем, всему своя мера. При написании такого кода он не должен *выглядеть* сложным в поддержке, код должен *быть* таковым. Явно кривой код может написать любой дурак. Это заметят, и вас уволят, а код будет переписан с нуля. Вы не можете такого допустить. Эти советы учитывают такую возможность. Да здравствует дзен. +...Впрочем, всему своя мера. При написании такого кода он не должен *выглядеть* сложным в поддержке, код должен *быть* таковым. + +Явно кривой код может написать любой дурак. Это заметят, и вас уволят, а код будет переписан с нуля. Вы не можете такого допустить. Эти советы учитывают такую возможность. Да здравствует дзен. -Статья представляет собой мой вольный перевод [How To Write Unmaintainable Code](http://mindprod.com/jgloss/unmain.html) с дополнениями, актуальными для JavaScript. [cut] -## Соглашения +## Соглашения -- по настроению [quote author="Сериал \"Симпсоны\", серия Helter Shelter"] Рабочий-чистильщик осматривает дом:
    @@ -31,22 +39,26 @@ Как затруднить задачу? Можно везде нарушать соглашения -- это помешает ему, но такое могут заметить, и код будет переписан. Как поступил бы ниндзя на вашем месте? -**...Правильно! Следуйте соглашениям "в общем", но иногда -- нарушайте их.** Тщательно разбросанные по коду нарушения соглашений с одной стороны не делают код явно плохим при первом взгляде, а с другой -- имеют в точности тот же, и даже лучший эффект, чем явное неследование им! +**...Правильно! Следуйте соглашениям "в общем", но иногда -- нарушайте их.** -Если пример, который я приведу ниже, пока сложноват -- пропустите его, но обязательно вернитесь к нему позже. Поверьте, это стоит того. +Тщательно разбросанные по коду нарушения соглашений с одной стороны не делают код явно плохим при первом взгляде, а с другой -- имеют в точности тот же, и даже лучший эффект, чем явное неследование им! +### Пример из jQuery + +[warn header="jQuery / DOM"] +Этот пример требует знаний jQuery/DOM, если пока их у вас нет -- пропустите его, ничего страшного, но обязательно вернитесь к нему позже. Подобное стоит многих часов отладки. +[/warn] Во фреймворке jQuery есть метод [wrap](http://api.jquery.com/wrap/), который обёртывает один элемент вокруг другого: ```js var img = $(''); // создали новые элементы (jQuery-синтаксис) var div = $('
    '); // и поместили в переменную -*!* img.wrap(div); // обернуть img в div -*/!* +div.append(''); ``` -Результат кода выше -- два элемента, один вложен в другой: +Результат кода после операции `wrap` -- два элемента, один вложен в другой: ```html
    @@ -54,66 +66,24 @@ img.wrap(div); // обернуть img в div
    ``` -(`div` обернулся вокруг `img`) +А что же после `append`? -А теперь, когда все расслабились и насладились этим замечательным методом... +Можно предположить, что `` добавится в конец `div`, сразу после `img`... Но ничего подобного! -...Самое время ниндзя нанести свой удар! +Искусный ниндзя уже нанёс свой удар и поведение кода стало неправильным, хотя разработчик об этом даже не подозревает. -**Как вы думаете, что будет, если добавить к коду выше строку:** +Как правило, методы jQuery работают с теми элементами, которые им переданы. Но не здесь! -```js -//+ lines first-line=5 -div.append(''); -``` +Внутри вызова `img.wrap(div)` происходит клонирование `div` и вокруг `img` оборачивается не сам `div`, а его клон. При этом исходная переменная `div` не меняется, в ней как был пустой `div`, так и остался. -[smart header="jQuery-справка"] -Вызов `elemA.append(elemB)` добавляет `elemB` в конец содержимого элемента `elemA`. -[/smart] +В итоге, после вызова получается два независимых `div'а`: первый содержит `img` (этот неявный клон никуда не присвоен), а второй -- наш `span`. -**Возможно, вы полагаете, что `` добавится в конец `div`, сразу после `img`?** +Злая магия? Плохой феншуй? -А вот и нет! А вот и нет!.. +Ничего подобного, просто избирательное следование соглашениям. Вызов `wrap` -- неявно клонирует элемент. -Оказывается, внутри вызова `img.wrap(div)` происходит *клонирование* `div`. И вокруг `img` оборачивается не сам `div`, а его злой клон. +Такой сюрприз, бесспорно, стоит многих часов отладки. -При этом исходная переменная `div` не меняется, в ней как был пустой `div`, так и остался. В итоге, после применения к нему `append` получается два `div'а`: один обёрнут вокруг `span`, а в другом -- только `img`. - - - - - - - - - - - - -
    Переменная `div`Клон `div`, созданный `wrap` - (не присвоен никакой переменной)
    - -```html -
    - -
    -``` - -
    - -```html -
    - -
    -``` - -
    - -Странно? Неочевидно? Да, и не только вам :) - -Соглашение в данном случае -- в том, что большинство методов jQuery не клонируют элементы. А вызов `wrap` -- клонирует. - -Код его истинный ниндзя писал! ## Краткость -- сестра таланта! @@ -148,7 +118,7 @@ i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0; Остановите свой взыскательный взгляд на чём-нибудь более экзотическом. Например, `x` или `y`. -Эффективность этого подхода особенно заметна, если тело цикла занимает одну-две страницы. +Эффективность этого подхода особенно заметна, если тело цикла занимает одну-две страницы (чем длиннее -- тем лучше). В этом случае заметить, что переменная -- счетчик цикла, без пролистывания вверх, невозможно. @@ -281,7 +251,7 @@ function ninjaFunction(elem) { var *!*user*/!* = authenticateUser(); function render() { - var *!*user*/!* = ... + var *!*user*/!* = anotherValue(); ... ...многобукв... ... @@ -290,7 +260,7 @@ function render() { } ``` -Зашедший в середину метода `render` программист, скорее всего, не заметит, что переменная `user` "уже не та" и использует её... Ловушка захлопнулась! Здравствуй, отладчик. +Зашедший в середину метода `render` программист, скорее всего, не заметит, что переменная `user` локально перекрыта и попытается работать с ней, полагая, что это результат `authenticateUser()`... Ловушка захлопнулась! Здравствуй, отладчик. ## Мощные функции! @@ -298,7 +268,9 @@ function render() { Например, функция `validateEmail(email)` может, кроме проверки e-mail на правильность, выводить сообщение об ошибке и просить заново ввести e-mail. -**Выберите хотя бы пару дополнительных действий, кроме основного назначения функции.** Главное -- они должны быть неочевидны из названия функции. Истинный ниндзя-девелопер сделает так, что они будут неочевидны и из кода тоже. +**Выберите хотя бы пару дополнительных действий, кроме основного назначения функции.** + +Главное -- они должны быть неочевидны из названия функции. Истинный ниндзя-девелопер сделает так, что они будут неочевидны и из кода тоже. **Объединение нескольких смежных действий в одну функцию защитит ваш код от повторного использования.** @@ -315,9 +287,9 @@ function render() { **Ещё одна вариация такого подхода -- возвращать нестандартное значение.** -Ведь общеизвестно, что `is..` и `check..` обычно возвращают `true/false`. Продемонстрируйте оригинальное мышление. Пусть вызов `checkPermission` возвращает не результат `true/false`, а объект -- с результатами проверки! А что, полезно. +Ведь общеизвестно, что `is..` и `check..` обычно возвращают `true/false`. Продемонстрируйте оригинальное мышление. Пусть вызов `checkPermission` возвращает не результат `true/false`, а объект с результатами проверки! А чего, полезно. -Те разработчики, кто попытается написать проверку `if (checkPermission(..))`, будут весьма удивлены результатом. Ответьте им: "надо читать документацию!". И перешлите эту статью. +Те же разработчики, кто попытается написать проверку `if (checkPermission(..))`, будут весьма удивлены результатом. Ответьте им: "надо читать документацию!". И перешлите эту статью. ## Заключение diff --git a/1-js/3-writing-js/4-testing/article.md b/1-js/3-writing-js/4-testing/article.md index 164607ab..a5721a71 100644 --- a/1-js/3-writing-js/4-testing/article.md +++ b/1-js/3-writing-js/4-testing/article.md @@ -8,13 +8,11 @@ При написании функции мы обычно представляем, что она должна делать, какое значение -- на каких аргументах выдавать. -В процессе разработки мы, время от времени, проверяем функцию. Самый простой способ проверки -- это запустить функцию и посмотреть результат. +В процессе разработки мы, время от времени, проверяем, правильно ли работает функция. Самый простой способ проверить -- это запустить её, например, в консоли, и посмотреть результат. -Потом написать ещё код, попробовать запустить -- опять посмотреть результат. +Если что-то не так -- поправить, опять запустить -- посмотреть результат... И так -- "до победного конца". -И так -- "до победного конца". - -К сожалению, такие ручные запуски -- очень несовершенное средство проверки. +Но такие ручные запуски -- очень несовершенное средство проверки. **Когда проверяешь работу кода вручную -- легко его "недотестировать".** @@ -28,15 +26,14 @@ BDD -- это не просто тесты. Это гораздо больше. -**Тесты BDD -- это три в одном: это И тесты И документация И примеры использования одновременно.** +**Тесты BDD -- это три в одном: И тесты И документация И примеры использования одновременно.** Впрочем, хватит слов. Рассмотрим примеры. -## Разработка pow +## Разработка pow: спецификация Допустим, мы хотим разработать функцию `pow(x, n)`, которая возводит `x` в целую степень `n`, для простоты `n≥0`. -### Спецификация Ещё до разработки мы можем представить себе, что эта функция будет делать и описать это по методике BDD. @@ -61,9 +58,15 @@ describe("pow", function() {
    `assert.equal(value1, value2)`
    Код внутри `it`, если реализация верна, должен выполняться без ошибок. -Для того, чтобы проверить, делает ли `pow` то, что задумано, используются функции вида `assert.*`. Пока что нас интересует только одна из них -- `assert.equal`, она сравнивает свой первый аргумент со вторым и выдаёт ошибку в случае, когда они не равны. Есть и другие виды сравнений и проверок, которые мы увидим далее.
    +Различные функции вида `assert.*` используются, чтобы проверить, делает ли `pow` то, что задумано. Пока что нас интересует только одна из них -- `assert.equal`, она сравнивает свой первый аргумент со вторым и выдаёт ошибку в случае, когда они не равны. В данном случае она проверяет, что результат `pow(2, 3)` равен `8`. + + +Есть и другие виды сравнений и проверок, которые мы увидим далее. + +## Поток разработки + Как правило, поток разработки таков:
    1. Пишется спецификация, которая описывает самый базовый функционал.
    2. @@ -77,7 +80,7 @@ describe("pow", function() { В нашем случае первый шаг уже завершён, начальная спецификация готова, хорошо бы приступить к реализации. Но перед этим проведём "нулевой" запуск спецификации, просто чтобы увидеть, что уже в таком виде, даже без реализации -- тесты работают. -### Проверка спецификации +## Пример в действии Для запуска тестов нужны соответствующие JavaScript-библиотеки. @@ -96,22 +99,23 @@ describe("pow", function() { ``` -Эту страницу можно условно разделить на три части: +Эту страницу можно условно разделить на четыре части:
        -
      1. В `` подключаем библиотеки и стили.
      2. -
      3. Подключаем `