Объекты и прототипы
Объекты — создание, копирование, перечисление, порядок свойств, аксессоры и дескрипторы свойств.
14 вопросов
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ТеорияЧастоЧто выведет программа?
Что выведет программа?
2 1
Открыть задачу →Типичные ошибки
- ✗Ждать, что
o.keyвозьмёт переменнуюkey: после точки имя берётся буквально, поэтомуo.keyчитает свойство с именемkey, то есть2. - ✗Ждать, что
o[key]прочитает свойство с именемkey: в скобках сначала вычисляется выражение, поэтомуo[key]— этоo["a"], то есть1.
Уточняющие вопросы
- →Как прочитать свойство
keyчерез квадратные скобки? - →Что напечатает
o.b, если у объекта нет свойстваb?
JuniorТеорияЧастоЧто выведет программа?
Что выведет программа?
15
Открыть задачу →Типичные ошибки
- ✗Ждать
6: геттер не вычисляется один раз при создании объекта, а запускается при каждом чтении, поэтомуr.areaберёт новоеw—5 * 3. - ✗Ждать
undefined, разareaчитается без(): метод сgetзапускается при обычном чтении свойства, а вызовr.area()как раз остановил бы программу сTypeError.
Уточняющие вопросы
- →Что выведет программа, если перенести
r.w = 5;ниже строки сconsole.log? - →Что произойдёт, если вызвать
r.area()со скобками?
JuniorТеорияЧастоЧто такое свойства-аксессоры, определяемые через get и set?
Что такое свойства-аксессоры, определяемые через get и set?
У свойства-аксессора нет хранимого значения; вместо этого get выполняет функцию при чтении, а set — при записи, и доступ идёт обычным синтаксисом вроде obj.lang. Они позволяют вычислять значение при доступе или проверять его при присваивании, выглядя как обычное поле и скрывая логику за точкой. Свойство является либо аксессором, либо свойством-данными, но не обоими.
Типичные ошибки
- ✗Вызывать геттер со
(), как метод, вместо чтения его как свойства - ✗Думать, что одно свойство может быть и данными, и аксессором сразу
- ✗Считать, что геттер кэширует значение и не перезапускается при каждом чтении
Уточняющие вопросы
- →Как добавить геттер к существующему объекту через
Object.defineProperty? - →Что произойдёт, если определить
get, но не задать парныйset?
JuniorТеорияЧастоЧто выведет программа?
Что выведет программа?
1 2 b a
Открыть задачу →Типичные ошибки
- ✗Ждать
b 2 a 1— порядок, в котором ключи записаны: целочисленные ключи вроде2и1всегда идут первыми, по возрастанию. - ✗Ждать
1 2 a b: сортируются только целочисленные ключи, а остальные сохраняют порядок добавления, поэтомуbостаётся передa.
Уточняющие вопросы
- →В каком порядке
for…inобойдёт ключи того же объекта? - →В каком порядке вышли бы ключи, если бы это был
Map?
JuniorТеорияЧастоКакую копию дают Object.assign({}, o) и синтаксис расширения {...o}?
Какую копию дают Object.assign({}, o) и синтаксис расширения {...o}?
Оба дают поверхностную копию — новый объект с теми же свойствами верхнего уровня. Вложенный объект не копируется: копия ссылается на тот же объект, поэтому его изменение через копию видно в оригинале.
Типичные ошибки
- ✗Ждать, что
{ ...o }скопирует и вложенные объекты: новый только верхний уровень, а вложенный объект остаётся общим; все уровни копируетstructuredClone(o). - ✗Ждать, что копия — тот же объект, что и
o:{ ...o } === oдаётfalse, а изменение свойства верхнего уровня в копии оставляетoпрежним.
Уточняющие вопросы
- →Как сделать копию, которая не делит с оригиналом ни одного вложенного объекта?
- →Что произойдёт с
o, если добавить новое свойство в его копию?
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]]для унаследованных ключей?