Контекст вызова
Что такое контекст?
Контекст - это значение ключевого слова this
, которое является ссылкой на объект, который «владеет» текущим исполняемым кодом или функцией, на которую он смотрит.
Мы знаем, что window
является глобальным объектом в браузере, поэтому, если мы введем его в консоли, он должен вернуть объект окна, что он и сделает.
Примечание
значение ключевого слова this
зависит от объекта, на котором функция запускается / вызывается / работает.
Поэтому ключевое слово this
имеет разные значения в зависимости от того, где оно используется.
Примечание
this
и context
взаимозаменяемы.
Контекст - глобально и внутри функции.
1function foo(){2console.log('foo context',this)3}
foo
- это функция, определенная на глобальном уровне и вызываемая на объекте глобального уровня, то есть window
, поэтому вызов foo
и window.foo
одинаков.
Следовательно, контекст - это объект window
.
Если мы вызовем foo
с помощью ключевого слова new
т.e new foo ()
на глобальном уровне, то получим это как объект foo
.
Примечание
Оператор new
создает экземпляр объекта. Контекст функции будет установлен на созданный экземпляр объекта.
Контекст - для функций 2-го уровня
1var person = {2 name:'John',3 me:function(){4 return this5 },6 birthday:{7 day:11,8 my:function(){9 return this10 }11 }12 }1314console.log(person.me() === person) // true1516console.log(person.birthday.my() === person) // false1718console.log(person.birthday.my() === person.birthday) //true
Неявное связывание (Implicit Binding)
Когда функция определяется глобально и используется в рамках объекта.
1function foo(){2 return this3}45var obj = {6 method:foo7}89console.log(obj.method() === obj) //true10console.log(obj.method() === window) // false
Из вышеописанного мы получаем, что значение this
зависит от вызываемой функции, а не от того, где функция определена.
Как контекст ведет себя в «use strict»?
При использовании use strict в функции контекст, т.e this
, ведет себя иначе.
Контекст остается тем, к чему он был призван.
1function foo(){2 'use strict'3 return this;4}56console.log(foo() === window) /// false7console.log(foo() === undefined) /// true8console.log(window.foo() === window) /// true
Как контекст ведет себя в контексте выполнения (execution context)?
Контекст выполнения - это «среда» или область действия, в которой выполняется функция. Каждый раз, когда вызывается функция, создается новый контекст выполнения. Каждый вызов контекста выполнения имеет 2 этапа
- Создание - при вызове функции
- Активация - при выполнении функции
Значение this
определяется на этапе создания, а не во время выполнения.
Однако правило определения this
остается прежним.
Чем контекст отличается от области видимости?
Области видимости и контекст - это совершенно разные концепции, но обычно они используются как взаимозаменяемые. Область видимости - это доступность переменных, функций или объектов в определенной части вашего кода во время выполнения. Подробнее об области видимости здесь.
Важно
Каждый вызов функции имеет как область видимости, так и связанный с ним контекст.
Как явно изменить контекст? (explicitly binding)
Мы можем динамически изменять контекст любого метода, используя методы call
, apply
и bind
.
Call
- первый аргумент, который принимает call
, - это контекст, который вы хотите использовать. После этого вы можете передать любое количество значений, разделенных запятыми.
1foo.call(context, param1, param2, param3 );
1function sayName(){2return this.name3}45console.log("my name is", sayName.call({name:"John"}))6console.log("my name is", sayName.call({name:"Bob"}))78// С параметрами910function sayFullName(lastName){11 return this.name + lastName12}1314console.log("my fullname is", sayFullName.call({name:"John"},'Doe'))15console.log("my fullname is", sayFullName("Cruz"))
Apply
- то же самое, что и call
, но фундаментальное различие между ними заключается в том, что функция call()
принимает список аргументов, в то время как функция apply()
принимает единичный массив аргументов.
1func.apply( context,[param1, param2, param3]);
1const person = {2 firstName: 'John',3 lastName: 'Doe'4}56function greet(greeting, message) {7 return `${greeting} ${this.firstName}. ${message}`;8}9const result = greet.apply(person, ['Hello', 'How are you?']);1011console.log(result); // Hello John. How are you?
Bind
- возвращает новую функцию, которая навсегда привязана к первому аргументу bind
, независимо от того, как функция используется.
bind
не вызывает связанную функцию сразу, а возвращает новую функцию, которую мы можем запустить позже.
1const person = {2 firstName: 'John',3 lastName: 'Doe'4}56function greet(greeting, message) {7 return `${greeting} ${this.firstName}. ${message}`;8}9const result = greet.apply(person, ['Hello', 'How are you?']);1011console.log(result); // Hello John. How are you?121314var greatPerson = greet.bind(person)1516console.log(greatPerson("Hello","How are you?"))17console.log(greatPerson("Hello","How is going?"))18console.log(greatPerson("Hello","What do you think?"))
Важно
call
/apply
вызывает функцию с заданным контекстом, аbind
, создаёт "обёртку" над функцией, которая подменяет контекст этой функции.bind
- не вызывает функцию, а лишь возвращает "обёртку", которую можно вызвать позже.call
/apply
- вызывает функцию сразу и возвращает ее результат.call
/apply
- позднее связывание,bind
- раннее связывание.
Зачем нам нужно явно менять контекст? 🤔
Когда нам нужно вызвать функцию, определенную внутри объекта, скажем,
x
, но на других объектах,скажемy
, используя явные методы привязки контекста для этого, чтобы увеличить возможность повторного использования функций.Каррирование и частичное применение - это еще одна часть, в которой используется явное изменение контекста.
Чтобы сделать служебные функции вроде
1function findMax(arr){2 return Math.max.apply(null,arr)3}45console.log(findMax([1,11,21,22,44,2,33])) // 44
- Наследование - это еще одно место, где можно использовать явное изменение контекста.
В каких случаях нам нужно учитывать контекст?
Мы можем потерять контекст, т.е. получить для него неопределенное значение.
- Вложенные функции
1var obj ={2 f1:function(){},3 f2:function(callback){4 callback()5 },6 exec:function(){7 this.f2(function callback(){8 console.log('this',this)9 this.f1()10 })11 }12}1314obj.exec()
Результат:
Нам нужно сохранить контекст объекта obj
, на который ссылается функция callback
, когда вызывается как описано выше.тк не происходит, и мы получаем ошибку.
Мы можем избавиться от указанной выше ошибки, заменив код exec
на приведенный ниже
1// bind method2exec: function () {3 this.f2(function () {4 this.f1();5 }.bind(this));6}78//9exec: function () {10 var that = this;11 this.f2(() => {12 that.f1();13 });14}15}
Важно
Любая ссылка на функцию (присвоение значения, передача в качестве аргумента) теряет исходную привязку функции.
Практика 👨💻👩💻
Задание :one:
Создать связанные копии printFullName и printDetails.
1var person = {2 firstName : "John",3 lastName : "Smith",4 age : 235};67function printFullName()8{9 console.log(this.firstName + " " + this.lastName);10}1112function printDetails()13{14 console.log(this.firstName + " is " + this.age + " years old");15}1617var boundPrintFullName;18var boundPrintDetails;1920boundPrintFullName();21boundPrintDetails();
Задание :two:
Создайте объект calc
с тремя методами
- read() запрашивает
prompt
два значения и сохраняет их как свойства объекта; - sum() возвращает сумму двух значений;
- multiply() возвращает произведение двух значений.
1var calc = {2 sum: function() {3 //4 },56 multiply: function() {7 //8 },910 read: function() {11 //12 }13}1415calc.read();16console.log(calc.sum());17console.log(calc.multiply());