Как получить корректный this в callback?
7 февраля 2021 г. • ☕️ 5 мин.
Проблема
На равне с пониманием возврата из асинхронной функции, проблема с доступом к this
в callback
возникает достаточно часто.
Представим, у нас есть машина, которая посылает свои координаты каждые 3 секунды.
class Car {
lat = 45.517441
lon = -73.728358
sendLocation() {
console.log(`lat: ${this.lat}`)
console.log(`lon: ${this.lon}`)
// ...
}
}
const car = new Car()
car.sendLocation()// lat: 45.517441
// lon: -73.728358
setInterval(car.sendLocation, 3000)// lat: undefined
// lon: undefined
Почему во втором случае, мы получаем не корректное поведение (теряем this
)?
Что нужно знать про this
?
this
(или текущий контекст) - это специальное ключевое слово внутри функции и он не является фиксированным. Значение this
зависит от того, как функция была вызвана и не зависит от того, как, где и когда функция была объявлена.
Внутри функции, this
ссылается на объект “перед точкой”, который использовался для вызова метода.
В первом примере, мы вызываем функцию sendLocation
на объекте car
.
Во втором примере, мы берем функцию sendLocation
на объекте car
и передаем ее, как callback
в функцию setInterval
. В дальнейшем, ее вызывает кто-то другой, в нашем случае среда исполнения javascript
при наступление заданного интервала времени.
Как получить правильный this
?
Обернуть вызов в другую функцию
setInterval(function() {
car.sendGPSLocation()
}, 3000)
setInterval(() => car.sendGPSLocation(), 3000)
В обоих случаях среда исполнения javascript
вызовет анонимную функцию “обертку”, внутри который уже мы вызовем нужную нам функцию, указав на контекст this
явно.
Использовать метод .bind
setInterval(car.sendGPSLocation.bind(car), 3000)
Метод .bind
позволяем нам явно “привязать” контекст к функции. По сути он возвращает новую функцию, создавая обертку, как в первом случае.
Указать на this
явно, в функциях, которые это поддерживают
array.map(callback[, thisArg])
Для примера метод .map
позволяет опционально передать this
.
Использовать стрелочные функции
В этом примере, код упадет с ошибкой, по той же причине - “потеря” this
.
class Car {
lastCoordinates = [
{ lat: 45.517441, lon: -73.728358 },
{ lat: 45.517441, lon: -73.728358 },
]
sendGPSLocation(lat, lon) {
console.log(`lat: ${lat}`)
console.log(`lon: ${lon}`)
// ...
}
sendLastCoorditanes() {
this.lastCoordinates.forEach(function({ lat, lon }) {
this.sendGPSLocation(lat, lon) // TypeError: Cannot read property 'sendGPSLocation' of undefined
})
}
}
const car = new Car()
car.sendLastCoorditanes()
У стрелочных функций нет своего this
. При обращении к this
внутри стрелочной функции, его значение берётся из внешней “обычной” функции.
class Car {
lastCoordinates = [
{ lat: 45.517441, lon: -73.728358 },
{ lat: 45.517441, lon: -73.728358 },
]
sendGPSLocation(lat, lon) {
console.log(`lat: ${lat}`)
console.log(`lon: ${lon}`)
// ...
}
sendLastCoorditanes() {
this.lastCoordinates.forEach(({ lat, lon }) => {
this.sendGPSLocation(lat, lon) })
}
}
const car = new Car()
car.sendLastCoorditanes()
Итоги
- значение
this
у функций определяется в момент вызова - при вызове функции “через точку”
car.sendLastCoorditanes()
, значениемthis
является объект перед точкой - при передачи функции куда-либо для дальнейшего вызова, контекст
this
будет потерян - контекст
this
нужно сохранять явно - использование стрелочных функций, позволит избежать потери контекста
this