Что такое контекст функции javascript
Перейти к содержимому

Что такое контекст функции javascript

  • автор:

Контекст выполнения и лексическая среда в JavaScript

Так как Javascript является однопоточным (single-threaded), в любой момент времени может быть запущен только один контекст выполнения!

Каждый раз, когда запускаем функцию, мы добавляем скобки <> , а затем выполняем или запускаем её.

// глобальный контекст выполнения function printMyName ()  // новый контекст выполнения return `Alex`; > function sayMyName ()  // новый контекст выполнения return printMyName(); > sayMyName(); // глобальный контекст выполнения

Как только движок JavaScript увидит эти скобки <> , он создаст контекст выполнения.

Первое, что сделает движок JavaScript — создаст глобальный контекст выполнения, и тем самым даст нам две вещи: глобальный объект (global object), и ключевое слово this . В браузере глобальный объект и this являются window .

window === this // true

У нас есть доступ к этим двум вещам, потому что браузер создал для нас глобальный контекст выполнения. Это самый первый шаг, который движок JavaScript делает для нас и эти два объекта будут равны друг другу.

Если мы используем что-то вроде NodeJS , глобальный объект не будет window . Вместо этого он будет называться global .

Поэтому, как только в нашем движке JavaScript будет сделана первая фаза — фаза создания, наступит вторая — фаза исполнения, в которой мы, фактически, запустим наш код.

Лексическая среда (Lexical Environment)

Лексическая среда означает, место где код был написан.

Каждый раз, когда мы создаем контекст выполнения, он проверяет, где были написаны слова (функции) и их местоположение.

Если у нас функция, которая объявлена внутри другой функции printMyName , вторая функция написана в другой лексической среде — лексической среды первой функции printMyName .

// глобальная лексическая среда function printMyName()  // лексическая среда функции printMyName function()  console.log('Alex') > >

Лексическая среда означает место, где написан код, во время компиляции. И в зависимости от того, когда компилятор или интерпретатор увидит наш код, он будет знать разные вещи об этом коде.

Мне нравится думать об этом так: в javascript каждый раз, когда у нас есть функция, она создает для нас новый “мир” внутри этой функции. Мы попадаем в этот “мир” каждый раз, когда добавляем его в стек вызовов, и внутри этого “мира” мы можем делать разные вещи, иметь разную информацию. Также, эти “миры” могут общаться друг с другом по-разному.

Контекст выполнения говорит нам, какая лексическая среда работает в данный момент.

Лексическая среда — это место, в котором написан код. Таким образом лексическая область (lexical scope), представляет собой доступные данные и переменные, для которых была определена функция. То есть то место, где мы пишем функцию, определяет являются ли доступные ей переменные. Так что важно где мы создаем функцию, а не где мы её вызываем.

  • лексическая среда означает, место где код был написан;
  • глобальная среда является родительской средой для всех других сред, созданных в коде;
  • в браузере глобальная среда называется window ;
  • в NodeJS глобальная среда называется global .

Что такое контекст функции javascript

Контекст выполнения функции — это одно из фундаментальных понятий в JavaScript. Контекстом еще часто называют значение переменной this внутри функции. Также иногда путают понятия «контекст выполнения» и «область видимости» — это не одно и то же. Давайте разберемся с этими понятиями.

Каждый вызов функции имеет и область видимости, и переменную this, и контекст выполнения. Область видимости определяет доступ к переменным при вызове функции и является уникальной для каждого вызова. Значение переменной this — это ссылка на объект, который «вызывает» код в данный момент. Контекст выполнения содержит и область видимости, и аргументы функции, и переменную this.

Переменная this

Значение переменной this чаще всего определяется тем, как вызывается функция. Когда функция вызывается как метод объекта, переменная this приобретает значение ссылки на объект, который вызывает этот метод:

 var user = < name: 'John Smith', getName: function() < console.log(this.name); >>; user.getName(); // John Smith 

Тот же принцип применяется при вызове функции с оператором new, чтобы создать экземпляр объекта. При вызове таким образом, в качестве значения this в рамках функции будет установлена ссылка на вновь созданный объект, например:

 function test() < alert(this); >test(); // window new test(); // test 

