Ключевое слово this и контекст в JavaScript
Mar 25, 2019 · 6 min read
Овладение концепцией использования this и умение применять его со знанием дела не представляют особых трудностей.
Основы this
Запомните: Контекст имеет смысл только внутри функций.
Представьте, что вы пишите программу с функциями без вложений. Вы просто пишите одну строку за другой, не создавая никаких структур. Это значит, что нет необходимости следить за тем, где вы находитесь, поскольку вы остаетесь на одном уровне.
При наличии функций, программа обладает несколькими уровнями, а ключевое слово this указывает на то, где вы находитесь, и какой именно объект является функцией.
Отслеживание объекта caller
Рассмотрим изменения ключевого слова this в зависимости от контекста:
Классы и экземпляры классов
Классы используются, чтобы делать код более абстрактным и разделять их поведение. Не стоит объявлять функцию info несколько раз, как было указано в последнем примере. Поскольку классы и их экземпляры находятся в фактических объектах, то и действуют они одинаково. Однако следует упомянуть, что объявление this в конструкторе представляет собой предсказание на будущее, когда появится экземпляр.
Ошибка: вложенные вызовы функции
Иногда мы оказываемся не в том контексте, которого мы ожидали. Это может произойти при вызове функции внутри контекста другого объекта. Самый распространенный пример при использовании setTimeout или setInterval :
React
При работе с React подобные ситуации скоро останутся в прошлом, благодаря Hooks. Но на данный момент все еще необходимо связать ( bind ) все функции тем или иным способом (подробнее об этом позже).
Рассмотрим два простых компонента класса React:
Чтобы решить эту проблему, необходимо связать ( bind ) методы в классах при передаче их из компонента, в котором они определены.
Рассмотрим еще один базовый пример:
Решение — bind
Лучшее решение в этой ситуации — связать ( bind ) методы, которые будут переданы из первоначального объекта или класса. Есть несколько способов связывания функций, но самый распространенный (даже в React) — это связать их в конструкторе. Нужно добавить эту строку в конструктор Battle :
Стрелочные функции `() => <>` автоматически привязывают функцию к контексту объявления.
Функции apply и call
Они обе выполняют одно и то же действие, различается лишь их синтаксис. В обоих случаях контекст передается в качестве первого аргумента. apply охватывает массив других аргументов, а при использовании call можно просто разделить другие аргументы запятыми. И что же они выполняют? Оба метода устанавливают контекст для одного определенного вызова функции. При вызове функции без метода call контекст устанавливается по умолчанию (или даже связанный контекст). Пример:
Вот и все. Надеюсь, вы разобрались в том, как использовать this в JavaScript.
Контекст выполнения и стек вызовов в JavaScript
Если вы — JavaScript-разработчик или хотите им стать, это значит, что вам нужно разбираться во внутренних механизмах выполнения JS-кода. В частности, понимание того, что такое контекст выполнения и стек вызовов, совершенно необходимо для освоения других концепций JavaScript, таких, как поднятие переменных, области видимости, замыкания. Материал, перевод которого мы сегодня публикуем, посвящён контексту выполнения и стеку вызовов в JavaScript.

Контекст выполнения
Контекст выполнения (execution context) — это, если говорить упрощённо, концепция, описывающая окружение, в котором производится выполнение кода на JavaScript. Код всегда выполняется внутри некоего контекста.
▍Типы контекстов выполнения
В JavaScript существует три типа контекстов выполнения:
Стек выполнения
Стек выполнения (execution stack), который ещё называют стеком вызовов (call stack), это LIFO-стек, который используется для хранения контекстов выполнения, создаваемых в ходе работы кода.
Когда JS-движок начинает обрабатывать скрипт, движок создаёт глобальный контекст выполнения и помещает его в текущий стек. При обнаружении команды вызова функции движок создаёт новый контекст выполнения для этой функции и помещает его в верхнюю часть стека.
Движок выполняет функцию, контекст выполнения которой находится в верхней части стека. Когда работа функции завершается, её контекст извлекается из стека и управление передаётся тому контексту, который находится в предыдущем элементе стека.
Изучим эту идею с помощью следующего примера:
Вот как будет меняться стек вызовов при выполнении этого кода.
Состояние стека вызовов
Когда вышеприведённый код загружается в браузер, JavaScript-движок создаёт глобальный контекст выполнения и помещает его в текущий стек вызовов. При выполнении вызова функции first() движок создаёт для этой функции новый контекст и помещает его в верхнюю часть стека.
Когда функция first() завершает работу, её контекст извлекается из стека и управление передаётся глобальному контексту. После того, как весь код оказывается выполненным, движок извлекает глобальный контекст выполнения из текущего стека.
О создании контекстов и о выполнении кода
До сих пор мы говорили о том, как JS-движок управляет контекстами выполнения. Теперь поговорим о том, как контексты выполнения создаются, и о том, что с ними происходит после создания. В частности, речь идёт о стадии создания контекста выполнения и о стадии выполнения кода.
▍Стадия создания контекста выполнения
Перед выполнением JavaScript-кода создаётся контекст выполнения. В процессе его создания выполняются три действия:
Привязка this
В глобальном контексте выполнения this содержит ссылку на глобальный объект (как уже было сказано, в браузере это объект window ).
В контексте выполнения функции значение this зависит от того, как именно была вызвана функция. Если она вызвана в виде метода объекта, тогда значение this привязано к этому объекту. В других случаях this привязывается к глобальному объекту или устанавливается в undefined (в строгом режиме). Рассмотрим пример:
Лексическое окружение
Проще говоря, лексическое окружение — это структура, которая хранит сведения о соответствии идентификаторов и переменных. Под «идентификатором» здесь понимается имя переменной или функции, а под «переменной» — ссылка на конкретный объект (в том числе — на функцию) или примитивное значение.
В лексическом окружении имеется два компонента:
Лексическое окружение можно представить в виде следующего псевдокода:
Окружение переменных
Окружение переменных (Variable Environment) — это тоже лексическое окружение, запись окружения которого хранит привязки, созданные посредством команд объявления переменных ( VariableStatement ) в текущем контексте выполнения.
Так как окружение переменных также является лексическим окружением, оно обладает всеми вышеописанными свойствами лексического окружения.
Рассмотрим примеры, иллюстрирующие то, что мы только что обсудили:
Схематичное представление контекста выполнения для этого кода будет выглядеть так:
Только что мы только что описали, называется «поднятием переменных» (Hoisting). Объявления переменных «поднимаются» в верхнюю часть их лексической области видимости до выполнения операций присвоения им каких-либо значений.
▍Стадия выполнения кода
Это, пожалуй, самая простая часть данного материала. На этой стадии выполняется присвоение значений переменным и осуществляется выполнение кода.
Итоги
Только что мы обсудили внутренние механизмы выполнения JavaScript-кода. Хотя для того, чтобы быть очень хорошим JS-разработчиком, знать всё это и не обязательно, если у вас имеется некоторое понимание вышеописанных концепций, это поможет вам лучше и глубже разобраться с другими механизмами языка, с такими, как поднятие переменных, области видимости, замыкания.
Уважаемые читатели! Как вы думаете, о чём ещё, помимо контекста выполнения и стека вызовов, полезно знать JavaScript-разработчикам?
Что такое контекст? Более обширный взгляд
Сколько уже разрабатываю приложения, до сих пор не понимаю что такое контекст. Например, возьмем старый добрый Toast
Последний вопрос я задал, так как по ходу написания остальных, я понял, что ничего о нем не знаю 🙂
2 ответа 2
класс Context содержит в себе всевозможную информацию о ресурсах системы, как уже было сказано в другом ответе. Конкретно в этом вопросе нас интересует, что он содержит, помимо прочего, и параметры темы (стилей) для отображения View
Почему в качестве контекста можно передать this? Это же ссылка на класс
Есть ли случаи когда надо передать именно getApplicationContext?
Тема (стиль) всего приложения и конкретной активити может отличаться (для активити в манифесте указан другой стиль). Тогда запрос контекста приложения и контекста активти вернет разное оформление View
Почему контекст нужен везде, где происходит работа с интерфейсом?
Потому что он содержит стиль для View
Возможно в приложении могут существовать и какие то другие отличия в окружении, назначенном всему приложению и конкретной активити, тогда обращение к контексту приложения или активити тоже будет иметь значение, но мне такие отличия (кроме тем и стилей) припомнить не удалось.
контекст приложения следует использовать везде, где контекст необходимо передать за пределы жизненного цикла передающего компонента (в объекты, которые будут жить дольше, чем создавшая/вызвавшая их активность, например) во избежании удержания ссылки на этот компонент при использовании его собственного контекста и утечек памяти.
во внешние библиотеки следует передавать контекст приложения по тем же причинам, что и п.1
контекст приложения не имеет информации по особенностям GUI отдельной активити, если они отличаются от параметров всего приложения, в таких случаях нельзя использовать контекст приложения при работе с GUI этой активити.
Context в Android приложении
Что такое Context?
Как следует из названия, это контекст текущего состояния приложения или объекта. Это позволяет вновь созданным объектам понять, что вообще происходит. Обычно его вызывают, чтобы получить информацию о другой части программы.
Context присутствует практически повсюду в Android приложении и является самой важной его частью, поэтому необходимо понимать, как правильно его использовать.
Неправильное использование Context может легко привести к утечкам памяти в Android приложении.
Существует много разных типов контекста, поэтому давайте разберёмся, что каждый из них представляет из себя, как и когда их правильно использовать.
Контекст приложения
Например, если вам нужно создать singleton-объект для вашего приложения, и этому объекту нужен какой-нибудь контекст, всегда используйте контекст приложения.
Если вы передадите контекст Activity в этом случае, это приведет к утечке памяти, так как singleton-объект сохранит ссылку на Activity и она не будет уничтожена сборщиком мусора, когда это потребуется.
Таким образом, getApplicationContext() нужно использовать тогда, когда известно, что вам нужен контекст для чего-то, что может жить дольше, чем любой другой контекст, который есть в вашем распоряжении.
Контекст Activity
Этот контекст доступен в Activity и привязан к её жизненному циклу. Контекст Activity следует использовать, когда вы передаете контекст в рамках Activity или вам нужен контекст, жизненный цикл которого привязан к текущему контексту.
getContext() в ContentProvider
Когда нельзя использовать getApplicationContext()?
Правило большого пальца
В большинстве случаев используйте контекст, доступный непосредственно из компонента, в котором вы работаете в данный момент. Вы можете безопасно хранить ссылку на него, если она не выходит за пределы жизненного цикла этого компонента. Как только вам нужно сохранить ссылку на контекст в объекте, который живет за пределами вашей Activity или другого компонента, даже временно, используйте ссылку на контекст приложения.
Почему контекст не является «инструментом для управления состоянием»
Context и Redux — это одно и тоже?
Нет. Это разные инструменты, делающие разные вещи и используемые в разных целях.
Является ли контекст инструментом «управления состоянием»?
Нет. Контекст — это форма внедрения зависимостей (dependency injection). Это транспортный механизм, который ничем не управляет. Любое «управление состоянием» осуществляется вручную, как правило, с помощью хуков useState()/useReducer().
Являются ли Context и useReducer() заменой Redux?
Нет. Они в чем-то похожи и частично пересекаются, но сильно отличаются в плане возможностей.
Когда следует использовать контекст?
Когда вы хотите сделать некоторые данные доступными для нескольких компонентов, но не хотите передавать эти данные в виде пропов на каждом уровне дерева компонентов.
Когда следует использовать Context и useReducer()?
Когда вам требуется управление состоянием умеренно сложного компонента в определенной части приложения.
Когда следует использовать Redux?
Redux наиболее полезен в следующих случаях:
Понимание Context и Redux
Для правильного использования инструмента критически важно понимать:
Неразбериха вокруг Context и Redux связана, в первую очередь, с непониманием того, для чего данные инструменты предназначены, и какие задачи они решают. Поэтому, прежде чем говорить о том, когда их следует использовать, необходимо определить, что они из себя представляют и какие проблемы решают.
Что такое контекст?
Начнем с определения контекста из официальной документации:
«Контекст позволяет передавать данные через дерево компонентов без необходимости передавать пропы на промежуточных уровнях.
В типичном React-приложении данные передаются сверху вниз (от предка к потомку) с помощью пропов. Однако, этот способ может быть чересчур громоздким для некоторых типов пропов (например, выбранный язык, тема интерфейса), которые необходимо передавать во многие компоненты в приложении. Контекст предоставляет способ распределять такие данные между компонентами без необходимости явно передавать пропы на каждом уровне дерева компонентов».
Обратите внимание, в данном определении ни слова не говорится об «управлении», только о «передаче» и «распределении».
Текущее API контекста (React.createContext()) впервые было представлено в React 16.3 в качестве замены устаревшего API, доступного в ранних версиях React, но имеющего несколько недостатков дизайна. Одной из главных проблем являлось то, что обновления значений, переданных через контекст, могли быть «заблокированы», если компонент пропускал рендеринг через shouldComponentUpdate(). Поскольку многие компоненты прибегали к shouldComponentUpdate() в целях оптимизации, передача данных через контекст становилась бесполезной. createContext() был спроектирован для решения этой проблемы, поэтому любое обновление значения отразится на дочерних компонентах, даже если промежуточный компонент пропускает рендеринг.
Использование контекста
Использование контекста в приложении предполагает следующее:
Обычно, значением контекста является состояние компонента:
После этого дочерний компонент может вызвать хук useContext() и прочитать значение контекста:
Цель и случаи использования контекста
Концептуально, это является формой внедрения зависимостей. Мы знаем, что потомок нуждается в данных определенного типа, но он не пытается создавать или устанавливать эти данные самостоятельно. Вместо этого, он полагается на то, что некоторый предок передаст эти данные во время выполнения (runtime).
Что такое Redux?
Вот о чем гласит определение из «Основ Redux»:
«Redux — это паттерн проектирования и библиотека для управления и обновления состояния приложения, использующая события, именуемые операциями (действиями, actions). Redux выступает в роли централизованного хранилища состояния приложения, следюущего правилам, позволяющим обеспечить предсказуемое обновление состояние.
Redux позволяет управлять „глобальным“ состоянием — состоянием, которое трубется нескольким частям приложения.
Паттерны и инструменты, предоставляемые Redux, позволяют легче определять где, когда, почему и как было обновлено состояние, и как отреагировало приложение на это изменение».
Обратите внимание, что данное описание указывает на:
Архитектурно, Redux подчеркнуто использует принципы функционального программирования, что позволяет писать код в форме предсказуемых «функций-редукторов» (reducers), и обособлять идею «какое событие произошло» от логики, определяющей «как обновляется состояние при возгникновении данного события». В Redux также используется промежуточное программное обеспечение (middleware) как способ расширения возможностей хранилища, включая обработку побочных эффектов (side effects).
Redux также предоставляет инструменты разработчика, позволяющие изучать историю операций и изменение состояния в течение времени.
Redux и React
Сам по себе Redux не зависит от UI — вы можете использовать его с любым слоем представления (view layer) (React, Vue, Angular, ванильный JS и т.д.) либо без UI вообще.
Однако, чаще всего, Redux используется совместно с React. Библиотека React Redux — это официальный связывающий слой UI, позволяющий React-компонентам взаимодействовать с хранилищем Redux, получая значения из состояния Redux и инициализируя выполнение операций. React-Redux использует контекст в своих внутренних механизмах. Тем не менее, следует отметить, что React-Redux передает через контекст экземпляр хранилища Redux, а не текущее значение состояния! Это пример использования контекста для внедрения зависимостей. Мы знаем, что наши подключенные к Redux компоненты нуждаются во взаимодействии с хранилищем Redux, но мы не знаем или нам неважно, что это за хранилище, когда мы определяем компонент. Настоящее хранилище Redux внедряется в дерево во время выполнения с помощью компонента
Цель и случаи использования (React-)Redux
Основное назначение Redux согласно официальной документации:
«Паттерны и инструменты, предоставляемые Redux, облегчают понимание того, когда, где, почему и как произошло изменение состояния, а также того, как на это отреагировало приложение».
Существует еще несколько причин использования Redux. Одной из таких причин является предотвращение «бурения».
Другие случаи использования:
Почему контекст не является инструментом «управления состоянием»?
Состояние — это любые данные, описывающие поведение приложения. Мы можем разделить состояние на такие категории, как состояние сервера, состояние коммуникаций и локальное состояние, если хотим, но ключевым аспектом является хранение, чтение, обновление и использование данных.
«Управление состоянием — это изменение состояния в течение времени».
Таким образом, мы можем сказать, что «управление состоянием» означает следующее:
React-хуки useState() и useReducer() являются отличными примерами управления состоянием. С помощью этих хуков мы можем:
React Context не соответствует названным критериям. Поэтому он не является инструментом управления состоянием
«Контекст — это то, как существующее состояние распределяется между компонентами. Контекст ничего не делает с состоянием».
«Полагаю, контекст — это как скрытые пропы, абстрагирующие состояние».
Все, что делает контекст, это позволяет избежать «бурения».
Сравнение Context и Redux
Сравним возможности контекста и React+Redux:
Context и useReducer()
Одной из проблем в дискуссии «Context против Redux» является то, что люди, зачастую, на самом деле имеют ввиду следующее: «Я использую useReducer() для управления состоянием и контекст для передачи значения». Но, вместо этого, они просто говорят: «Я использую контекст». В этом, на мой взгляд, кроется основная причина неразберихи, способствующая поддержанию мифа о том, что контекст «управляет состоянием».
Рассмотрим комбинацию Context + useReducer(). Да, такая комбинация выглядит очень похоже на Redux + React-Redux. Обе эти комбинации имеют:
«Мое личное мнение состоит в том, что новый контекст готов к использованию для маловероятных обновлений с низкой частотой (таких как локализация или тема). Он также может использоваться во всех тех случаях, в которых использовался старый контекст, т.е. для статических значений с последующим распространением обновления по подпискам. Он не готов к использованию в качестве замены Flux-подобных „распространителей“ состояния».
В сети существует много статей, рекомендующих настройку нескольких отдельных контекстов для разных частей состояния, что позволяет избежать ненужных повторных рендерингов и решить проблемы, связанные с областями видимости. Некоторые из постов также предлагают добавлять собственные «компоненты по выбору контекста», что требует использования сочетания React.memo(), useMemo() и аккуратного разделения кода на два контекста для каждой части приложения (одна для данных, другая для функций обновления). Безусловно, код можно писать и так, но в этом случае вы заново изобретаете React-Redux.
Таким образом, несмотря на то, что Context + useReducer() — это легкая альтернатива Redux + React-Redux в первом приближении… эти комбинации не идентичны, контекст + useReducer() не может полностью заменить Redux!
Выбор правильного инструмента
Для выбора правильного инструмента очень важно понимать, какие задачи решает инструмент, а также какие задачи стоят перед вами.
Обзор случаев использования
Рекомендации
Как же решить, что следует использовать?
Для этого вам всего лишь нужно определить, какой инструмент наилучшим образом решает задачи вашего приложения.
Часто можно услышать, что «использование Redux предполагает написание большого количества шаблонного кода», однако, «современный Redux» значительно облегчает изучение данного инструмента и его использование. Официальный пакет Redux Toolkit решает проблему шаблонизации, а хуки React-Redux упрощают использование Redux в компонентах React.
Разумеется, добавление RTK и React-Redux в качестве зависимостей увеличивает «бандл» приложения по сравнению с контекстом + useReducer(), которые являются встроенными. Но преимущества такого подхода перекрывают недостатки — лучшая трассировка состояния, простая и более предсказуемая логика, улучшенная оптимизация рендринга компонентов.
Также важно отметить, что одно не исключает другого — вы можете использовать Redux, Context и useReducer() вместе. Мы рекомендуем хранить «глобальное» состояние в Redux, а локальное — в компонентах и внимательно подходить к определению того, какая часть приложения должна храниться в Redux, а какая — в компонентах. Так что вы можете использовать Redux для хранения глобального состояния, Context + useReducer() — для хранения локального состояния, и Context — для статических значений, одновременно и в одном приложении.
Еще раз: я не утверждаю, что все состояние приложения должно храниться в Redux или что Redux — это всегда лучшее решение. Я утверждаю лишь, что Redux — хороший выбор, существует много причин использовать Redux, и плата за его использование не так высока, как многие думают.
Наконец, контекст и Redux не единственные в своем роде. Существует множество других инструментов, решающих иные аспекты управления состоянием. MobX — популярное решение, использующее ООП и наблюдаемые объекты (observables) для автоматического обновления зависимостей. Среди других подходов к обновлению состояния можно назвать Jotai, Recoil и Zustand. Библиотеки для работы с данными, вроде React Query, SWR, Apollo и Urql, предоставляют абстракции, упрощающие применение распространенных паттернов для работы с состоянием, кэшируемым сервером (скоро похожая библиотека (RTK Query) появится и для Redux Toolkit).
Надеюсь, данная статья помогла вам понять разницу между контекстом и Redux, а также какой инструмент и в каких случаях следует использовать. Благодарю за внимание.









