this и прототипы
Вопрос
От чего зависит this и чем прототип отличается от класса?
Короткий ответ
this определяется способом вызова, а не местом объявления функции.
- Вызов как метод,
obj.method(), —thisравенobj. - Обычный вызов
fn()— в строгом режимеthisравенundefined, без него указывает на глобальный объект. call,applyиbindзадаютthisявно.bindвозвращает новую функцию.- Стрелочная функция не имеет своего
thisи берёт его из внешнего лексического окружения. newсоздаёт объект и подставляет его какthis.
Прототип — это объект, на который ссылается [[Prototype]]. Поиск свойства идёт по цепочке, пока не найдётся поле или не кончится цепочка. class — синтаксис над той же моделью: методы лежат на Constructor.prototype, статические — на самой функции.
Разбор
Метод, переданный колбэком, теряет получателя:
const user = {
name: 'Ann',
hi() {
return this.name
},
}
setTimeout(user.hi, 0) // this больше не userЧинится стрелкой-обёрткой или bind. Стрелочный метод в литерале объекта как раз не подойдёт, если нужен this объекта: у стрелки своего this нет.
obj.hasOwnProperty и Object.hasOwn(obj, key) смотрят только собственные поля. 'toString' in obj может быть истинным из-за прототипа. На собеседовании это связывают с перебором for...in, который идёт и по перечисляемым унаследованным ключам.
На что смотрят на senior
Длинная цепочка прототипов и частые промахи по свойствам стоят дороже, чем один собственный объект, но в прикладном UI это редко узкое место. Важнее не мутировать Object.prototype: вы добавите поле всем объектам и сломаете чужие переборы.
Композиция (функция принимает объект и возвращает поведение) часто читается проще, чем иерархия из четырёх классов. Класс уместен, когда есть инварианты экземпляра и понятный жизненный цикл.
Типичные ошибки
- Путать
thisсо замыканием. Замыкание помнит переменные,thisзависит от вызова. - Считать, что стрелка «всегда берёт компонент». Она берёт
thisтого места, где написана. - Думать, что
classсоздаёт другую объектную модель, отдельную от прототипов.
Задача
Чему равен this в каждом вызове?
const user = {
name: 'Ann',
hi() {
return this.name
},
hiArrow: () => this.name,
}
const hi = user.hi
user.hi()
hi()Показать ответ
user.hi() берёт this из вызова через точку: 'Ann'.
hi() — вызов без базы. В строгом режиме this будет undefined, чтение name упадёт. В нестрогом this станет глобальным объектом.
hiArrow — стрелка. Своего this у неё нет, она возьмёт this снаружи объекта, не user.