HTML Diff
1 added 1 removed
Original 2026-01-01
Modified 2026-02-26
1 <p>Понимание работы функций в немалой степени зависит от знания некоторых деталей реализации языка. К таким деталям относятся окружения (Environment). Разговор о них шел в<a>соответствующем уроке</a>"Введения в программирование". Советую освежить память, пересмотрев тот урок или перечитав конспект. После этого урока вы будете еще лучше разбираться в окружениях.</p>
1 <p>Понимание работы функций в немалой степени зависит от знания некоторых деталей реализации языка. К таким деталям относятся окружения (Environment). Разговор о них шел в<a>соответствующем уроке</a>"Введения в программирование". Советую освежить память, пересмотрев тот урок или перечитав конспект. После этого урока вы будете еще лучше разбираться в окружениях.</p>
2 <p>Познакомимся с термином "словарь".<strong>Словарь - это набор пар "ключ - значение"</strong>. Зная ключ, можно получить значение. Точно так же, как и при работе с обычным бумажным словарем, где ключ - это слово, а значение - определение слова. В данном уроке важно только концептуальное понимание, без конкретных реализаций.</p>
2 <p>Познакомимся с термином "словарь".<strong>Словарь - это набор пар "ключ - значение"</strong>. Зная ключ, можно получить значение. Точно так же, как и при работе с обычным бумажным словарем, где ключ - это слово, а значение - определение слова. В данном уроке важно только концептуальное понимание, без конкретных реализаций.</p>
3 - <p>Каждый раз, когда в программе вызывается функция, внутри интерпретатора создается специальный словарь LexicalEnvironment (лексическое окружение), привязанный к этому вызову. Все определения констант, переменных и прочего внутри функции автоматически записываются в словарь. Имя определения (идентификатор, то есть имя константы, переменной и так далее) становится ключом, а значение определения становится значением в словаре. К таким определениям относятся аргументы, константы, функции, переменные и т.д. Лексическое окружение - это хранилище для данных в памяти и механизм для извлечения этих данных при обращении.</p>
3 + <p>Каждый раз, когда в программе вызывается функция, внутри интерпретатора создается спциальный словарь LexicalEnvironment (лексическое окружение), привязанный к этому вызову. Все определения констант, переменных и прочего внутри функции автоматически записываются в словарь. Имя определения (идентификатор, то есть имя константы, переменной и так далее) становится ключом, а значение определения становится значением в словаре. К таким определениям относятся аргументы, константы, функции, переменные и т.д. Лексическое окружение - это хранилище для данных в памяти и механизм для извлечения этих данных при обращении.</p>
4 <p>В примере ниже в комментариях показано состояние словаря перед выполнением каждой строчки кода. Не забывайте, что наполнение словаря происходит<strong>при вызове</strong>функции, а не при определении.</p>
4 <p>В примере ниже в комментариях показано состояние словаря перед выполнением каждой строчки кода. Не забывайте, что наполнение словаря происходит<strong>при вызове</strong>функции, а не при определении.</p>
5 <p>Код console.log(warning) активизирует поиск значения идентификатора warning в лексическом окружении.</p>
5 <p>Код console.log(warning) активизирует поиск значения идентификатора warning в лексическом окружении.</p>
6 <p>В процессе выполнения функции значения переменных могут меняться, что сразу же отражается в лексическом окружении. После выполнения функции ее лексическое окружение уничтожается, а занятая им память освобождается.</p>
6 <p>В процессе выполнения функции значения переменных могут меняться, что сразу же отражается в лексическом окружении. После выполнения функции ее лексическое окружение уничтожается, а занятая им память освобождается.</p>
7 <p>Из этого поведения есть исключение - возврат функции. В следующем уроке мы рассмотрим связанный с ним механизм так называемых "замыканий". Ранее мы разбирали его во "<a>Введении в программирование</a>".</p>
7 <p>Из этого поведения есть исключение - возврат функции. В следующем уроке мы рассмотрим связанный с ним механизм так называемых "замыканий". Ранее мы разбирали его во "<a>Введении в программирование</a>".</p>
8 <p>Окружение есть не только у функций. Любой идентификатор, определенный на уровне модуля, попадает в лексическое окружение модуля. Кроме того, существует и глобальное окружение. Благодаря ему мы с легкостью используем в JS такие функции, как console.log или Math.sqrt, даже особо не задумываясь, откуда они берутся.</p>
8 <p>Окружение есть не только у функций. Любой идентификатор, определенный на уровне модуля, попадает в лексическое окружение модуля. Кроме того, существует и глобальное окружение. Благодаря ему мы с легкостью используем в JS такие функции, как console.log или Math.sqrt, даже особо не задумываясь, откуда они берутся.</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>А так нет:</p>
12 <p>А так нет:</p>
13 <p>Если подумать логически, так и должно быть. Представьте: если бы сработал второй вариант, то автоматически это бы означало, что вы можете случайно создать константу с именем, совпадающим с именем константы внутри функции, написанной другим человеком. Как при этом будет работать код - предположить невозможно.</p>
13 <p>Если подумать логически, так и должно быть. Представьте: если бы сработал второй вариант, то автоматически это бы означало, что вы можете случайно создать константу с именем, совпадающим с именем константы внутри функции, написанной другим человеком. Как при этом будет работать код - предположить невозможно.</p>
14 <p>Попробуйте самостоятельно ответить на вопрос: сработает ли такой код, в котором константа определена позже ее использования внутри функции?</p>
14 <p>Попробуйте самостоятельно ответить на вопрос: сработает ли такой код, в котором константа определена позже ее использования внутри функции?</p>
15 <p>Ответ: сработает.</p>
15 <p>Ответ: сработает.</p>
16 <p>Окружение - это не "все, что было объявлено до функции, в которой мы используем эти объявления". Не важно, что number появился позже использования внутри функции. Главное, что<strong>вызов функции</strong>square происходит позже определения number, а значит к этому времени идентификатор уже был добавлен в окружение, внутри которого была создана функция square.</p>
16 <p>Окружение - это не "все, что было объявлено до функции, в которой мы используем эти объявления". Не важно, что number появился позже использования внутри функции. Главное, что<strong>вызов функции</strong>square происходит позже определения number, а значит к этому времени идентификатор уже был добавлен в окружение, внутри которого была создана функция square.</p>
17 <h2>Переменные</h2>
17 <h2>Переменные</h2>
18 <p>Когда мы работаем с константами, все просто. Нет изменений - нет проблем. В случае с переменными ситуация становится сложнее.</p>
18 <p>Когда мы работаем с константами, все просто. Нет изменений - нет проблем. В случае с переменными ситуация становится сложнее.</p>
19 <p>Изменение переменной следует читать как "изменение значения ключа в окружении". Соответственно, обращение к number всегда вернет последнее присвоенное значение. Завязка на переменные, описанная в коде выше, должна восприниматься как абсолютное зло. Она порождает неявные зависимости, сложный код и отладку. Функция автоматически перестает быть чистой, так как начинает зависеть от внешнего контекста.</p>
19 <p>Изменение переменной следует читать как "изменение значения ключа в окружении". Соответственно, обращение к number всегда вернет последнее присвоенное значение. Завязка на переменные, описанная в коде выше, должна восприниматься как абсолютное зло. Она порождает неявные зависимости, сложный код и отладку. Функция автоматически перестает быть чистой, так как начинает зависеть от внешнего контекста.</p>
20 <h2>Вложенные функции</h2>
20 <h2>Вложенные функции</h2>
21 <p>Напомню вам слегка модифицированный код курса "Введение в программирование":</p>
21 <p>Напомню вам слегка модифицированный код курса "Введение в программирование":</p>
22 <p>В этом коде реализовано вычисление факториала с применением итеративного процесса. Внутри функции factorial определяется внутренняя функция iter, которая накапливает аккумулятор, вызываясь рекурсивно. Условие выхода из рекурсии - попытка посчитать число большее, чем нужно.</p>
22 <p>В этом коде реализовано вычисление факториала с применением итеративного процесса. Внутри функции factorial определяется внутренняя функция iter, которая накапливает аккумулятор, вызываясь рекурсивно. Условие выхода из рекурсии - попытка посчитать число большее, чем нужно.</p>
23 <p>В этой проверке используется переменная n, которая явно в iter не передавалась. Но благодаря тому, как работают окружения, любые функции (в том числе и вложенные), определенные внутри factorial, имеют к ней доступ. Как видно из кода, n используется как константа, а значит такое использование абсолютно безопасно.</p>
23 <p>В этой проверке используется переменная n, которая явно в iter не передавалась. Но благодаря тому, как работают окружения, любые функции (в том числе и вложенные), определенные внутри factorial, имеют к ней доступ. Как видно из кода, n используется как константа, а значит такое использование абсолютно безопасно.</p>
24 <h2>Перекрытие (Shadowing)</h2>
24 <h2>Перекрытие (Shadowing)</h2>
25 <p>Перекрытием называется ситуация, когда во внутреннем окружении создается идентификатор с таким же именем, как и во внешнем. Причем не важно, что это: аргумент функции, константа или переменная.</p>
25 <p>Перекрытием называется ситуация, когда во внутреннем окружении создается идентификатор с таким же именем, как и во внешнем. Причем не важно, что это: аргумент функции, константа или переменная.</p>
26 <p>Несмотря на то, что сам код остается рабочим, перекрытие больше не позволяет обратиться к идентификатору из внешнего окружения, ведь поиск всегда происходит<strong>сначала в локальном окружении</strong>, а уже затем во внешних. Но еще большей проблемой является то, что такой код сложнее в анализе. Глядя на него недостаточно видеть имена, нужно также учитывать их контекст, так как одно и то же имя на разных строках может означать разные вещи. Если запустить линтер для подобного кода, то он укажет на перекрытие как на плохую практику программирования. Подробнее об этом можно прочитать в<a>правилах Eslint</a>.</p>
26 <p>Несмотря на то, что сам код остается рабочим, перекрытие больше не позволяет обратиться к идентификатору из внешнего окружения, ведь поиск всегда происходит<strong>сначала в локальном окружении</strong>, а уже затем во внешних. Но еще большей проблемой является то, что такой код сложнее в анализе. Глядя на него недостаточно видеть имена, нужно также учитывать их контекст, так как одно и то же имя на разных строках может означать разные вещи. Если запустить линтер для подобного кода, то он укажет на перекрытие как на плохую практику программирования. Подробнее об этом можно прочитать в<a>правилах Eslint</a>.</p>