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

Функции вызываются различными способами и в каждом случае контекст вызова будет отличаться.

Стандартный вызов обычной функции


Рассмотрим стандартный вызов функции:

Код

function showThis() {
  console.log(this);
}

showThis();

В этом случае this ссылается на глобальный объект Window (в случае без 'use strict'). В случае если включен режим 'use strict', то this будет неопределён (undefined)

Это касается и случаев, когда внутри функции находится другая функция:

Код

function showThis(a,b) {
  console.log(this);
  function sum() {
  console.log(this);
  }

  sum();
}

showThis();

Для функции sum() контекст вызова будет аналогичным: Window либо undefined, в зависимости от того, какой режим включен, поскольку не имеет значения где запускается подобная функция.

Метод внутри объекта


Допустим, есть какой-то объект obj и внутри него какой-то метод sum:

Код

const obj = {
  a : 20,
  b : 13,
  sum : function() {
  console.log(this);
  }
};

obj.sum();

Если используется метод внутри объекта, то контекст вызова будет ссылаться на этот объект, то есть контекст у методов объекта - это сам объект.

А вот если внутри метода будет какая-то функция, назовём её shout(), и она внутри метода вызывается:

Код

const obj = {
  a : 20,
  b : 13,
  sum : function() {
  function shout() {
  console.log(this);
  }
  shout();
  }
};

obj.sum();

то её контекст вызова будет undefined, поскольку это простой вызов функции, не относящийся к методу

this в функциях-конструкторах и классах


Ранее мы с вами разбирали функции-конструкторы.
Так вот, внутри функции-конструктора контекст вызова для всех её свойств и методов будет только что созданный объект.

Возьмём функцию-конструктор:

Код

function User(name, id) {
  this.name = name;
  this.id = id;
  this.human = true;
}

и экземпляр объекта, созданный с её помощью:

Код

const ivan = new User('Ivan', 25);

Так вот, this внутри этой функции-конструктора каждый раз ссылается на этот экземпляр объекта, сохранённый в переменной ivan.

Присвоение this вручную для любой функции


Допустим, у нас есть какая-то функция sayName():

Код

function sayName() {
  console.log(this);
  console.log(this.name);
}

и какой-то объект, лежащий в переменной user:

Код

const user = {
  name: 'John'
};

Если сейчас всё оставить как есть и вызвать функцию sayName(), то контекст вызова её будет либо Window, либо undefined, мы об этом говорили ранее.

Как же сделать, чтобы её контекст поменялся в сторону user? Чтобы мы могли получить доступ к свойству name объекта?

Для этого существуют два метода: call() и apply().
Метод call() принимает список аргументов, а метод apply() массив аргументов, в этом их основное отличие.

Как они используются? Например, мы берём нашу функцию sayName и метод call(), а внутрь круглых скобок метода помещаем тот контекст вызова, который нам нужен, в данном случае user:

Код

sayName.call(user);

Тоже самое с методом apply():

Код

sayName.apply(user);

В консоли мы увидим следующий результат, он дважды повторяется потому что в данном случае одинаковый результат работы что у метода call(), что у метода apply():

Код

{ name: 'John' }
John
{ name: 'John' }
John

Разница между call() и apply() появляется, когда функция принимает дополнительные аргументы, допустим у нашей функции будет аргумент surname и мы его будем использовать внутри функции, чтобы выводить не только имя, но и фамилию пользователя:

Код

function sayName(surname) {
  console.log(this.name + surname);
}

const user = {
  name: 'John'
};

Нам этот аргумент нужно как-то передать, так вот, чтобы его передать, в методе call() мы значение для аргумента указываем просто через запятую:

Код

sayName.call(user, ' Smith' );

Если же используем apply(), то в виде массива:

Код

sayName.apply(user, [' Smith']);

В консоли в результате получим:

Код

{ name: 'John' }
John Smith
{ name: 'John' }
John Smith

И есть ещё один метод ручного присвоения this, он называется bind(). Метод bind() создаёт новую функцию, связанную с определённым контекстом.
Рассмотрим пример, напишем какую-то функцию удваивания:

Код

function count(num) {
  return this*num;
}

В данном случае нам не хватает контекста вызова, который бы умножался на num при вызове этой функции. Здесь мы можем использовать метод bind().

Создадим новую переменную double, в неё мы поместим новую функцию, взяв уже существующую функцию count и через точку добавляем bind(), передав в него контекст вызова число 2, чтобы любое число, которое придёт в эту функцию, удваивалось.

Код

const double = count.bind(2);

Таким образом, у нас теперь есть контекст вызова в виде числа 2, оно заменит собой this и при вызове функции, например, double(3), в консоли мы увидим результат умножения 3 * 2 = 6.
Важно понимать, что double() - это новая функция, у которой есть жёстко привязанный контекст в виде числа 2.

Обработчики событий и контекст вызова


Код

const btn = document.querySelector('button');

btn.addEventListener('click', function() {
  console.log(this);
});

В данном случае(при классическом написании функции в обработчике события) контекстом вызова будет сам элемент, на котором произошло событие (в данном случае кнопка).
По сути, this здесь будет тоже самое что и event.target

Ещё один пример с той же кнопкой:

Код

const btn = document.querySelector('button');

btn.addEventListener('click', function() {
  this.style.backgroundColor = 'red';
});

После того как этот код отработает, фон кнопки сменится на красный, потому как контекстом вызова будет сама кнопка.
Это равнозначно такому коду:

Код

const btn = document.querySelector('button');

btn.addEventListener('click', function() {
  btn.style.backgroundColor = 'red';
});

или такому:

Код

const btn = document.querySelector('button');

btn.addEventListener('click', function(e) {
  e.target.style.backgroundColor = 'red';
});


Стрелочные функции и контекст вызова


У стрелочной функции нет своего контекста вызова, контекст вызова она всегда берёт у своего родителя.

Допустим, есть какой-то объект obj и в нём метод sayNumber, внутри которого стрелочная функция say:

Код

const obj = {
  num: 5,
  sayNumber: function() {
  const say = () => {
  console.log(this);
  };

  say();
  }
};

obj.sayNumber();

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

Код

{ num: 5, sayNumber: [Function: sayNumber] }

именно поэтому, если мы напишем следующее:

Код

console.log(this.num);

то в консоли увидим результат 5, поскольку это будет равнозначно, как если бы мы написали:

Код

console.log(obj.num);


Если мы используем стрелочную функцию в обработчике события, то контекст вызова теряется, он будет undefined, например как в данном примере:

Код

const btn = document.querySelector('button');

btn.addEventListener('click', () => {
  this.style.backgroundColor = 'red';
});

Комментарии к материалу: