Объекты и прототипы
Объекты — создание, копирование, перечисление, порядок свойств, аксессоры и дескрипторы свойств.
11 вопросов
JuniorТеорияОчень частоКак перебрать собственные свойства объекта, и что добавляет for...in?
Как перебрать собственные свойства объекта, и что добавляет for...in?
Object.keys(obj), Object.values(obj) и Object.entries(obj) возвращают массивы собственных перечислимых свойств объекта со строковыми ключами. for...in тоже перебирает перечислимые строковые ключи, но ещё проходит по унаследованным из цепочки прототипов, поэтому его защищают через obj.hasOwnProperty(key), чтобы пропустить унаследованные ключи.
Типичные ошибки
- ✗Использовать
for...inпо объекту без защитыhasOwnPropertyи подхватывать унаследованные ключи - ✗Ожидать, что
Object.keysвключит символьные ключи или неперечислимые свойства - ✗Думать, что
Object.valuesвозвращает ключи, а не соответствующие им значения
Уточняющие вопросы
- →Почему перебор массивов через
for...inне рекомендуется по сравнению сfor...of? - →Чем
Object.getOwnPropertyNamesотличается отObject.keys?
JuniorТеорияОчень частоКакие есть основные способы создать объект в JavaScript?
Какие есть основные способы создать объект в JavaScript?
Основные способы: литерал объекта {} (самый частый); new Object() (не рекомендуется); Object.create(proto), создающий объект с прототипом proto; функция-конструктор, вызванная через new; и синтаксис class из ES6, который является обёрткой над системой прототипов. Все дают обычные объекты, но различаются тем, как задаётся прототип.
Типичные ошибки
- ✗Думать, что
classвводит новую модель объектов, а не является обёрткой над прототипами - ✗Считать
new Object()рекомендуемым или самым быстрым способом по сравнению с литералом - ✗Полагать, что все формы создания дают один прототип независимо от выбора
Уточняющие вопросы
- →Чем
Object.create(null)отличается от{}по своему прототипу? - →Что делает оператор
newза вызовом конструктора?
JuniorТеорияЧастоЧто такое свойства-аксессоры, определяемые через get и set?
Что такое свойства-аксессоры, определяемые через get и set?
У свойства-аксессора нет хранимого значения; вместо этого get выполняет функцию при чтении, а set — при записи, и доступ идёт обычным синтаксисом вроде obj.lang. Они позволяют вычислять значение при доступе или проверять его при присваивании, выглядя как обычное поле и скрывая логику за точкой. Свойство является либо аксессором, либо свойством-данными, но не обоими.
Типичные ошибки
- ✗Вызывать геттер со
(), как метод, вместо чтения его как свойства - ✗Думать, что одно свойство может быть и данными, и аксессором сразу
- ✗Считать, что геттер кэширует значение и не перезапускается при каждом чтении
Уточняющие вопросы
- →Как добавить геттер к существующему объекту через
Object.defineProperty? - →Что произойдёт, если определить
get, но не задать парныйset?
JuniorТеорияЧастоКакую копию дают Object.assign и спред {...o}?
Какую копию дают Object.assign и спред {...o}?
И Object.assign({}, o), и спред {...o} создают поверхностную копию: они делают новый объект и копируют собственные перечислимые свойства источника на один уровень в глубину. Примитивы копируются по значению, но вложенный объект копируется по ссылке, поэтому оригинал и копия делят этот вложенный объект, и изменение через один виден в другом.
Типичные ошибки
- ✗Считать, что спред или
assignглубоко клонируют вложенные объекты - ✗Думать, что копия — отдельный объект, но ждать изоляции вложенных правок
- ✗Путать поверхностную копию с присваиванием ссылки, дающим псевдоним
Уточняющие вопросы
- →Как сделать настоящую глубокую копию вложенного объекта?
- →Копирует ли спред унаследованные свойства прототипа или только собственные?
MiddleТеорияЧастоВ чём разница между поверхностной и глубокой копией объекта?
В чём разница между поверхностной и глубокой копией объекта?
Поверхностная копия дублирует только верхний уровень; вложенные объекты общие по ссылке, поэтому Object.assign и спред оставляют копию и оригинал указывающими на одни внутренние объекты. Глубокая копия рекурсивно дублирует каждый вложенный объект, так что они ничего не делят. structuredClone даёт глубокую копию большинства данных без потери функций и дат, свойственной обходу через JSON.
Типичные ошибки
- ✗Считать, что
Object.assignили спред делают глубокую копию - ✗Менять местами определения поверхностной и глубокой копии
- ✗Полагать, что
JSON.parse(JSON.stringify(o))безопасно копирует функции и даты
Уточняющие вопросы
- →Что
structuredCloneне может скопировать, например функции? - →Почему обход через
JSONтеряетundefined, даты и функции?
JuniorТеорияИногдаВ каком порядке JavaScript перебирает собственные ключи объекта?
В каком порядке JavaScript перебирает собственные ключи объекта?
Собственные ключи выдаются в фиксированном порядке: сначала целочисленные ключи по возрастанию, затем остальные строковые ключи в порядке вставки, затем символьные ключи в порядке вставки. Поэтому {2:'a', 1:'b', x:'c'} перебирается как 1, 2, x. Этот порядок соблюдают Object.keys, for...in, JSON.stringify и spread.
Типичные ошибки
- ✗Считать, что порядок ключей объекта не определён или зависит от движка в современном JavaScript
- ✗Ожидать, что числовые строковые ключи останутся в порядке вставки, а не отсортируются по возрастанию
- ✗Полагать, что символьные ключи перемежаются со строковыми, а не образуют финальную группу
Уточняющие вопросы
- →Что именно считается целочисленным ключом для этого правила порядка?
- →Сохраняет ли
Mapпорядок вставки иначе, чем обычный объект?
MiddleТеорияИногдаЧто добавляют вычисляемые имена свойств и сокращённая запись в литералах объектов?
Что добавляют вычисляемые имена свойств и сокращённая запись в литералах объектов?
Вычисляемое имя {[expr]: v} вычисляет expr в момент создания и использует результат как ключ, поэтому ключ может быть динамическим. Сокращение {x} — сахар для {x: x}, а {f(){}} определяет метод. Литералы также могут объявлять аксессоры через get name(){} / set name(v){}, создавая дескрипторы getter/setter, а не обычные свойства-данные.
Типичные ошибки
- ✗Думать, что вычисляемые ключи ограничены литералами, а не любым выражением
- ✗Ожидать, что сокращение
{x}захватит живую ссылку, а не текущее значение - ✗Полагать, что синтаксис аксессоров
get name(){}допустим только в телахclass, а не в литералах
Уточняющие вопросы
- →Перечислимы ли свойства-аксессоры, созданные
get/setв литерале? - →Как использовать
Symbolв качестве вычисляемого имени свойства?
MiddleТеорияИногдаЧто такое дескриптор свойства и какие атрибуты он содержит?
Что такое дескриптор свойства и какие атрибуты он содержит?
Дескриптор — это запись, описывающая одно свойство. У свойства-данных есть value, writable, enumerable и configurable; у свойства-аксессора value/writable заменяются на get/set. Object.defineProperty создаёт или правит свойство с выбранными атрибутами (по умолчанию false), а Object.getOwnPropertyDescriptor читает их обратно. configurable: false запирает сами атрибуты.
Типичные ошибки
- ✗Думать, что
definePropertyставит пропущенные флаги вtrue - ✗Путать
writable(смена значения) сconfigurable(удаление или переопределение) - ✗Считать, что дескриптор может держать
valueиgetодновременно
Уточняющие вопросы
- →Почему свойство, заданное через
defineProperty, по умолчанию не видно вfor...in? - →Что произойдёт при попытке переопределить свойство с
configurable: false?
MiddleТеорияРедкоЧем перечислимые и неперечислимые свойства различаются в методах перебора?
Чем перечислимые и неперечислимые свойства различаются в методах перебора?
Флаг enumerable в дескрипторе свойства решает, попадёт ли оно в перебор. Object.keys, for...in и JSON.stringify перечисляют только собственные перечислимые строковые ключи; for...in вдобавок поднимается по цепочке прототипов. Object.getOwnPropertyNames возвращает все собственные строковые ключи, включая неперечислимые, но никогда унаследованные. Методы на прототипах class по умолчанию неперечислимы.
Типичные ошибки
- ✗Считать, что методы прототипа
classпоявятся в выводеObject.keysилиfor...in - ✗Думать, что
getOwnPropertyNamesпроходит цепочку прототипов какfor...in - ✗Забывать, что
JSON.stringifyмолча отбрасывает неперечислимые свойства
Уточняющие вопросы
- →Как сделать свойство неперечислимым через
Object.defineProperty? - →Какой метод также возвращает собственные символьные ключи, которые остальные опускают?
SeniorТеорияРедкоПочему Object.freeze поверхностный, и как глубоко заморозить объект?
Почему Object.freeze поверхностный, и как глубоко заморозить объект?
Object.freeze лишь переключает дескриптор каждого собственного свойства в writable: false и configurable: false; свойство, хранящее вложенный объект, по-прежнему ссылается на полностью изменяемый объект, поэтому заморозка останавливается на одном уровне. Для глубокой заморозки рекурсируют: замораживают объект, затем обходят его свойства-объекты и замораживают каждое, проверяя уже замороженные, чтобы избежать циклов.
Типичные ошибки
- ✗Думать, что
Object.freezeсам рекурсирует во вложенные объекты - ✗Считать, что
freezeкопирует объект, а не меняет его дескрипторы на месте - ✗Забывать защищаться от циклов при написании рекурсивной глубокой заморозки
Уточняющие вопросы
- →Чем
configurable: falseзамороженного свойства отличается отwritable: false? - →Почему рекурсивная глубокая заморозка должна защищаться от циклических ссылок?
SeniorТеорияРедкоКак спецификация определяет порядок собственных ключей, и почему порядок for...in исторически различался?
Как спецификация определяет порядок собственных ключей, и почему порядок for...in исторически различался?
Внутренний метод [[OwnPropertyKeys]] фиксирует порядок: целочисленные индексы по возрастанию, затем строковые ключи в порядке вставки, затем символы в порядке вставки. Object.keys и соседи следуют ему точно. Порядок for...in, однако, оставался неопределённым до ES2020 — движки различались (особенно на целочисленных ключах), поэтому старый код не мог на него полагаться. Символьные ключи исключены из Object.keys, for...in и JSON.stringify.
Типичные ошибки
- ✗Считать, что порядок
for...inбыл определён всегда, тогда как его стандартизировал лишь ES2020 - ✗Думать, что символьные ключи появляются в выводе
Object.keysилиJSON.stringify - ✗Полагать, что целочисленные ключи сохраняют порядок вставки, а не сортируются по возрастанию
Уточняющие вопросы
- →Какой метод всё же извлекает собственные символьные ключи объекта?
- →Почему порядок
for...inостаётся отвязан от[[OwnPropertyKeys]]для унаследованных ключей?