Прототипы и наследование
Цепочка прототипов, __proto__ против prototype, прототипное наследование, расширяемость и Proxy.
10 вопросов
JuniorТеорияОчень частоЧто такое цепочка прототипов и как поиск свойства её использует?
Что такое цепочка прототипов и как поиск свойства её использует?
У каждого объекта есть скрытая ссылка [[Prototype]] на другой объект. При чтении свойства движок проверяет сам объект, затем идёт по [[Prototype]] вверх по цепочке объект за объектом, пока не найдёт свойство или не достигнет null, где поиск завершается и результат равен undefined. Так в JavaScript работают наследование и совместное использование методов.
Типичные ошибки
- ✗Думать, что отсутствующее свойство даёт
undefinedбез обхода цепочки - ✗Считать, что свойства копируются с прототипа, а не ищутся вживую
- ✗Забывать, что цепочка завершается на
null, а не на самомObject.prototype
Уточняющие вопросы
- →Где заканчивается цепочка прототипов обычного литерала объекта?
- →Чем запись свойства отличается от чтения вдоль цепочки?
MiddleТеорияОчень частоКак работает прототипное наследование через Object.create и setPrototypeOf?
Как работает прототипное наследование через Object.create и setPrototypeOf?
Объекты наследуют, связываясь с прототипом, а не копируя. Object.create(parent) создаёт новый объект, чей [[Prototype]] — это parent, а Object.setPrototypeOf(child, parent) перенаправляет ссылку существующего объекта. Свойство, отсутствующее у потомка, затем разрешается подъёмом к родителю, поэтому методы родителя совместно используются вживую, а не дублируются между всеми потомками.
Типичные ошибки
- ✗Думать, что наследование копирует члены родителя в каждый объект-потомок
- ✗Путать направление — потомок ссылается на родителя, а не наоборот
- ✗Считать, что
setPrototypeOfсхлопывает ключи, а не задаёт живую ссылку
Уточняющие вопросы
- →Почему
Object.setPrototypeOfне рекомендуется из-за производительности? - →Как потомок переопределяет метод, унаследованный от прототипа?
JuniorТеорияЧастоЧто делает Object.freeze и почему его называют поверхностным?
Что делает Object.freeze и почему его называют поверхностным?
Object.freeze(obj) делает obj неизменяемым: нельзя добавлять, удалять или менять его собственные свойства, и он возвращает тот же объект, а не копию. Он поверхностный — заморожен только верхний уровень, поэтому вложенный объект, хранимый как свойство, всё ещё можно изменить по его ссылке. В строгом режиме запись в замороженное свойство бросает TypeError.
Типичные ошибки
- ✗Считать
freezeглубоким и защищающим вложенные объекты от изменения - ✗Думать, что
freezeвозвращает новую копию, а не тот же объект - ✗Полагать, что запись в замороженное свойство всегда бросает, даже не в строгом режиме
Уточняющие вопросы
- →Как сделать глубоко вложенный объект полностью неизменяемым?
- →Чем
Object.freezeотличается отObject.seal?
MiddleТеорияЧастоВ чём разница между __proto__ и .prototype?
В чём разница между __proto__ и .prototype?
__proto__ — ссылка экземпляра на его собственный прототип, объект, используемый при поиске; он устарел, поэтому предпочтительнее Object.getPrototypeOf. .prototype есть только у функций-конструкторов: это объект, который new назначает как [[Prototype]] нового экземпляра. Поэтому (new Foo).__proto__ === Foo.prototype, а у обычного экземпляра своего .prototype нет.
Типичные ошибки
- ✗Считать
__proto__и.prototypeодним и тем же свойством - ✗Ожидать у экземпляра собственное свойство
.prototype - ✗Использовать
__proto__в коде вместоObject.getPrototypeOf
Уточняющие вопросы
- →Почему чтение
__proto__не рекомендуется в пользуObject.getPrototypeOf? - →Каков
[[Prototype]]самогоFoo.prototype?
MiddleТеорияИногдаЧто такое Proxy и как работают его ловушки-обработчики?
Что такое Proxy и как работают его ловушки-обработчики?
new Proxy(target, handler) оборачивает целевой объект, так что операции над прокси сначала проходят через обработчик. Каждый метод обработчика — это ловушка: get, set, has, deleteProperty и другие, перехватывающая соответствующую фундаментальную операцию, позволяя настроить или проверить её. Пропущенная ловушка передаётся стандартному поведению на цели без изменений.
Типичные ошибки
- ✗Думать, что
Proxyклонирует цель, а не оборачивает её вживую - ✗Считать, что пропущенная ловушка блокирует операцию, а не передаёт дальше
- ✗Полагать, что ловушки перечисляют имена свойств, а не перехватывают операции
Уточняющие вопросы
- →Что предлагает API
Reflectрядом с ловушкамиProxy? - →Назовите реальный сценарий, например реактивное состояние во Vue 3.
MiddleТеорияИногдаЧем различаются preventExtensions, seal и freeze?
Чем различаются preventExtensions, seal и freeze?
Они образуют лестницу. preventExtensions лишь блокирует добавление новых свойств. seal делает это и ещё помечает существующие как non-configurable, поэтому ни одно нельзя удалить, но их значения остаются записываемыми. freeze добавляет сверху non-writable, поэтому изменить нельзя ничего. Парные проверки — isExtensible, isSealed и isFrozen.
Типичные ошибки
- ✗Считать
sealиfreezeравнозначными, игнорируя записываемость значений - ✗Переворачивать лестницу — думать, что
freezeслабееseal - ✗Забывать, что
sealоставляет значения записываемыми, блокируя удаление
Уточняющие вопросы
- →После
Object.sealможно ли ещё переприсвоить значение существующего свойства? - →Все ли три операции поверхностны, и что это значит для вложенных объектов?
SeniorТеорияИногдаЧто создаёт new String('hi'), и почему предпочитать строковые литералы?
Что создаёт new String('hi'), и почему предпочитать строковые литералы?
new String('hi') возвращает объект-обёртку — typeof даёт object, а не string — упаковывая примитив в экземпляр String. Литералы вроде 'hi' остаются примитивами, а при вызове метода движок упаковывает их в недолговечную временную обёртку, читает через её прототип, затем отбрасывает. Явные объекты-обёртки ломают ===, typeof и истинность, поэтому литералы предпочтительнее.
Типичные ошибки
- ✗Думать, что
new String('hi')даёт примитив сtypeofравнымstring - ✗Ожидать, что
new String('hi') === 'hi'этоtrue - ✗Считать, что примитивы не вызывают методы без вручную созданной обёртки
Уточняющие вопросы
- →Почему объект-обёртка вроде
new Boolean(false)всегда истинен? - →Как временная обёртка достигает
String.prototype, чтобы разрешить метод?
MiddleТеорияРедкоКак эмулировать перечисление в JavaScript, где его нет нативно?
Как эмулировать перечисление в JavaScript, где его нет нативно?
В JavaScript нет ключевого слова enum, поэтому его строят из замороженного объекта: const Color = Object.freeze({ RED: 'red', GREEN: 'green' }). Заморозка блокирует переприсваивание и новые члены. Использование Symbol('RED') в качестве значений гарантирует уникальность каждого члена и неравенство случайной строке ценой невозможности сериализации в JSON.
Типичные ошибки
- ✗Полагать, что в JavaScript есть встроенное ключевое слово
enum, как в TypeScript или C - ✗Пропускать
Object.freeze, оставляя члены перечисления изменяемыми во время выполнения - ✗Ожидать, что два отдельных вызова
Symbol('RED')дадут равные значения
Уточняющие вопросы
- →Почему перечисления со значениями-
Symbolусложняют хранение и отладку? - →Как
Object.freezeвзаимодействует с вложенными объектами внутри перечисления?
SeniorДебаггингРедкоПочему переопределение в подклассе не срабатывает, когда метод — стрелочное поле класса?
Почему переопределение в подклассе не срабатывает, когда метод — стрелочное поле класса?
Стрелочное поле класса — это собственное свойство инстанса, присваиваемое в конструкторе, а не метод прототипа. Поэтому investor.getPayment находит сначала поле базового класса, а метод подкласса на Investor.prototype перекрыт — переопределение не срабатывает.
Типичные ошибки
- ✗Думать, что метод прототипа переопределяет собственное стрелочное поле класса с тем же именем
- ✗Считать, что поля класса и методы прототипа лежат в одном месте при поиске
- ✗Смешивать стрелочное поле в базовом классе с методом прототипа в подклассе
Уточняющие вопросы
- →Где на цепочке поиска лежит собственное поле класса против метода прототипа?
- →Что даёт стрелочное поле базового класса, чего лишается обычный метод прототипа при отвязывании?
SeniorТеорияРедкоКак new связывает прототип экземпляра, и у каких объектов его нет?
Как new связывает прототип экземпляра, и у каких объектов его нет?
new Foo() создаёт объект, чей [[Prototype]] установлен в Foo.prototype, который сам связан с Object.prototype, образуя цепочку поиска. Цепочка завершается на объекте без прототипа: Object.prototype, чей [[Prototype]] равен null, и объекты из Object.create(null), у которых прототипа нет, поэтому они не наследуют методы Object.
Типичные ошибки
- ✗Думать, что
newставит прототип экземпляра в конструктор, а не в его.prototype - ✗Считать, что каждый объект наследует от
Object.prototypeбез исключений - ✗Полагать, что
Object.create(null)всё ещё разрешает методыObject
Уточняющие вопросы
- →Зачем использовать
Object.create(null)для словаря или карты поиска? - →Каков
[[Prototype]]функции, и как завершается эта цепочка?