Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

this, call, apply и bind

Внутри метода слово this указывает на объект, у которого метод вызвали. Важная деталь: this определяется в момент вызова, а не когда функцию написали. Поэтому метод, переданный куда-то отдельно от объекта, может «потерять» свой this. Методы call, apply и bind позволяют задать его явно.

this зависит от вызова

Когда функцию вызывают через точку — user.greet(), — this внутри неё равен объекту слева от точки:

const user = {
  name: 'Анна',
  greet() {
    return `Привет, я ${this.name}`;
  },
};
 
console.log(user.greet()); // Привет, я Анна

Потеря this

Если сохранить метод в переменную и вызвать без объекта, связь с объектом пропадает. В классах this тогда равен undefined, и обращение к его свойству вызывает ошибку:

class User {
  constructor(name) {
    this.name = name;
  }
 
  greet() {
    return `Привет, я ${this.name}`;
  }
}
 
const anna = new User('Анна');
const greet = anna.greet;
 
try {
  greet();
} catch (error) {
  console.log(error.name); // TypeError
}

call

Метод call() вызывает функцию сразу и задаёт ей this — первым аргументом. Остальные аргументы передаются в функцию как обычно:

function introduce(city) {
  return `${this.name} из города ${city}`;
}
 
const oleg = { name: 'Олег' };
 
console.log(introduce.call(oleg, 'Рига')); // Олег из города Рига

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

apply

apply() делает то же самое, но аргументы функции принимает массивом:

function introduce(city, country) {
  return `${this.name}: ${city}, ${country}`;
}
 
const maria = { name: 'Мария' };
const place = ['Мадрид', 'Испания'];
 
console.log(introduce.apply(maria, place)); // Мария: Мадрид, Испания

Сегодня apply() встречается реже: вместо него пишут introduce.call(maria, ...place) с оператором spread.

bind

bind() не вызывает функцию, а возвращает новую, у которой this закреплён навсегда. Её можно передавать куда угодно:

const user = {
  name: 'Анна',
  greet() {
    return `Привет, я ${this.name}`;
  },
};
 
const greet = user.greet.bind(user);
 
console.log(greet()); // Привет, я Анна

Через bind() можно закрепить и первые аргументы. Если this не нужен, передают null:

const multiply = (a, b) => a * b;
const double = multiply.bind(null, 2);
 
console.log(double(5)); // 10

Стрелочные функции

У стрелочной функции нет своего this: она берёт его из окружающего кода. Поэтому внутри метода стрелка видит тот же объект, а call и bind на неё не действуют:

const counter = {
  count: 0,
  addAll(numbers) {
    numbers.forEach(() => {
      this.count++;
    });
    return this.count;
  },
};
 
console.log(counter.addAll([1, 2, 3])); // 3

Частая ошибка

Передать метод как колбэк — в таймер или обработчик события. Запись setTimeout(timer.tick, 100) передаёт функцию без объекта, и внутри tick пропадает this. Закрепите его через bind() или оберните вызов в стрелку:

class Timer {
  constructor() {
    this.seconds = 0;
  }
 
  tick() {
    this.seconds++;
    console.log(this.seconds);
  }
}
 
const timer = new Timer();
 
setTimeout(timer.tick.bind(timer), 100); // 1
setTimeout(() => timer.tick(), 200); // 2

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.