← JavaScript/Методы объекта и this#67 из 383← ПредыдущийСледующий →+25 XP
Полезно по теме:Гайд: как учить JavaScriptПрактика: JS базаПрактика: async и сетьТермин: Closure

Методы объекта и this

Реальная проблема: объект умеет работать с собой

В Spotify плеер знает свой текущий трек, громкость и состояние. Когда вы нажимаете «пауза», плеер обращается к своим же данным: this.isPlaying = false. Ключевое слово this внутри метода ссылается на объект, которому принадлежит этот метод. Без this методы не могли бы обращаться к данным своего объекта.

Что решает this

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

На основе предыдущих уроков

  • «Объекты» — создание объектов, свойства и методы
  • «Стрелочные функции» — стрелочные функции не имеют своего this
  • «Копирование объектов» — объекты передаются по ссылке
  • Базовый синтаксис

    const player = {
      track: 'Bohemian Rhapsody',
      volume: 80,
      isPlaying: false,
    
      play() {
        this.isPlaying = true
        console.log(`Играет: ${this.track}`)  // this = player
      },
    
      setVolume(level) {
        this.volume = Math.min(100, Math.max(0, level))
      }
    }
    
    player.play()  // 'Играет: Bohemian Rhapsody'
    console.log(player.isPlaying)  // true

    Цепочка вызовов (method chaining)

    Если метод возвращает this, можно вызывать методы цепочкой:

    const query = {
      table: '',
      conditions: [],
      _limit: null,
    
      from(table) {
        this.table = table
        return this  // возвращаем this для цепочки
      },
    
      where(condition) {
        this.conditions.push(condition)
        return this
      },
    
      limit(n) {
        this._limit = n
        return this
      },
    
      build() {
        let sql = `SELECT * FROM ${this.table}`
        if (this.conditions.length) sql += ` WHERE ${this.conditions.join(' AND ')}`
        if (this._limit) sql += ` LIMIT ${this._limit}`
        return sql
      }
    }
    
    console.log(
      query.from('users').where('age > 18').where('active = 1').limit(10).build()
    )
    // SELECT * FROM users WHERE age > 18 AND active = 1 LIMIT 10

    Потеря this — классическая ошибка

    Если метод отцепить от объекта и вызвать отдельно — this теряется:

    const user = {
      name: 'Алексей',
      greet() {
        return `Привет, я ${this.name}`
      }
    }
    
    const greet = user.greet    // копируем ссылку на функцию
    greet()  // 'Привет, я undefined' — this потерян!
    
    // В строгом режиме — TypeError: Cannot read properties of undefined

    this в стрелочных функциях

    Стрелочные функции не имеют своего this — они берут его из внешней области:

    const timer = {
      count: 0,
    
      start() {
        // ОШИБКА: обычная функция теряет this
        setInterval(function() {
          this.count++  // this здесь — undefined (strict) или window
        }, 1000)
    
        // ВЕРНО: стрелочная берёт this из start() — то есть timer
        setInterval(() => {
          this.count++  // this = timer
        }, 1000)
      }
    }

    Типичные ошибки

    Ошибка 1: передача метода как коллбэк

    // Сломано:
    const cart = {
      items: ['Ноутбук', 'Мышь'],
      prefix: 'Товар',
      printAll() {
        this.items.forEach(function(item) {
          console.log(this.prefix + ': ' + item)  // this потерян!
        })
      }
    }
    
    // Исправлено — стрелочная функция:
    printAll() {
      this.items.forEach(item => {
        console.log(this.prefix + ': ' + item)  // this = cart
      })
    }

    Ошибка 2: деструктуризация метода

    const { greet } = user  // то же что const greet = user.greet
    greet()  // this потерян!

    Ошибка 3: this в обычной функции (не методе)

    function show() {
      console.log(this)  // undefined в strict mode, window в браузере
    }
    show()  // вызов без объекта — this не определён как объект

    В реальных проектах

  • jQuery/DOM: $(this) в обработчиках событий — элемент на котором сработало событие
  • Vue.js: this.username в методах компонента — доступ к данным компонента
  • React: в классовых компонентах this.setState() — поэтому нужен .bind(this)
  • Express.js: this в методах маршрутизатора
  • Примеры

    Корзина покупок с цепочкой методов

    const cart = {
      items: [],
      discount: 0,
    
      add(product) {
        const existing = this.items.find(i => i.id === product.id)
        if (existing) {
          existing.qty++
        } else {
          this.items.push({ ...product, qty: 1 })
        }
        return this  // цепочка
      },
    
      remove(productId) {
        this.items = this.items.filter(i => i.id !== productId)
        return this
      },
    
      applyDiscount(percent) {
        this.discount = percent
        return this
      },
    
      subtotal() {
        return this.items.reduce((sum, i) => sum + i.price * i.qty, 0)
      },
    
      total() {
        return Math.round(this.subtotal() * (1 - this.discount / 100))
      },
    
      summary() {
        this.items.forEach(i => {
          // стрелочная — this = cart
          console.log(`${i.name} x${i.qty} = ${i.price * i.qty} ₽`)
        })
        console.log(`Итого: ${this.total()} ₽ (скидка ${this.discount}%)`)
      }
    }
    
    cart
      .add({ id: 1, name: 'Ноутбук', price: 75000 })
      .add({ id: 2, name: 'Мышь',    price: 1500  })
      .add({ id: 1, name: 'Ноутбук', price: 75000 })  // +1 к qty
      .applyDiscount(10)
      .remove(2)
    
    cart.summary()
    // Ноутбук x2 = 150000 ₽
    // Итого: 135000 ₽ (скидка 10%)

    Методы объекта и this

    Реальная проблема: объект умеет работать с собой

    В Spotify плеер знает свой текущий трек, громкость и состояние. Когда вы нажимаете «пауза», плеер обращается к своим же данным: this.isPlaying = false. Ключевое слово this внутри метода ссылается на объект, которому принадлежит этот метод. Без this методы не могли бы обращаться к данным своего объекта.

    Что решает this

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

    На основе предыдущих уроков

  • «Объекты» — создание объектов, свойства и методы
  • «Стрелочные функции» — стрелочные функции не имеют своего this
  • «Копирование объектов» — объекты передаются по ссылке
  • Базовый синтаксис

    const player = {
      track: 'Bohemian Rhapsody',
      volume: 80,
      isPlaying: false,
    
      play() {
        this.isPlaying = true
        console.log(`Играет: ${this.track}`)  // this = player
      },
    
      setVolume(level) {
        this.volume = Math.min(100, Math.max(0, level))
      }
    }
    
    player.play()  // 'Играет: Bohemian Rhapsody'
    console.log(player.isPlaying)  // true

    Цепочка вызовов (method chaining)

    Если метод возвращает this, можно вызывать методы цепочкой:

    const query = {
      table: '',
      conditions: [],
      _limit: null,
    
      from(table) {
        this.table = table
        return this  // возвращаем this для цепочки
      },
    
      where(condition) {
        this.conditions.push(condition)
        return this
      },
    
      limit(n) {
        this._limit = n
        return this
      },
    
      build() {
        let sql = `SELECT * FROM ${this.table}`
        if (this.conditions.length) sql += ` WHERE ${this.conditions.join(' AND ')}`
        if (this._limit) sql += ` LIMIT ${this._limit}`
        return sql
      }
    }
    
    console.log(
      query.from('users').where('age > 18').where('active = 1').limit(10).build()
    )
    // SELECT * FROM users WHERE age > 18 AND active = 1 LIMIT 10

    Потеря this — классическая ошибка

    Если метод отцепить от объекта и вызвать отдельно — this теряется:

    const user = {
      name: 'Алексей',
      greet() {
        return `Привет, я ${this.name}`
      }
    }
    
    const greet = user.greet    // копируем ссылку на функцию
    greet()  // 'Привет, я undefined' — this потерян!
    
    // В строгом режиме — TypeError: Cannot read properties of undefined

    this в стрелочных функциях

    Стрелочные функции не имеют своего this — они берут его из внешней области:

    const timer = {
      count: 0,
    
      start() {
        // ОШИБКА: обычная функция теряет this
        setInterval(function() {
          this.count++  // this здесь — undefined (strict) или window
        }, 1000)
    
        // ВЕРНО: стрелочная берёт this из start() — то есть timer
        setInterval(() => {
          this.count++  // this = timer
        }, 1000)
      }
    }

    Типичные ошибки

    Ошибка 1: передача метода как коллбэк

    // Сломано:
    const cart = {
      items: ['Ноутбук', 'Мышь'],
      prefix: 'Товар',
      printAll() {
        this.items.forEach(function(item) {
          console.log(this.prefix + ': ' + item)  // this потерян!
        })
      }
    }
    
    // Исправлено — стрелочная функция:
    printAll() {
      this.items.forEach(item => {
        console.log(this.prefix + ': ' + item)  // this = cart
      })
    }

    Ошибка 2: деструктуризация метода

    const { greet } = user  // то же что const greet = user.greet
    greet()  // this потерян!

    Ошибка 3: this в обычной функции (не методе)

    function show() {
      console.log(this)  // undefined в strict mode, window в браузере
    }
    show()  // вызов без объекта — this не определён как объект

    В реальных проектах

  • jQuery/DOM: $(this) в обработчиках событий — элемент на котором сработало событие
  • Vue.js: this.username в методах компонента — доступ к данным компонента
  • React: в классовых компонентах this.setState() — поэтому нужен .bind(this)
  • Express.js: this в методах маршрутизатора
  • Примеры

    Корзина покупок с цепочкой методов

    const cart = {
      items: [],
      discount: 0,
    
      add(product) {
        const existing = this.items.find(i => i.id === product.id)
        if (existing) {
          existing.qty++
        } else {
          this.items.push({ ...product, qty: 1 })
        }
        return this  // цепочка
      },
    
      remove(productId) {
        this.items = this.items.filter(i => i.id !== productId)
        return this
      },
    
      applyDiscount(percent) {
        this.discount = percent
        return this
      },
    
      subtotal() {
        return this.items.reduce((sum, i) => sum + i.price * i.qty, 0)
      },
    
      total() {
        return Math.round(this.subtotal() * (1 - this.discount / 100))
      },
    
      summary() {
        this.items.forEach(i => {
          // стрелочная — this = cart
          console.log(`${i.name} x${i.qty} = ${i.price * i.qty} ₽`)
        })
        console.log(`Итого: ${this.total()} ₽ (скидка ${this.discount}%)`)
      }
    }
    
    cart
      .add({ id: 1, name: 'Ноутбук', price: 75000 })
      .add({ id: 2, name: 'Мышь',    price: 1500  })
      .add({ id: 1, name: 'Ноутбук', price: 75000 })  // +1 к qty
      .applyDiscount(10)
      .remove(2)
    
    cart.summary()
    // Ноутбук x2 = 150000 ₽
    // Итого: 135000 ₽ (скидка 10%)

    Задание

    Создай объект todoList для управления задачами. У него есть массив items и методы: add(text) — добавляет задачу {id, text, done: false}, complete(id) — помечает задачу выполненной, remove(id) — удаляет задачу, getPending() — возвращает массив невыполненных задач. Все методы кроме getPending возвращают this для цепочки.

    Подсказка

    complete ищет задачу через find(i => i.id === id). getPending фильтрует: filter(i => !i.done). Не забудь return this в add и remove.

    Загружаем среду выполнения...
    Загружаем AI-помощника...