Привет, Хабр! Эту статью написал Тарас Голомозый, fullstack web-разработчик и преподаватель в школе программирования Elbrus Bootcamp. В своей практике он часто сталкивается с кейсами выпускников, которых на собеседовании просят рассказать о роли ключевого слова this в JavaScript. Простого определения про ссылку на контекст часто оказывается недостаточно, требуется более глубокое погружение в тему. В этой статье он на нескольких примерах разбирает, в каких ситуациях может пригодиться это ключевое слово и как используется call, apply и bind.

В общем смысле this — это ссылка на определенный контекст внутри объекта. Рассмотрим, что это означает, на примере.

Создадим объект car с набором свойств: моделью и годом выпуска автомобиля.  

const car = {
  model: 'Toyota',
  year: 2007,
    }

Для того, чтобы обратиться к объекту извне, достаточно выбрать имя переменной и через точку выбрать любое свойство: например, model

console.log (car.model)

В консоли мы увидим модель автомобиля: Toyota.

Обратиться к объекту изнутри сложнее: находясь внутри фигурных скобок, мы не знаем, куда будет записан объект и будет ли записан вообще. Попробуем сделать это, создав внутри объекта функцию showModel. Ее единственная задача — показывать модель автомобиля: 

const car = {
  model: 'Toyota',
  year: 2007,
  showModel: function(model, year){
  console.log(this.model);
  }
};

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

Теперь из объекта car можно вызвать showModel — метод покажет модель автомобиля, Toyota. 

Допустим, у нас есть еще один автомобиль. Создадим новый объект и назовем его anotherCar. У этого объекта будут те же свойства: model и year.

const anotherCar = {
  model: 'Benz',
  year: 1998
 showModel: function(model, year){
  console.log(this.model);
  }
};

Если скопировать функцию showModel с this внутрь нового объекта, то при вызове anotherCar.showModel, то в консоли мы увидим модель той машины, внутри которой вызывается этот метод. В данном случае — Benz.

Исходя из этих примеров, можно сказать, что this — это ссылка на объект, внутри которого находится это ключевое слово. 

Важно отметить, что функции бывают обычными (как в этом примере) и стрелочными. Если в этом примере заменить функцию на стрелочную, в консоли мы увидим undefined. Дело в том, что стрелочные функции не имеют своего this.

Call, apply и bind

Что делать, если у второго автомобиля нет метода, который показывает его модель? Можем ли мы использовать функцию с вызовом модели первого автомобиля? Да, для этого в JavaScript есть специальные ключевые слова: call, apply и bind. 

Начнем с call. Вызываем из первой машины метод car.showModel, используя call. В качестве аргумента ключевого слова указываем название объекта, в который записан второй автомобиль, anotherCar. В консоли мы увидим Benz.

То же самое можно сделать с помощью метода apply, просто меняя ключевое слово: car.showModel.apply(anotherCar);. Результат будет таким же: в консоли мы увидим Benz.  

Возникает вопрос — чем call отличается от apply? Разница между ними в формате передачи параметров. Рассмотрим пример: функция showModel, кроме вывода модели автомобиля, может принимать еще какие-то параметры: например, цвет автомобиля и тип двигателя.

const car = {
  model: 'Toyota',
  year: 2007,
  showModel: function(color, engine){
    console.log(this.model, color, engine);
  }
};

Используя apply, новые параметры можно передать в виде массива: например, автомобиль будет красным и с бензиновым двигателем: 

// car.showModel.apply(anotherCar, ['red', 'diesel']);

В консоли мы увидим, что это красный дизельный Benz. 

Call позволяет передать те же параметры не в виде массива, а простым перечислением:// car.showModel.call(anotherCar, 'green', 'gas').

Третий метод, bind, позволяет создать новую функцию и записать ее в переменную. Выглядеть это будет так:

const modelShower = car.showModel.bind(anotherCar);
modelShower('black', 'diesel');

Функция modelShower вызывает метод showModel, а bind привязывает к нему контекст другого автомобиля — в нашем случае это anotherCar.Таким образом, в переменной modelShower появилась функция, способная показывать контекст anotherCar так, будто она написана внутри самого anotherCar.

Передадим в функцию цвет и параметры двигателя: 

modelShower('black', 'diesel');

В выводе мы, как и ожидалось, получим черный дизельный Benz.

Заключение

Рады, если теперь вам удалось разобраться, что такое this в JavaScript, в чем разница между call и apply и для чего нужен bind. Если у вас появились вопросы или уточнения, пишите их в комментариях!

Комментарии (3)


  1. MaryRabinovich
    23.11.2022 11:32

    По оформлению кода немного занудства: в первом примере с this вы передаёте в функцию лишние на этом этапе параметры. Вы их пока никак не используете внутри функции. Ну и дальше в примере с apply почему-то закомментированный код. И пример выводится без подсветки, серым.


    1. domovoi89 Автор
      23.11.2022 11:49
      +1

      Спасибо за уточнения! Все поправим, согласен


  1. iliazeus
    23.11.2022 13:10
    +1

    Примеры вышли чересчур синтетическими. Из них не совсем понятно, в каких случаях нужны call/apply/bind в реальной жизни.

    Исходя из этих примеров, можно сказать, что this — это ссылка на объект, внутри которого находится это ключевое слово.

    Так думать опасно:

    Welcome to Node.js v19.1.0.
    Type ".help" for more information.
    > var foo = { prop: 123, f: function () { return this.prop } } // this "внутри" foo
    undefined
    > var bar = { prop: 456 }
    undefined
    > bar.f = foo.f
    [Function: f]
    > bar.f() // но вернула bar.prop
    456
    

    Проще и правильнее всего думать о this как о том объекте, на котором метод вызвали. Несколько более глубокое объяснение уже есть на Хабре.