24. JS с нуля, ваще с нуля (this, контекст вызова)

Оценить качество материала и подачу материала автором видео:

Front-end

Трудоустройтесь middle front-end разработчиком на React JS (TypeScript) за 12-16 месяцев обучения с ежедневной менторской поддержкой в формате видео 1 на 1 и коммерческими проектами в портфолио

Перейти на курс
Front-end

Back-end

Трудоустройтесь middle back-end разработчиком за 12-16 месяцев обучения с ежедневной менторской поддержкой в формате видео 1 на 1 и коммерческими проектами в портфолио

Перейти на курс
Back-end

Карьерный бустер

Получите коммерческий опыт на реальных стартапах, прокачайте tech & soft навыки, научитесь работать в команде, проходить собеседования и получите первую работу в IT!

Перейти на курс
Карьерный бустер

Основы Front-end

Сделайте первый шаг в IT, освоив базовые знания разработки и научившись создавать небольшие проекты на JavaScript

Перейти на курс
Основы Front-end

Основы Back-end

Сделайте первый шаг в IT, освоив базовые знания разработки. Без опыта. Без математики. Только практика: JavaScript, SQL, Node JS, база данных

Перейти на курс
Основы Back-end

This, контекст вызова

Автор конспекта: Stanislav

1. Проблема с this

👉

Мы ожидали, что this внутри метода объекта будет ссылаться на сам объект, но оно начинает ссылаться на кнопку, которая вызвала этот метод через обработчик события

Почему так происходит?

  1. this в JavaScript – это не просто ссылка на объект, которому принадлежит метод, а на контекст вызова метода.
  2. Когда мы вызываем метод напрямую у объекта (например, slider1.start()), this внутри этого метода действительно ссылается на сам объект slider1.
  3. Но когда метод объекта передается в addEventListener (например, для клика по кнопке), кнопка становится контекстом вызова. То есть, кнопка "вызывает методы от своего имени". Поэтому this внутри такого метода ссылается на саму кнопку.

2. Что такое this и почему оно меняется

Чтобы решить проблему, важно глубоко понять, что такое this в JavaScript и как определяется его значение.

👉

this является переменной, которая автоматически создаётся и переприсваивается браузером каждый раз, когда функция начинает работать. Браузер постоянно меняет значение this в зависимости от того, какой объект исполняет (вызывает) метод в данный момент

👉

Важно не то, у кого находится метод (кому он принадлежит), а в каком контексте этот метод вызывается. Например, если кнопка вызывает метод, принадлежащий slider1, this внутри метода будет ссылаться на кнопку, как будто у кнопки появился собственный метод.

2.1 Примеры, когда this ведет себя ожидаемо

  1. Когда мы вызываем человек1.говорить(), this внутри говорить будет ссылаться на человек1, потому что человек1 является контекстом вызова.
  2. Аналогично, при вызове car.start() или slider1.start(), this внутри метода будет ссылаться на car или slider1 соответственно, поскольку метод вызван явно у этих объектов.

2.2 Почему this меняется в обработчиках событий

  1. JavaScript — интересный язык программирования, который позволяет другому объекту вызывать методы от своего имени.
  2. Когда мы регистрируем button.addEventListener('click', obj.method), мы говорим кнопке: "Кнопка, когда по тебе кликнут, выполни этот метод".
  3. В этот момент кнопка (или браузер) вызывает метод obj.method "от своего имени".
  4. Контекстом вызова становится кнопка, и поэтому браузер автоматически "переприсваивает" this внутри метода obj.method этой кнопке. Метод как бы "забывает", что он принадлежал другому объекту.
  5. Браузер постоянно перезаписывает this: Как только функция начинает работать, браузер берет this и присваивает ему объект, который является контекстом вызова в данный момент.

3. Замыкания (Closures) и Функции-обёртки (Wrapper Functions)

Чтобы сохранить правильный контекст this, используется техника с замыканиями и функциями-обёртками

3.1 Сохранить ссылку на this

В начале метода start (или другого инициализирующего метода), где this корректно ссылается на наш объект (slider1), создаётся локальная переменная (например, that) и ей присваивается значение this. Эта переменная that "ухватилась" за наш объект slider1. В отличие от this, that не перезаписывается браузером.