Когда мы вызываем функцию как функцию (не как метод объекта), эта функция будет выполнена в глобальном контексте. Значением переменной this в данном случае будет ссылка на глобальный объект. Однако, если функция вызывается как функция в строгом режиме (strict mode) — значением this будет undefined.

Контекст выполнения

Код в JavaScript может быть одного из следующих типов:

  • eval-код — код, выполняющийся внутри функции eval();
  • код функции — код, выполняющийся в теле функции;
  • глобальный код — код, не выполняющийся в рамках какой-либо функции.

Когда интерпретатор JavaScript выполняет код, по умолчанию контекстом выполнения является глобальный контекст. Каждый вызов функции приводит к созданию нового контекста выполнения.

 var hello = 'Hello'; var user = function() < // контекст выполнения функции var name = 'John Smith'; var getName = function() < // контекст выполнения функции return name; >var sayHello = function() < // контекст выполнения функции console.log(hello + ', ' + getName()); >sayHello(); > user(); 

В данном примере мы имеем один глобальный контекст выполнения и 3 контекста выполнения функции.

Каждый раз, когда создается новый контекст выполнения, он добавляется в верхнюю часть стека выполнения. Браузер всегда будет выполнять код в текущем контексте выполнения, который находится на вершине стека выполнения. После завершения, контекст будет удален из верхней части стека и управление вернется к контексту выполнения ниже.

Основные вещи, которые необходимо помнить и понимать о контексте выполнения:

  • Однопоточность — JavaScript работает в однопоточном режиме, т.е. только одна операция может быть выполнена в определенный момент времени.
  • Синхронное выполнение кода — код выполняется синхронно, т.е. следующая операция не выполняется до завершения предыдущей.
  • Один глобальный контекст выполнения.
  • Бесконечное количество контекстов выполнения функции.
  • Каждый вызов функции создает новый контекст выполнения, даже если функция рекурсивно вызывает сама себя.

В интерпретаторе JavaScript каждое создание контекста выполнения происходит в два этапа: этап создания (когда функция только вызвана, но код внутри нее еще не выполняется) и этап выполнения. На этапе создания интерпретатор сначала создает объект переменных (также называемый объект активации), который состоит из всех переменных, объявлений функций и аргументов, определенных внутри контекста выполнения. Затем инициализируется область видимости, и в последнюю очередь определяется значение переменной this. На этапе выполнения внутренним переменным присваивается значение, код интерпретируется и выполняется.

Таким образом, контекст выполнения функции можно представить в виде следующего объекта:

 executionContextObj = < variableObject: < /* объект активации - состоит из параметров функции, внутренних переменных и объявлений функций */ >, scopeChain: < /* цепочка областей видимости - объект активации + все объекты активации родительских контекстов выполнения */ >, this: <> > 

Для каждого контекста выполнения существует своя цепочка областей видимости. Цепочка областей видимости контекста выполнения включает области видимости из предыдущих контекстов в стеке выполнения.

Т.е. каждый раз, когда мы пытаемся получить доступ к переменной в контексте выполнения функции, процесс поиска этой переменной начинается с собственной области видимости функции. Если переменная с таким именем в текущей области видимости не найдена, поиск продолжается в иерархии областей видимости.

Понятия области видимости и контекста выполнения очень важны и играют значительную роль в языке JavaScript. Их хорошее понимание важно для изучения ряда шаблонов проектирования, понимания работы замыканий, функций обратного вызова, частичного применения функций и других важных концепций JavaScript.

JavaScript. Контекст вызова функции

В JavaScript this — это текущий контекст исполнения функции, он определяется в момент вызова. Функцию можно вызвать четырьмя способами и каждый из них определяет свой контекст. Кроме того, режим strict также влияет на контекст исполнения. Рассмотрим каждый способ вызова функции и посмотрим, на что будет указывать this в каждом из них.

  1. Простой вызов функции — hello(‘Привет’)
  2. Вызов метода объекта — user.hello(‘Привет’)
  3. Вызов с указанием контекста — hello.call(user, ‘Привет’)
  4. Вызов конструктора — new User(‘Вася’)

1. Простой вызов функции

Здесь все просто — контекстом вызова будет глобальный объект window или undefined , в зависимости от режима strict .

function foo()  console.log(this); // window bar(); function bar()  console.log(this); // window > > foo();
'use strict'; function foo()  console.log(this); // undefined bar(); function bar()  console.log(this); // undefined > > foo();

