HTML Diff
0 added 0 removed
Original 2026-01-01
Modified 2026-02-26
1 <p>В скриптовых языках, подобных JavaScript, внутри файлов (но вне определений) можно писать любой код: определения функций, вызовы функций, определения и изменения переменных. Такая свобода упрощает разработку, например, создание одноразовых скриптов для каких-то простых или не очень задач. С другой стороны, при неаккуратной разработке появляются ошибки, значительно усложняющие код и его поддержку. Они так часто встречаются в продакшен коде, что об этом нужно поговорить отдельно.</p>
1 <p>В скриптовых языках, подобных JavaScript, внутри файлов (но вне определений) можно писать любой код: определения функций, вызовы функций, определения и изменения переменных. Такая свобода упрощает разработку, например, создание одноразовых скриптов для каких-то простых или не очень задач. С другой стороны, при неаккуратной разработке появляются ошибки, значительно усложняющие код и его поддержку. Они так часто встречаются в продакшен коде, что об этом нужно поговорить отдельно.</p>
2 <p><em>Эти проблемы не специфичны для JavaScript, то же самое встречается и во многих других интерпретируемых языках, таких как Python, Ruby или PHP.</em></p>
2 <p><em>Эти проблемы не специфичны для JavaScript, то же самое встречается и во многих других интерпретируемых языках, таких как Python, Ruby или PHP.</em></p>
3 <p>Подробнее о разнице между модулями и скриптами можно прочитать<a>в статье</a>. Здесь же мы сосредоточимся на неверно спроектированных модулях.</p>
3 <p>Подробнее о разнице между модулями и скриптами можно прочитать<a>в статье</a>. Здесь же мы сосредоточимся на неверно спроектированных модулях.</p>
4 <blockquote><p>Подписывайтесь на<a>канал Кирилла Мокевнина в Telegram</a>- чтобы узнать больше о программировании и профессиональном пути разработчика</p>
4 <blockquote><p>Подписывайтесь на<a>канал Кирилла Мокевнина в Telegram</a>- чтобы узнать больше о программировании и профессиональном пути разработчика</p>
5 </blockquote><h2>Содержание</h2>
5 </blockquote><h2>Содержание</h2>
6 <ul><li><a>Разбираем пример</a></li>
6 <ul><li><a>Разбираем пример</a></li>
7 <li><a>Локальное состояние</a></li>
7 <li><a>Локальное состояние</a></li>
8 <li><a>Допустимое глобальное состояние</a></li>
8 <li><a>Допустимое глобальное состояние</a></li>
9 <li><a>Дополнительные материалы</a></li>
9 <li><a>Дополнительные материалы</a></li>
10 </ul><h2>Разбираем пример</h2>
10 </ul><h2>Разбираем пример</h2>
11 <p>Предположим, что у нас есть модуль<em>index.js</em>с таким содержимым:</p>
11 <p>Предположим, что у нас есть модуль<em>index.js</em>с таким содержимым:</p>
12 <p>Где-то в других местах программы он импортируется и используется. Как правило, импорт подобных модулей происходит не в одном месте, а в разных местах программы.</p>
12 <p>Где-то в других местах программы он импортируется и используется. Как правило, импорт подобных модулей происходит не в одном месте, а в разных местах программы.</p>
13 <p>Возникает вопрос, сколько раз реально вызывается содержимое файла<em>index.js</em>? Проверить очень легко - достаточно внутри модуля вызвать печать на экран:</p>
13 <p>Возникает вопрос, сколько раз реально вызывается содержимое файла<em>index.js</em>? Проверить очень легко - достаточно внутри модуля вызвать печать на экран:</p>
14 <p>Запустив программу, можно увидеть, что вызов произошел ровно один раз. То же самое относится и к константе. Она была определена ровно один раз. В этом смысле export сильно отличается от return внутри функций. return вызывается на каждый вызов, а export срабатывает только при первом импорте, и затем всегда переиспользуется.</p>
14 <p>Запустив программу, можно увидеть, что вызов произошел ровно один раз. То же самое относится и к константе. Она была определена ровно один раз. В этом смысле export сильно отличается от return внутри функций. return вызывается на каждый вызов, а export срабатывает только при первом импорте, и затем всегда переиспользуется.</p>
15 <p>Из этой особенности есть важное следствие - модуль легко превратить в хранилище глобального состояния.</p>
15 <p>Из этой особенности есть важное следствие - модуль легко превратить в хранилище глобального состояния.</p>
16 <p>Где-то в других частях системы:</p>
16 <p>Где-то в других частях системы:</p>
17 <p>Обратите внимание. Хотя это и разные файлы, объект, который импортируется из<em>state.js</em>, всегда тот же самый.</p>
17 <p>Обратите внимание. Хотя это и разные файлы, объект, который импортируется из<em>state.js</em>, всегда тот же самый.</p>
18 <p>Что же здесь произошло? Хотя код кажется удобным для использования, он основывается на практиках, которые в программировании всегда считались плохими. Фактически здесь была создана глобальная переменная, к которой может получить доступ любая часть системы (через импорт) и изменить ее любым способом. Это довольно опасно, и может приводить к серьезным ошибкам. Именно поэтому глобальные переменные рекомендуется избегать всегда, когда это можно сделать.</p>
18 <p>Что же здесь произошло? Хотя код кажется удобным для использования, он основывается на практиках, которые в программировании всегда считались плохими. Фактически здесь была создана глобальная переменная, к которой может получить доступ любая часть системы (через импорт) и изменить ее любым способом. Это довольно опасно, и может приводить к серьезным ошибкам. Именно поэтому глобальные переменные рекомендуется избегать всегда, когда это можно сделать.</p>
19 <p>Частично проблему можно решить методами доступа, добавленными к состоянию:</p>
19 <p>Частично проблему можно решить методами доступа, добавленными к состоянию:</p>
20 <p>Так мы получим не просто глобальные данные, а глобальный объект (с точки зрения ООП, а не типов данных). Однако, это мало что меняет. Глобальные данные по прежнему остались глобальными.</p>
20 <p>Так мы получим не просто глобальные данные, а глобальный объект (с точки зрения ООП, а не типов данных). Однако, это мало что меняет. Глобальные данные по прежнему остались глобальными.</p>
21 <p>Почему это плохо? Представьте, что мы написали библиотеку для автокомплита и подключили ее на странице. Эта библиотека, скорее всего, внутри себя хранит те данные, которые она загрузила с бекенда, например, для ускорения доступа. Затем, нам понадобилось подключить два автокомплита на одной странице. И вот тут начнутся сюрпризы. Изменения в одном автокомплите начнут влиять на другой.</p>
21 <p>Почему это плохо? Представьте, что мы написали библиотеку для автокомплита и подключили ее на странице. Эта библиотека, скорее всего, внутри себя хранит те данные, которые она загрузила с бекенда, например, для ускорения доступа. Затем, нам понадобилось подключить два автокомплита на одной странице. И вот тут начнутся сюрпризы. Изменения в одном автокомплите начнут влиять на другой.</p>
22 <p><em>В теории для различных автокомплитов можно сделать разные куски данных, но, например, в случае динамического добавления и удаления автокомплитов, проблема все равно вылезет. То есть не существует теоретического способа сделать подобную реализацию такой, чтобы с ней не возникало никаких проблем во всех ситуациях</em></p>
22 <p><em>В теории для различных автокомплитов можно сделать разные куски данных, но, например, в случае динамического добавления и удаления автокомплитов, проблема все равно вылезет. То есть не существует теоретического способа сделать подобную реализацию такой, чтобы с ней не возникало никаких проблем во всех ситуациях</em></p>
23 <p>Есть и другой пример. Подобные решения сразу всплывают в тестах. В тестировании тесты не должны зависеть друг от друга, но с глобальным состоянием это невозможно. Любые изменения состояния в одном тесте отразятся на втором. Можно, конечно, пытаться их восстанавливать в исходное состояние руками, но это крайне ненадежно (нужно умело обрабатывать ошибки) и добавляет много ненужной сложности.</p>
23 <p>Есть и другой пример. Подобные решения сразу всплывают в тестах. В тестировании тесты не должны зависеть друг от друга, но с глобальным состоянием это невозможно. Любые изменения состояния в одном тесте отразятся на втором. Можно, конечно, пытаться их восстанавливать в исходное состояние руками, но это крайне ненадежно (нужно умело обрабатывать ошибки) и добавляет много ненужной сложности.</p>
24 <h2>Локальное состояние</h2>
24 <h2>Локальное состояние</h2>
25 <p>Правильный способ работы с состоянием - его локализация относительно приложения. Например, в случае автокомплитов это может быть функция:</p>
25 <p>Правильный способ работы с состоянием - его локализация относительно приложения. Например, в случае автокомплитов это может быть функция:</p>
26 <p>И использование:</p>
26 <p>И использование:</p>
27 <p>Подобная организация кода позволяет добавлять на страницу любое число автокомплитов не боясь, что они друг на друга повлияют. У каждого автокомплита свое локальное состояние, с которым он работает.</p>
27 <p>Подобная организация кода позволяет добавлять на страницу любое число автокомплитов не боясь, что они друг на друга повлияют. У каждого автокомплита свое локальное состояние, с которым он работает.</p>
28 <p>Точно так же нужно поступать и во всех остальных ситуациях, не важно используются там фреймворки или пишется нативный JavaScript. Общий принцип работы с состоянием остается неизменным - все приложение заворачивается в функцию, которая определяет состояние глобальное для конкретного приложения, но локальное относительно среды запуска этого приложения.</p>
28 <p>Точно так же нужно поступать и во всех остальных ситуациях, не важно используются там фреймворки или пишется нативный JavaScript. Общий принцип работы с состоянием остается неизменным - все приложение заворачивается в функцию, которая определяет состояние глобальное для конкретного приложения, но локальное относительно среды запуска этого приложения.</p>
29 <h2>Допустимое глобальное состояние</h2>
29 <h2>Допустимое глобальное состояние</h2>
30 <p>Есть ли примеры, когда глобальное состояние допустимо? Как минимум, им пользуются многие библиотеки для конфигурации или своих внутренних нужд. Это может рождать определенные сложности, как описано выше, но довольно часто мешает не сильно:</p>
30 <p>Есть ли примеры, когда глобальное состояние допустимо? Как минимум, им пользуются многие библиотеки для конфигурации или своих внутренних нужд. Это может рождать определенные сложности, как описано выше, но довольно часто мешает не сильно:</p>
31 <p>Итого. Никогда не используйте глобальное состояние для данных. Если нужно хранить данные - используйте объекты, создаваемые внутри приложения.</p>
31 <p>Итого. Никогда не используйте глобальное состояние для данных. Если нужно хранить данные - используйте объекты, создаваемые внутри приложения.</p>
32 <h2>Дополнительные материалы</h2>
32 <h2>Дополнительные материалы</h2>
33 <ul><li><a>Отделяем получение данных от их использования</a></li>
33 <ul><li><a>Отделяем получение данных от их использования</a></li>
34 </ul>
34 </ul>