HTML / CSSJavaScriptNode jsПаттерны проектированияПрактические

Контекст вызова

Что такое контекст?

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

context

Мы знаем, что 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.

global scope
ℹ️Примечание

Оператор new создает экземпляр объекта. Контекст функции будет установлен на созданный экземпляр объекта.

Контекст - для функций 2-го уровня

1var person = {
2 name:'John',
3 me:function(){
4 return this
5 },
6 birthday:{
7 day:11,
8 my:function(){
9 return this
10 }
11 }
12 }
13
14console.log(person.me() === person) // true
15
16console.log(person.birthday.my() === person) // false
17
18console.log(person.birthday.my() === person.birthday) //true

Неявное связывание (Implicit Binding)

Когда функция определяется глобально и используется в рамках объекта.

1function foo(){
2 return this
3}
4
5var obj = {
6 method:foo
7}
8
9console.log(obj.method() === obj) //true
10console.log(obj.method() === window) // false

Из вышеописанного мы получаем, что значение this зависит от вызываемой функции, а не от того, где функция определена.

Как контекст ведет себя в «use strict»?

При использовании use strict в функции контекст, т.e this, ведет себя иначе. Контекст остается тем, к чему он был призван.

1function foo(){
2 'use strict'
3 return this;
4}
5
6console.log(foo() === window) /// false
7console.log(foo() === undefined) /// true
8console.log(window.foo() === window) /// true

Как контекст ведет себя в контексте выполнения (execution context)?

Контекст выполнения - это «среда» или область действия, в которой выполняется функция. Каждый раз, когда вызывается функция, создается новый контекст выполнения. Каждый вызов контекста выполнения имеет 2 этапа

  1. Создание - при вызове функции
  2. Активация - при выполнении функции

Значение this определяется на этапе создания, а не во время выполнения. Однако правило определения this остается прежним.

Чем контекст отличается от области видимости?

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

ℹ️Важно

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

Как явно изменить контекст? (explicitly binding)

Мы можем динамически изменять контекст любого метода, используя методы call, apply и bind.

Call - первый аргумент, который принимает call, - это контекст, который вы хотите использовать. После этого вы можете передать любое количество значений, разделенных запятыми.

1foo.call(context, param1, param2, param3 );
1function sayName(){
2return this.name
3}
4
5console.log("my name is", sayName.call({name:"John"}))
6console.log("my name is", sayName.call({name:"Bob"}))
7
8// С параметрами
9
10function sayFullName(lastName){
11 return this.name + lastName
12}
13
14console.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}
5
6function greet(greeting, message) {
7 return `${greeting} ${this.firstName}. ${message}`;
8}
9const result = greet.apply(person, ['Hello', 'How are you?']);
10
11console.log(result); // Hello John. How are you?

Bind - возвращает новую функцию, которая навсегда привязана к первому аргументу bind, независимо от того, как функция используется. bind не вызывает связанную функцию сразу, а возвращает новую функцию, которую мы можем запустить позже.

1const person = {
2 firstName: 'John',
3 lastName: 'Doe'
4}
5
6function greet(greeting, message) {
7 return `${greeting} ${this.firstName}. ${message}`;
8}
9const result = greet.apply(person, ['Hello', 'How are you?']);
10
11console.log(result); // Hello John. How are you?
12
13
14var greatPerson = greet.bind(person)
15
16console.log(greatPerson("Hello","How are you?"))
17console.log(greatPerson("Hello","How is going?"))
18console.log(greatPerson("Hello","What do you think?"))
❗️Важно
  1. call/apply вызывает функцию с заданным контекстом, а bind, создаёт "обёртку" над функцией, которая подменяет контекст этой функции.

  2. bind - не вызывает функцию, а лишь возвращает "обёртку", которую можно вызвать позже. call/apply - вызывает функцию сразу и возвращает ее результат.

  3. call/apply - позднее связывание, bind - раннее связывание.

Зачем нам нужно явно менять контекст? 🤔

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

  2. Каррирование и частичное применение - это еще одна часть, в которой используется явное изменение контекста.

  3. Чтобы сделать служебные функции вроде

1function findMax(arr){
2 return Math.max.apply(null,arr)
3}
4
5console.log(findMax([1,11,21,22,44,2,33])) // 44
  1. Наследование - это еще одно место, где можно использовать явное изменение контекста.

В каких случаях нам нужно учитывать контекст?

Мы можем потерять контекст, т.е. получить для него неопределенное значение.

  1. Вложенные функции
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}
13
14obj.exec()

Результат:

nested context

Нам нужно сохранить контекст объекта obj, на который ссылается функция callback, когда вызывается как описано выше.тк не происходит, и мы получаем ошибку. Мы можем избавиться от указанной выше ошибки, заменив код exec на приведенный ниже

1// bind method
2exec: function () {
3 this.f2(function () {
4 this.f1();
5 }.bind(this));
6}
7
8//
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 : 23
5};
6
7function printFullName()
8{
9 console.log(this.firstName + " " + this.lastName);
10}
11
12function printDetails()
13{
14 console.log(this.firstName + " is " + this.age + " years old");
15}
16
17var boundPrintFullName;
18var boundPrintDetails;
19
20boundPrintFullName();
21boundPrintDetails();

Задание :two:

Создайте объект calc с тремя методами

  • read() запрашивает prompt два значения и сохраняет их как свойства объекта;
  • sum() возвращает сумму двух значений;
  • multiply() возвращает произведение двух значений.
1var calc = {
2 sum: function() {
3 //
4 },
5
6 multiply: function() {
7 //
8 },
9
10 read: function() {
11 //
12 }
13}
14
15calc.read();
16console.log(calc.sum());
17console.log(calc.multiply());
Hello