2. Вызов метода объекта

Метод — это функция, хранящаяся в объекте. При вызове метода this — это объект, которому принадлежит метод.

var user =  name: 'Вася', hello: function ()  console.log('Привет,', this.name); > >; user.hello(); // Привет, Вася

Ловушка — потеря контекста

Метод объекта можно переместить в отдельную переменную. При вызове метода с использованием этой переменной можно подумать, что this — это объект, в котором определён метод. На самом деле, если метод вызван без объекта, происходит простой вызов функции, и this становится глобальным объектом window или undefined .

var user =  surname: 'Иванов', show: function ()  console.log('Фамилия', this.surname); > >; setTimeout(user.show, 1000); // Фамилия undefined

Фактически, последняя строка может быть переписана как:

var f = user.show; setTimeout(f, 1000); // контекст user потерян

Метод setTimeout() в браузере имеет особенность — он устанавливает this=window для вызова функции. Таким образом, для this.surname он пытается получить window.surname , которого не существует. В других подобных случаях this обычно просто становится undefined .

Самый простой способ обойти ловушку — это обернуть вызов в анонимную функцию, создав замыкание:

var user =  surname: 'Иванов', show: function ()  console.log('Фамилия', this.surname); > >; setTimeout(function ()  user.show(); >, 1000); // Фамилия Иванов

Теперь объект user достаётся из замыкания, а затем вызывается его метод show() .

3. Вызов с указанием контекста

Методы call() и apply() объекта Function позволяют явно указать this .

func.call(context, arg1, arg2, ...);

При этом вызывается функция func , первый аргумент call становится её this , а остальные передаются «как есть».

func.apply(context, [arg1, arg2]);

Вызов функции при помощи func.apply работает аналогично func.call , но принимает массив аргументов вместо списка.

function showFullName()  console.log(this.firstName + ' ' + this.lastName); > var user =  firstName: 'Василий', lastName: 'Иванов' >; // функция вызовется с this=user showFullName.call(user) // Василий Иванов

4. Вызов конструктора

Функции-конструкторы являются обычными функциями. Но есть два соглашения:

  • Имя функции-конструктора должно начинаться с большой буквы
  • Функция-конструктор должна вызываться при помощи оператора new
function User(name)  this.name = name; this.isAdmin = false; > let user = new User('Вася'); alert(user.name); // Вася alert(user.isAdmin); // false

Когда функция вызывается как new User(…) , происходит следующее:

  • Создаётся новый пустой объект, и он присваивается this
  • Выполняется код функции. Обычно он модифицирует this , добавляя туда новые свойства
  • Возвращается значение this

Другими словами, вызов new User(…) делает примерно вот что:

function User(name)  // this = <>; (неявно) // добавляет свойства к this this.name = name; this.isAdmin = false; // return this; (неявно) >

Вместо заключения

Мы уже видели пример потери this . Как только метод передаётся отдельно от объекта — this теряется.

var user =  surname: 'Иванов', show: function ()  console.log('Фамилия', this.surname); > >; setTimeout(user.show, 1000); // Фамилия undefined

В современном JavaScript у функций есть встроенный метод bind , который позволяет зафиксировать this .

var user =  surname: 'Иванов', show: function ()  console.log('Фамилия', this.surname); > >; user.show.bind(user) setTimeout(user.show, 1000); // Фамилия Иванов
  • React и Redux вместе. Часть 7 из 7
  • React и Redux вместе. Часть 6 из 7
  • React и Redux вместе. Часть 5 из 7
  • React и Redux вместе. Часть 4 из 7
  • React и Redux вместе. Часть 3 из 7
  • React и Redux вместе. Часть 2 из 7
  • React и Redux вместе. Часть 1 из 7

Каталог оборудования

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

Производители

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

Функциональные группы

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