slider.js
var slider1 = {
  imagesUrls: [],
  currentImageIndex: 0,
  showPrevBtn: document.getElementById("show-prev-btn"),
  showNextBtn: document.getElementById("show-next-btn"),
  slideImage: document.getElementById("slide-img"),
 
  start: function () {
    var that = this
 
    //subscribe to event
    this.showPrevBtn.addEventListener("click", this.onShowPrevBtnClick)
    this.showNextBtn.addEventListener("click", this.onShowNextBtnClick)
 
    this.imagesUrls.push("https://www.cstatic-images.com/car-pictures/main/USC50ALC051A021001.png")
    this.imagesUrls.push(
      "https://article.images.consumerreports.org/image/upload/w_652,f_auto,q_auto,ar_16:9,c_lfill/v1731947043/prod/content/dam/CRO-Images-2024/Cars/CR-Cars-InlineHero-10-Most-Satisfying-Cars-and-SUVs-1124",
    )
    this.imagesUrls.push(
      "https://hips.hearstapps.com/hmg-prod/images/pop-index-2020-chevrolet-corvette-c8-102-1571146873.jpg?crop=0.881xw:0.881xh;0.0358xw,0.116xh&resize=1200:*",
    )
    this.imagesUrls.push(
      "https://images.hgmsites.net/lrg/2021-chevrolet-corvette-2-door-stingray-convertible-w-3lt-angular-front-exterior-view_100835748_l.jpg",
    )
 
    this.slideImage.src = this.imagesUrls[this.currentImageIndex]
    this.showPrevBtn.disabled = true
  },
 
  onShowPrevBtnClick: function (e) {
    this.currentImageIndex--
    this.slideImage.src = this.imagesUrls[this.currentImageIndex]
    this.showNextBtn.disabled = false
 
    //disable prev button if need
    if (this.currentImageIndex === 0) {
      this.showPrevBtn.disabled = true
    }
  },
  onShowNextBtnClick: function (e) {
    this.currentImageIndex--
    this.slideImage.src = this.imagesUrls[this.currentImageIndex]
    this.showPrevBtn.disabled = false
 
    //disable next button if need
    if (this.currentImageIndex === this.imagesUrls.length - 1) {
      this.showNextBtn.disabled = true
    }
  },
}

3.2. Использовать анонимную функцию-обёртку и Замыкание (Closure)

Вместо прямой передачи метода объекта в addEventListener, передаётся анонимная функция. Внутри этой обёртки вызывается метод объекта, используя сохранённую ссылку that.

slider.js
var slider1 = {
  imagesUrls: [],
  currentImageIndex: 0,
  showPrevBtn: document.getElementById("show-prev-btn"),
  showNextBtn: document.getElementById("show-next-btn"),
  slideImage: document.getElementById("slide-img"),
 
  start: function () {
    var that = this
 
    //subscribe to event
    this.showPrevBtn.addEventListener("click", function (e) {
      that.onShowPrevBtnClick
    })
    this.showNextBtn.addEventListener("click", function (e) {
      that.onShowNextBtnClick
    })
 
    this.imagesUrls.push("https://www.cstatic-images.com/car-pictures/main/USC50ALC051A021001.png")
    this.imagesUrls.push(
      "https://article.images.consumerreports.org/image/upload/w_652,f_auto,q_auto,ar_16:9,c_lfill/v1731947043/prod/content/dam/CRO-Images-2024/Cars/CR-Cars-InlineHero-10-Most-Satisfying-Cars-and-SUVs-1124",
    )
    this.imagesUrls.push(
      "https://hips.hearstapps.com/hmg-prod/images/pop-index-2020-chevrolet-corvette-c8-102-1571146873.jpg?crop=0.881xw:0.881xh;0.0358xw,0.116xh&resize=1200:*",
    )
    this.imagesUrls.push(
      "https://images.hgmsites.net/lrg/2021-chevrolet-corvette-2-door-stingray-convertible-w-3lt-angular-front-exterior-view_100835748_l.jpg",
    )
 
    this.slideImage.src = this.imagesUrls[this.currentImageIndex]
    this.showPrevBtn.disabled = true
  },
 
  onShowPrevBtnClick: function (e) {
    this.currentImageIndex--
    this.slideImage.src = this.imagesUrls[this.currentImageIndex]
    this.showNextBtn.disabled = false
 
    //disable prev button if need
    if (this.currentImageIndex === 0) {
      this.showPrevBtn.disabled = true
    }
  },
  onShowNextBtnClick: function (e) {
    this.currentImageIndex--
    this.slideImage.src = this.imagesUrls[this.currentImageIndex]
    this.showPrevBtn.disabled = false
 
    //disable next button if need
    if (this.currentImageIndex === this.imagesUrls.length - 1) {
      this.showNextBtn.disabled = true
    }
  },
}
👉

Анонимная функция "замыкает" переменную that. Это означает, что даже когда эта функция будет вызвана кнопкой позже (при клике), она всё равно будет иметь доступ к переменной that из своей внешней области видимости. Таким образом, метод (onShowPrevBtnClick или onShowNextBtnClick) вызывается от имени самого объекта (slider1), что гарантирует, что this внутри onShowPrevBtnClick или onShowNextBtnClick будет нашим объектом.

Боевой маршрут (JS Ваще с нуля)

Видеоурок - 25 видео из 29