HTML Diff
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>Определение функции обладает правой ассоциативностью. Все, что находится справа от =&gt;, считается<em>телом функции</em>. Количество вложений никак не ограничено. Вполне можно встретить и такие варианты:</p>
9 <p>Определение функции обладает правой ассоциативностью. Все, что находится справа от =&gt;, считается<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>