Категории блога
Облако тегов

  • 1С:Предприятие (31)
  • API (29)
  • Bash (43)
  • CLI (122)
  • CMS (139)
  • CSS (50)
  • Frontend (75)
  • HTML (66)
  • JavaScript (150)
  • Laravel (72)
  • Linux (169)
  • MySQL (76)
  • PHP (125)
  • React.js (66)
  • SSH (27)
  • Ubuntu (69)
  • Web-разработка (509)
  • WordPress (73)
  • Yii2 (69)
  • БазаДанных (95)
  • Битрикс (66)
  • Блог (29)
  • Верстка (43)
  • ИнтернетМагаз… (84)
  • КаталогТоваров (87)
  • Класс (30)
  • Клиент (28)
  • Ключ (28)
  • Команда (87)
  • Компонент (60)
  • Конфигурация (64)
  • Корзина (32)
  • ЛокальнаяСеть (32)
  • Модуль (34)
  • Навигация (31)
  • Настройка (141)
  • ПанельУправле… (29)
  • Плагин (33)
  • Пользователь (26)
  • Практика (101)
  • Сервер (75)
  • Событие (27)
  • Теория (106)
  • Установка (66)
  • Файл (51)
  • Форма (58)
  • Фреймворк (192)
  • Функция (36)
  • ШаблонСайта (68)

Контекст выполнения. Основы

Лексическое окружение позволяет сформировать доступный нам набор переменных и функций, учитывая вложенность кода. Но по ходу выполнения программы этот набор данных изменяется, более того у каждой вызванной функции своё собственное лексическое окружение. Каким образом можно отследить происходящие в лексическом окружении изменения, а также определить какое лексическое окружение является текущим, соответствующим данному этапу выполнения кода? Для этих целей используется контекст выполнения.

Контекст выполнения (execution context) в JavaScript используется для того, чтобы отслеживать ход выполнения кода. Именно с его помощью определяется доступное окружение на текущем этапе выполнения программы. А также контекст выполнения содержит в себе дополнительные параметры, которые формируются самостоятельно JavaScript-движком при обработке вашего кода.

Контекст выполнения тоже является абстрактным механизмом спецификации, как и лексическое окружение, к которому невозможно напрямую обратиться или изменить из программы. По сути он представляет собой некую обертку для выполняемого кода, содержащую определенные вспомогательные компоненты для отслеживания состояния программы, к некоторым из которых, мы можем обратиться из нашего кода.

Одним из таких динамически устанавливаемых параметров, к которому можно напрямую обратиться из кода и получить доступ к определенному набору данных в рамках текущего контекста выполнения, является ключевое слово this. Основным назначением ключевого слова this является переиспользование связанного с ним кода в рамках различных окружений. Значение, на которое ссылается ключевое слово this в том или ином месте программы определяется самим местом и способом создания текущего контекста выполнения. Более подробно механизм this будет рассмотрен в последующих частях курса.

Первым контекстом выполнения, который создаётся при запуске JavaScript скрипта является глобальный контекст выполнения (Global Execution Context). В рамках этого контекста будет обрабатываться весь глобальный код программы. Соответственно текущим Лексическим окружением, связанным с глобальным контекстом выполнения, является глобальное окружение Global Environment.

В рамках глобального контекста JavaScript-движок создает глобальный объект (global object) c определенными внутренними свойствами, который будет доступен в любой точке программы. Глобальный объект global object может разнится в зависимости от среды выполнения кода. В рамках браузера глобальным объектом будет объект window . Но если мы рассмотрим глобальный объект в среде NodeJS, то им уже будет объект global . Ключевое слово this в рамках глобального контекста выполнения, будет ссылаться именно на этот глобальный объект.

Например, создадим пустой файл JavaScript и страницу html, загружающую этот файл. Файл JavaScript добавляется на страницу с помощью тега script

 src="путь_к_файлу"> 

Подробнее об этом теге можно прочитать здесь. Архив с уже готовыми исходными файлами доступен по этой ссылке.

Открыв эту html страницу в браузере и запустив Консоль Разработчика (F12), можно получить доступ к глобальному объекту, введя в консоли window this

window > Window postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, > 

Этот глобальный объект был создан JavaScript-движком, несмотря на то, что запускался абсолютно пустой скрипт. И в этом глобальном объекте уже есть определенный набор встроенных, доступных для нашего использования, методов. Развернув в Консоли Разработчика объект Window , можно увидеть все доступные методы, например те, что уже использовались нами ранее — alert и prompt .

Далее, так как в глобальном контексте выполнения ключевое слово this указывает на глобальный объект, то введя в консоли this , отобразится тот же самый глобальный объект window

