0 added
0 removed
Original
2026-01-01
Modified
2026-02-26
1
<p>По своему опыту могу сказать, что возврат функций из функций вызывает наибольшие сложности у новичков. И дело даже не в том, что возврат сложен сам по себе, а в том, что поначалу очень сложно понять, зачем это может понадобиться. В реальной жизни эта техника используется часто, причем как в JavaScript, так и во многих других языках. Функции, принимающие на вход функции, которые возвращают функции - обычное дело для любого кода на JavaScript.</p>
1
<p>По своему опыту могу сказать, что возврат функций из функций вызывает наибольшие сложности у новичков. И дело даже не в том, что возврат сложен сам по себе, а в том, что поначалу очень сложно понять, зачем это может понадобиться. В реальной жизни эта техника используется часто, причем как в JavaScript, так и во многих других языках. Функции, принимающие на вход функции, которые возвращают функции - обычное дело для любого кода на JavaScript.</p>
2
<p>Для закрепления материала пройдитесь по нему два раза. Первый раз просто бегло прочитайте, второй раз изучите внимательно, проверяя каждую строчку кода.</p>
2
<p>Для закрепления материала пройдитесь по нему два раза. Первый раз просто бегло прочитайте, второй раз изучите внимательно, проверяя каждую строчку кода.</p>
3
<p>Начнем погружение с уже пройденного материала:</p>
3
<p>Начнем погружение с уже пройденного материала:</p>
4
<p>Функции - это такие же данные, как числа или строки, поэтому функции можно передавать в другие функции в виде аргументов, а также возвращать из функций. Мы даже можем определить функцию внутри другой функции и вернуть ее наружу. И в этом нет ничего удивительного. Константы можно создавать где угодно.</p>
4
<p>Функции - это такие же данные, как числа или строки, поэтому функции можно передавать в другие функции в виде аргументов, а также возвращать из функций. Мы даже можем определить функцию внутри другой функции и вернуть ее наружу. И в этом нет ничего удивительного. Константы можно создавать где угодно.</p>
5
<p>Можно даже обойтись без промежуточного создания константы:</p>
5
<p>Можно даже обойтись без промежуточного создания константы:</p>
6
<p>Всегда, когда видите подобные вызовы f()()(), знайте:<strong>функции возвращаются</strong>!</p>
6
<p>Всегда, когда видите подобные вызовы f()()(), знайте:<strong>функции возвращаются</strong>!</p>
7
<p>Теперь посмотрим, как еще можно описать функцию generateSumFinder:</p>
7
<p>Теперь посмотрим, как еще можно описать функцию generateSumFinder:</p>
8
<p>Для понятности можно расставить скобки:</p>
8
<p>Для понятности можно расставить скобки:</p>
9
<p>Определение функции обладает правой ассоциативностью. Все, что находится справа от =>, считается<em>телом функции</em>. Количество вложений никак не ограничено. Вполне можно встретить и такие варианты:</p>
9
<p>Определение функции обладает правой ассоциативностью. Все, что находится справа от =>, считается<em>телом функции</em>. Количество вложений никак не ограничено. Вполне можно встретить и такие варианты:</p>
10
<p>Ту же функцию можно представить другим способом, вынеся каждую функцию в свою собственную константу. Этот способ полезен как мысленный эксперимент, чтобы понять, где заканчивается одна и начинается другая функция. Но сама по себе она не заработает, потому что теряется замыкание.</p>
10
<p>Ту же функцию можно представить другим способом, вынеся каждую функцию в свою собственную константу. Этот способ полезен как мысленный эксперимент, чтобы понять, где заканчивается одна и начинается другая функция. Но сама по себе она не заработает, потому что теряется замыкание.</p>
11
<p>Попробуем последовательно пройтись по вызовам функции выше, чтобы понять, как получается результат. После каждого вызова (кроме последнего) возвращается новая функция, в которую подставлено значение из внешней функции за счет замыкания.</p>
11
<p>Попробуем последовательно пройтись по вызовам функции выше, чтобы понять, как получается результат. После каждого вызова (кроме последнего) возвращается новая функция, в которую подставлено значение из внешней функции за счет замыкания.</p>
12
<p>Как видно выше, sum1, sum2 и sum3 - это функции, а sum4 уже число, так как были вызваны все внутренние функции.</p>
12
<p>Как видно выше, sum1, sum2 и sum3 - это функции, а sum4 уже число, так как были вызваны все внутренние функции.</p>
13
<p>Давайте распишем все функции:</p>
13
<p>Давайте распишем все функции:</p>
14
<ul><li>Функция sum принимает x и возвращает функцию, которая<ul><li>принимает y и возвращает функцию, которая<ul><li>принимает z и возвращает сумму x + y + z</li>
14
<ul><li>Функция sum принимает x и возвращает функцию, которая<ul><li>принимает y и возвращает функцию, которая<ul><li>принимает z и возвращает сумму x + y + z</li>
15
</ul></li>
15
</ul></li>
16
</ul></li>
16
</ul></li>
17
</ul><p>Попробуем развить идею функции callTwice из предыдущего урока. Напишем функцию generate, которая не применяет функцию сразу, а генерирует новую.</p>
17
</ul><p>Попробуем развить идею функции callTwice из предыдущего урока. Напишем функцию generate, которая не применяет функцию сразу, а генерирует новую.</p>
18
<p>Функция generate принимает функцию в качестве аргумента и возвращает новую функцию. Внутри новой функции переданная изначально функция вызывается два раза:</p>
18
<p>Функция generate принимает функцию в качестве аргумента и возвращает новую функцию. Внутри новой функции переданная изначально функция вызывается два раза:</p>
19
<p>Создадим функцию f1. Она будет той функцией, которую вернет generate если передать ей функцию Math.sqrt (она вычисляет квадратный корень числа).</p>
19
<p>Создадим функцию f1. Она будет той функцией, которую вернет generate если передать ей функцию Math.sqrt (она вычисляет квадратный корень числа).</p>
20
<p>Получается, f1 - это функция, которая принимает число и возвращает корень корня - Math.sqrt(Math.sqrt(x)):</p>
20
<p>Получается, f1 - это функция, которая принимает число и возвращает корень корня - Math.sqrt(Math.sqrt(x)):</p>
21
<p>Еще пример: передадим в функцию generate новую функцию на ходу, без предварительного создания. Переданная функция возводит число в квадрат.</p>
21
<p>Еще пример: передадим в функцию generate новую функцию на ходу, без предварительного создания. Переданная функция возводит число в квадрат.</p>
22
<p>Теперь функция f2 возводит число в квадрат два раза: (42)2.</p>
22
<p>Теперь функция f2 возводит число в квадрат два раза: (42)2.</p>
23
<p>Функция generate имеет такое имя не просто так. Дело в том, что возврат функции порождает каждый раз новую функцию при каждом вызове, даже если тела этих функций совпадают:</p>
23
<p>Функция generate имеет такое имя не просто так. Дело в том, что возврат функции порождает каждый раз новую функцию при каждом вызове, даже если тела этих функций совпадают:</p>
24
<p>Поэтому про любую функцию, которая возвращает функцию можно сказать что она генерирует функцию. Запомнить довольно просто, если вы где-то слышите или читаете что происходит генерация функций, значит кто-то их возвращает.</p>
24
<p>Поэтому про любую функцию, которая возвращает функцию можно сказать что она генерирует функцию. Запомнить довольно просто, если вы где-то слышите или читаете что происходит генерация функций, значит кто-то их возвращает.</p>
25
<h2>Замыкание</h2>
25
<h2>Замыкание</h2>
26
<p>Работа практически всех описанных примеров базировалась на одном интересном свойстве, которое называется "замыкание". О нем говорилось в предыдущем курсе, но пришло время освежить память.</p>
26
<p>Работа практически всех описанных примеров базировалась на одном интересном свойстве, которое называется "замыкание". О нем говорилось в предыдущем курсе, но пришло время освежить память.</p>
27
<p>Когда generateDouble закончила работу и вернула новую функцию, экземпляр функции generateDouble исчез, уничтожился вместе с используемыми внутри аргументами.</p>
27
<p>Когда generateDouble закончила работу и вернула новую функцию, экземпляр функции generateDouble исчез, уничтожился вместе с используемыми внутри аргументами.</p>
28
<p>Но та функция, которую вернула generateDouble, все еще использует аргумент. В обычных условиях он бы навсегда исчез, но тут он "запомнился" или "замкнулся" внутри возвращенной функции. Технически внутренняя функция, как и любая другая в JavaScript, связана со своим лексическим окружением, которое не пропадает, даже если функция покидает это окружение.</p>
28
<p>Но та функция, которую вернула generateDouble, все еще использует аргумент. В обычных условиях он бы навсегда исчез, но тут он "запомнился" или "замкнулся" внутри возвращенной функции. Технически внутренняя функция, как и любая другая в JavaScript, связана со своим лексическим окружением, которое не пропадает, даже если функция покидает это окружение.</p>
29
<p>Функция, которая была возвращена из generateDouble, называется<strong>замыканием</strong>. Замыкание - это функция, "запомнившая" часть окружения, где она была задана. Функция замыкает в себе идентификаторы (все, что мы определяем) из лексической области видимости.</p>
29
<p>Функция, которая была возвращена из generateDouble, называется<strong>замыканием</strong>. Замыкание - это функция, "запомнившая" часть окружения, где она была задана. Функция замыкает в себе идентификаторы (все, что мы определяем) из лексической области видимости.</p>
30
<p>В СИКП дается прекрасный пример на понимание замыканий. Представьте себе, что мы проектируем систему, в которой нужно запомнить пароль пользователя, а потом проверять его, когда пользователь будет заново заходить. Можно смоделировать функцию savePassword, которая принимает на вход пароль и возвращает предикат, то есть функцию, возвращающую true или false, для его проверки. Посмотрите, как это выглядит:</p>
30
<p>В СИКП дается прекрасный пример на понимание замыканий. Представьте себе, что мы проектируем систему, в которой нужно запомнить пароль пользователя, а потом проверять его, когда пользователь будет заново заходить. Можно смоделировать функцию savePassword, которая принимает на вход пароль и возвращает предикат, то есть функцию, возвращающую true или false, для его проверки. Посмотрите, как это выглядит:</p>
31
<p>А вот как выглядит код функции savePassword:</p>
31
<p>А вот как выглядит код функции savePassword:</p>
32
<h2>Возврат функций в реальном мире (Debug)</h2>
32
<h2>Возврат функций в реальном мире (Debug)</h2>
33
<p>Логгирование - неотъемлемая часть разработки. Для понимания того, что происходит внутри кода, используют специальные библиотеки, с помощью которых можно логгировать (выводить) информацию о проходящих внутри процессах, например в файл. Типичный лог веб-сервера, обрабатывающего HTTP-запросы выглядит так:</p>
33
<p>Логгирование - неотъемлемая часть разработки. Для понимания того, что происходит внутри кода, используют специальные библиотеки, с помощью которых можно логгировать (выводить) информацию о проходящих внутри процессах, например в файл. Типичный лог веб-сервера, обрабатывающего HTTP-запросы выглядит так:</p>
34
<p>[ DEBUG] [2015-11-19 19:02:30.836222] accept: HTTP/1.1 GET - / - 200, 4238 [ INFO] [2015-11-19 19:02:32.106331] config: server has reload its config in 200 ms [WARNING] [2015-11-19 19:03:12.176262] accept: HTTP/1.1 GET - /info - 404, 829 [ ERROR] [2015-11-19 19:03:12.002127] accept: HTTP/1.1 GET - /info - 503, 829</p>
34
<p>[ DEBUG] [2015-11-19 19:02:30.836222] accept: HTTP/1.1 GET - / - 200, 4238 [ INFO] [2015-11-19 19:02:32.106331] config: server has reload its config in 200 ms [WARNING] [2015-11-19 19:03:12.176262] accept: HTTP/1.1 GET - /info - 404, 829 [ ERROR] [2015-11-19 19:03:12.002127] accept: HTTP/1.1 GET - /info - 503, 829</p>
35
<p>В JavaScript самой популярной библиотекой для логгирования считается<a>Debug</a>. Вот как выглядит ее вывод:</p>
35
<p>В JavaScript самой популярной библиотекой для логгирования считается<a>Debug</a>. Вот как выглядит ее вывод:</p>
36
<p>Обратите внимание на левую часть каждой строки. Debug для каждой выводимой строчки использует так называемый неймспейс, некоторую строчку, которая указывает принадлежность выводимой строчки к определенной подсистеме или части кода. Он используется для фильтрации, когда логов становится много. Другими словами, можно указать "выводи сообщения только для http". А вот как это работает:</p>
36
<p>Обратите внимание на левую часть каждой строки. Debug для каждой выводимой строчки использует так называемый неймспейс, некоторую строчку, которая указывает принадлежность выводимой строчки к определенной подсистеме или части кода. Он используется для фильтрации, когда логов становится много. Другими словами, можно указать "выводи сообщения только для http". А вот как это работает:</p>
37
<p>Что приведет к такому выводу:</p>
37
<p>Что приведет к такому выводу:</p>
38
<p>http hello! +0ms http i am from http +2ms handler hello from handler! +0ms handler i am from handler +1ms</p>
38
<p>http hello! +0ms http i am from http +2ms handler hello from handler! +0ms handler i am from handler +1ms</p>
39
<p>Получается, что импортированный debug - это функция, которая принимает на вход неймспейс в виде строки и возвращает другую функцию, которая уже используется для логгирования.</p>
39
<p>Получается, что импортированный debug - это функция, которая принимает на вход неймспейс в виде строки и возвращает другую функцию, которая уже используется для логгирования.</p>