Распространенное заблуждение при вызове функции - это считать, что контекст this во внутренней функции такой же, что и во внешней функции.
Контекст внутренней функции (кроме стрелочной функции) зависит только от ее собственного типа вызова, но не от контекста внешней функции.
Чтобы присвоить к this желаемое значение, измените контекст внутренней функции с помощью непрямого вызова (используя .call() или .apply(), см. главу 5) или создайте связанную функцию (используя .bind(), см. главу 6).
В следующем примере вычисляется сумма двух чисел:
const numbers = {
numberA: 5,
numberB: 10,
sum: function() {
console.log(this === numbers); // => true
function calculate() {
// this в этой функции равно объекту window или undefined для строгого режима
console.log(this === numbers); // => false
return this.numberA + this.numberB;
}
return calculate();
}
};
numbers.sum(); // => получим NaN или ошибку TypeError для строгого режима
numbers.sum() - это вызов метода объекта (см. главу 3), поэтому для функции sum() this равен объекту numbers. Если определить для sum метод calculate, то для numbers.sum.calculate() this будет равен numbers.sum.
В примере выше calculate() - это вызов функции (а не вызов метода), таким образом, здесь this - это глобальный объект window (глава 2.1) или undefined для строгого режима (глава 2.2). Даже если внешняя функция numbers.sum() имеет контекст numbers, на функцию calculate() она не имеет никакого влияния.
Результатом вызова numbers.sum() является NaN или ошибка TypeError, и это определенно не ожидаемый результат 15. Все потому, что calculate() не вызывается правильно.
Чтобы решить проблему, функция calculate() должна выполняться с тем же контекстом, что и метод numbers.sum() для доступа к свойствам this.numberA и this.numberB.
Одно из решений - вручную изменить контекст для calculate() на нужный, вызвав calculate.call(this) (непрямой вызов функции, см. главу 5):
const numbers = {
numberA: 5,
numberB: 10,
sum: function() {
console.log(this === numbers); // => true
function calculate() {
console.log(this === numbers); // => true
return this.numberA + this.numberB;
}
// используем .call() метод для изменения контекста
return calculate.call(this);
}
};
numbers.sum(); // => 15
calculate.call(this) отрабатывает функцию calculate() обычным образом, но при этом дополнительно изменяет контекст на значение, указанное в качестве первого параметра.
Теперь this.numberA + this.numberB совпадает с numbers.numberA + numbers.numberB. Функция возвращает ожидаемый результат 5 + 10 = 15.
Другое, немного лучшее решение - использовать стрелочную функцию:
const numbers = {
numberA: 5,
numberB: 10,
sum: function() {
console.log(this === numbers); // => true
const calculate = () => {
console.log(this === numbers); // => true
return this.numberA + this.numberB;
}
return calculate();
}
};
numbers.sum(); // => 15
Стрелочная функция решает вопрос с this лексически, или, другими словами, не имеет собственного, а значит использует значение this метода numbers.sum().