что такое контекст функции javascript
Ключевое слово this в javascript — учимся определять контекст на практике
По просьбам некоторых читателей решил написать топик про контекст в javascript. Новички javascript часто не понимают значение ключевого слова this в javascript. Данный топик будет интересен не только новичкам, а также тем, кто просто хочет освежить данный аспект в памяти. Посмотрите пример ниже. Если вы затрудняетесь ответить на вопрос «что будет выведено в логе» хотя бы в одном из пунктов или хотите просто посмотреть ответы — добро пожаловать под кат.
1. Теория
В отличие от многих других языков программирования ключевое слово this в javascript не привязывается к объекту, а зависит от контекста вызова. Для упрощения понимания будем рассматривать примеры применительно к браузеру, где глобальным объектом является window.
1.1. Простой вызов функции
В данном случае this внутри функции f равен глобальному объекту (например, в браузере это window, в Node.js — global).
Самовызывающиеся функции (self-invoking) работают по точно такому же принципу.
1.2. В конструкторе
При вызове функции с использованием ключевого слова new функция выступает в роли конструктора, и в данном случе this указывает на создаваемый объект.
1.3. В методе объекта
Если функция запускается как свойство объекта, то в this будет ссылка на этот объект. При этом не имеет значения, откуда данная функция появилась в объекте, главное — как она вызывается, а именно какой объект стоит перед вызовом функции:
1.4. Методы apply, call
Методы apply и call позволяют задать контекст для выполняемой функции. Разница между apply и call — только в способе передачи параметров в функцию. Первый параметр обеих функций определяет контекст выполнения функции (то, чему будет равен this).
2. Разбираем задачу
Применим полученные знания к приведенной в начале топика задаче. Опять же для упрощения будем рассматривать примеры применительно к браузеру, где глобальным объектом является window.
Контекст выполнения и стек вызовов в 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-разработчикам?
Ключевое слово 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. Контекстом еще часто называют значение переменной this внутри функции. Также иногда путают понятия «контекст выполнения» и «область видимости» — это не одно и то же. Давайте разберемся с этими понятиями.
Каждый вызов функции имеет и область видимости, и переменную this, и контекст выполнения. Область видимости определяет доступ к переменным при вызове функции и является уникальной для каждого вызова. Значение переменной this — это ссылка на объект, который «вызывает» код в данный момент. Контекст выполнения содержит и область видимости, и аргументы функции, и переменную this.
Переменная this
Значение переменной this чаще всего определяется тем, как вызывается функция. Когда функция вызывается как метод объекта, переменная this приобретает значение ссылки на объект, который вызывает этот метод:
Тот же принцип применяется при вызове функции с оператором new, чтобы создать экземпляр объекта. При вызове таким образом, в качестве значения this в рамках функции будет установлена ссылка на вновь созданный объект, например:
Когда мы вызываем функцию как функцию (не как метод объекта), эта функция будет выполнена в глобальном контексте. Значением переменной this в данном случае будет ссылка на глобальный объект. Однако, если функция вызывается как функция в строгом режиме (strict mode) — значением this будет undefined.
Контекст выполнения
Код в JavaScript может быть одного из следующих типов:
Когда интерпретатор JavaScript выполняет код, по умолчанию контекстом выполнения является глобальный контекст. Каждый вызов функции приводит к созданию нового контекста выполнения.
В данном примере мы имеем один глобальный контекст выполнения и 3 контекста выполнения функции.
Каждый раз, когда создается новый контекст выполнения, он добавляется в верхнюю часть стека выполнения. Браузер всегда будет выполнять код в текущем контексте выполнения, который находится на вершине стека выполнения. После завершения, контекст будет удален из верхней части стека и управление вернется к контексту выполнения ниже.
Основные вещи, которые необходимо помнить и понимать о контексте выполнения:
В интерпретаторе JavaScript каждое создание контекста выполнения происходит в два этапа: этап создания (когда функция только вызвана, но код внутри нее еще не выполняется) и этап выполнения. На этапе создания интерпретатор сначала создает объект переменных (также называемый объект активации), который состоит из всех переменных, объявлений функций и аргументов, определенных внутри контекста выполнения. Затем инициализируется область видимости, и в последнюю очередь определяется значение переменной this. На этапе выполнения внутренним переменным присваивается значение, код интерпретируется и выполняется.
Таким образом, контекст выполнения функции можно представить в виде следующего объекта:
Для каждого контекста выполнения существует своя цепочка областей видимости. Цепочка областей видимости контекста выполнения включает области видимости из предыдущих контекстов в стеке выполнения.
Т.е. каждый раз, когда мы пытаемся получить доступ к переменной в контексте выполнения функции, процесс поиска этой переменной начинается с собственной области видимости функции. Если переменная с таким именем в текущей области видимости не найдена, поиск продолжается в иерархии областей видимости.
Понятия области видимости и контекста выполнения очень важны и играют значительную роль в языке JavaScript. Их хорошее понимание важно для изучения ряда шаблонов проектирования, понимания работы замыканий, функций обратного вызова, частичного применения функций и других важных концепций JavaScript.
Привязка контекста к функции
В этой главе мы посмотрим, как её можно решить.
Потеря «this»
Вот как это может произойти в случае с setTimeout :
Это произошло потому, что setTimeout получил функцию sayHi отдельно от объекта user (именно здесь функция и потеряла контекст). То есть последняя строка может быть переписана как:
Задача довольно типичная – мы хотим передать метод объекта куда-то ещё (в этом конкретном случае – в планировщик), где он будет вызван. Как бы сделать так, чтобы он вызывался в правильном контексте?
Решение 1: сделать функцию-обёртку
Самый простой вариант решения – это обернуть вызов в анонимную функцию, создав замыкание:
То же самое, только короче:
Выглядит хорошо, но теперь в нашем коде появилась небольшая уязвимость.
Что произойдёт, если до момента срабатывания setTimeout (ведь задержка составляет целую секунду!) в переменную user будет записано другое значение? Тогда вызов неожиданно будет совсем не тот!
Следующее решение гарантирует, что такого не случится.
Решение 2: привязать контекст с помощью bind
Базовый синтаксис bind :
Все аргументы передаются исходному методу func как есть, например:
Теперь давайте попробуем с методом объекта:
Если у объекта много методов и мы планируем их активно передавать, то можно привязать контекст для них всех в цикле:
Некоторые JS-библиотеки предоставляют встроенные функции для удобной массовой привязки контекста, например _.bindAll(obj) в lodash.
Частичное применение
Полный синтаксис bind :
Это позволяет привязать контекст this и начальные аргументы функции.
Например, у нас есть функция умножения mul(a, b) :
Это называется частичное применение – мы создаём новую функцию, фиксируя некоторые из существующих параметров.
В следующем коде функция triple умножает значение на три:
Для чего мы обычно создаём частично применённую функцию?
В других случаях частичное применение полезно, когда у нас есть очень общая функция и для удобства мы хотим создать её более специализированный вариант.
Частичное применение без контекста
Встроенный bind не позволяет этого. Мы не можем просто опустить контекст и перейти к аргументам.
Также есть готовый вариант _.partial из библиотеки lodash.
Итого
Когда мы привязываем аргументы, такая функция называется «частично применённой» или «частичной».
Частичное применение удобно, когда мы не хотим повторять один и тот же аргумент много раз. Например, если у нас есть функция send(from, to) и from всё время будет одинаков для нашей задачи, то мы можем создать частично применённую функцию и дальше работать с ней.