this > Window postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, > 

Также в JavaScript есть такая особенность, что объявленные в глобальной области видимости функции и var переменные помещаются в свойства глобального объекта window :

// global scope var foo = "hello"; function bar() <> console.log(window.foo); // "hello" console.log(window.foo == this.foo); // true console.log(window.bar); // ƒ bar()<> console.log(window.bar == this.bar); // true 

Создатель JavaScript Брендан Эйх считает глобальный объект одним из своих «самых больших сожалений». Такая особенность глобального объекта отрицательно сказывается на производительности, значительно усложняет реализацию областей видимости переменных и приводит к меньшему модульности кода.

Глобальные переменные имеют два недостатка. Во-первых, части программного обеспечения, которые полагаются на глобальные переменные, подвержены побочным эффектам; они менее надежны, ведут себя менее предсказуемо и менее пригодны для повторного использования. Во-вторых, весь JavaScript на веб-странице имеют одни и те же глобальные переменные: ваш код, встроенные модули, код аналитики, кнопки социальных сетей и т. д. А значит, может возникнуть конфликт имен переменных в разных частях вашей программы.

Именно поэтому лучше стараться максимально избегать создания глобальных переменных и объявлять переменные в локальных областях видимости, где они будут непосредственно использоваться.

А также с выходом стандарта ES6, где появилось новое объявление переменных через const и let , объявление переменных через var начало устаревать и всё меньше используется в коде. И объявленные глобально переменные через const или let , уже не попадут в глобальный объект.

let newValue = "hello"; console.log(window.newValue); // undefined 

Еще одной особенностью является то, что если в коде присвоить значение какой-либо необъявленной ранее переменной и, если этот код выполняется не в строгом режиме “use strict”, то JavaScript не только не отобразит ошибку, о несуществующей переменной, но и создаст её для нас в глобальном объекте.

function foo()  // используется переменная undeclared, которая нигде ранее не объявлялась undeclared = 5; // необъявленная переменная undeclared будет добавлена в глобальный объект console.log(window.undeclared); // 5 > foo(); 

Эта особенность тоже носит негативные эффекты и может привести к возникновению ошибок в процессе выполнения программы. Поэтому переменные всегда необходимо объявлять для их последующего использования. В строгом режиме “use strict” этот код уже не выполнится и корректно отобразит ошибку

"use strict"; function foo()  undeclared = 5; // Uncaught ReferenceError: undeclared is not defined console.log(window.undeclared); > foo(); 

Когда в JavaScript программе кроме глобального кода есть еще вызов функций. Например

let value = "value from global scope"; function foo()  let fooValue = "value from foo function"; > foo(); 

То при каждом новом вызове функции создаётся свой контекст выполнения, при этом текущий контекст выполнения (тот, откуда была вызвана функция) приостанавливается.

В предыдущем примере, когда мы из глобального контекста вызываем функцию, обработка глобального контекста приостанавливается и управление переходит в контекст выполнения этой функции. Важно отметить, что в определенное время может обрабатываться только один контекст выполнения. Далее, после выполнения этой функции необходимо вернуться обратно в глобальный контекст, для выполнения следующего кода программы.

Чтобы хранить и отслеживать контексты выполнения они формируются в стек контекстов выполнения (call stack) — список контекстов, организованных по принципу «последним пришёл — первым вышел». Выполняемый контекст всегда является верхним элементом этого стека. После того, как необходимый код выполнится, связанный с ним контекст выполнения удалится, управление вернется в контекст, который находился элементом ниже, и теперь он будет верхним элементом — текущим контекстом выполнения.

Для предыдущего примера стек контекстов выполнения в ходе программы схематично можно показать так:

схема контекстов выполнения

На этой схеме голубым цветом обозначен текущий, выполняющийся в данный момент времени контекст. А серым — приостановленные контексты, ожидающие окончания выполнения текущего контекста.

Если функция была вызвана из другой функции

var x = 10; function foo()  var y = 20; function bar()  var z = y + x; > bar(); > foo(); 

то стек контекстов выполнения будет меняться так:

схема контекстов выполнения

Дата изменения: February 26, 2023

Поделиться

Обнаружили ошибку или хотите добавить что-то своё в документацию? Отредактируйте эту страницу на GitHub!

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *