Тест: Прелести деструктуризации
Вы мастер деструктуризации?
Или это ваша Симфония разрушения?
Этот тест проверит ваши знания деструктуризации в JavaScript: от «базового» синтаксиса объектов до вложенной деструктуризации и значений по умолчанию. Плюс бонусные вопросы по TypeScript и inline‑типам!
Перейдите сразу к разогреву — продемонстрируйте свои навыки деструктуризации! 👇
Что выведет этот код?
const person = { name: 'Dan Levy', location: 'Cape Town',};const { name, age } = person;console.log(`Name: ${name}, Age: ${age}`);Свойство age отсутствует в объекте person, поэтому age будет undefined. Точно не Infinity 😅
В результате получаем:
Name: Dan Levy, Age: undefinedЧто сделает этот код?
const person = [ 'Dan Levy', 'Cape Town' ];const [ name, origin, age ] = person;console.log(`Name: ${name}, Age: ${age}`);Переменная age отсутствует в массиве tuple, поэтому её значение будет undefined.
Это приводит к:
Name: Dan Levy, Age: undefinedКак насчёт вложенной деструктуризации?
'use strict';const person = { name: { first: 'Dan' }, address: { city: 'Denver' },};const { name: { first }, address: { city }, birth: { place },} = person;console.log( `First: ${first}, City: ${place}`,);Свойство birth: { place } отсутствует у person, поэтому будет выброшена ошибка.
Одно из решений — задать значения по умолчанию для вложенных свойств.
При доступе к вложенным свойствам будьте осторожны — ошибки могут быть трудно заметны. Текст сообщений об ошибках различается в разных браузерах и платформах, что усложняет отладку.
В современном Chrome: TypeError: Cannot read properties of undefined (reading 'place')
В Node это тоже TypeError, потому что JavaScript пытается деструктурировать place из undefined, прежде чем place будет прочитан.
Точный формулировка зависит от браузера и среды выполнения.
Теперь с некоторыми значениями по умолчанию, что это сделает?
'use strict';const person = { name: { first: 'Dan' }, address: { city: 'Denver' },};const { name: { first = 'Unknown' }, birth: { place = 'Unknown' },} = person;console.log( `Hi ${first} from ${place}`,);Свойство birth отсутствует в person, поэтому всему объекту всё равно нужен дефолт, а не только вложенному свойству. По сути здесь не хватает дефолта = {}.
Такая запись говорит: «если person.birth равно undefined, то place будет Unknown». Но person.birth равно undefined, и происходит попытка деструктурировать undefined, что приводит к ошибке.
In modern Chrome: `TypeError: Cannot read properties of undefined (reading 'place')`
In Node, this is also a `TypeError` because JavaScript tries to destructure `place` from `undefined`.
Exact wording varies between browsers and runtimes.Что это сделает?
const person = { name: { first: 'Dan' }, address: { city: 'Denver' },};const { name: { first = 'Unknown' }, birth: { place = 'Unknown' } = {},} = person;
console.log( `Hi ${first} from ${place}`,);Свойство birth отсутствует в объекте person, поэтому используется пустой объект = {}. Это позволяет применить значение по умолчанию.
А теперь в параметрах функции, что это сделает?
'use strict';function displayUser({ name = "Unknown", age = -1,} = { place: "Unknown" }) { console.log(`Hi ${name} from ${place}`);}displayUser({ name: "Dan" });Эта функция извлекает свойства name и age, используя значения по умолчанию при необходимости. В данном случае ключ place в объекте по умолчанию — просто шум, он не используется внутри displayUser().
Стричный режим не меняет ситуацию: попытка чтения необъявленной привязки place бросает ReferenceError.
Как обрабатываются значения undefined?
'use strict';function displayPlace({ name = "N/A", place = "N/A", age = -1,} = { place: "Unknown" }) { console.log(`${place}`);}displayPlace({ name: "Dan" });displayPlace({ name: "Dan", place: undefined });displayPlace({ name: "Dan", place: "Joburg" });Функция displayPlace будет ИСПОЛЬЗОВАТЬ объект по умолчанию ТОЛЬКО если не передан объект. Поэтому единственный способ получить значение по умолчанию { place: "Unknown" } — вызвать её без аргументов displayPlace().
Ещё одна примечательная особенность: передача undefined для place заставит использовать значение по умолчанию, что немного напоминает поведение JSON.stringify (игнорирует undefined, учитывает null).
В результате получается:
displayPlace() // UnknowndisplayPlace({ name: "Dan" }) // N/AdisplayPlace({ name: "Dan", place: undefined }) // N/AПохожим образом на предыдущий пример… как обрабатывается null?_
function displayPlace({ name = "N/A", place = "N/A", age = -1,} = { place: "Unknown" }) { console.log(`${place}`);}displayPlace({ name: "Dan", place: null });displayPlace({ name: "Dan", place: undefined });В этом случае свойство place устанавливается в null при первом вызове и в undefined при втором. Значение по умолчанию для place используется только если весь объект отсутствует или undefined. null будет передаваться как null.
Теперь в TypeScript… что это сделает?
'use strict';function displayPlace( { name = 'N/A', place = 'N/A', }: { name: string; place: string; age: number; },) { console.log(`${place}`);}displayPlace({ name: 'Dan', place: null });TypeScript сообщает об ошибке, потому что place имеет тип string, а вызов передаёт null. Кроме того, вызов опускает обязательное свойство age.
Если игнорировать ошибки типов, выполнение кода выведет null в консоль.
Попробуем немного переименования/присваивания…
'use strict';function displayPlace({ name = 'N/A', place: location = 'N/A',}: { name: string; place: string; age?: number;}) { console.log(`${location}`);}displayPlace({ name: 'Dan', place: 'Denver' });Это выведет Denver в консоль. Свойство place переименовано в location в сигнатуре функции. Это распространённый приём (переименование свойств при деструктуризации) при адаптации сторонних структур данных.
Найдите ошибку типов:
function greet({ name: {first = "N/A", last = "N/A"}, birth: {place = "N/A"} = {}, age = -1,}: { name: {first?: string, last?: string}; birth: {place?: string}; age: number;}) { console.log(`Hi ${first} ${last} from ${place}`);}greet({ name: {first: 'Dan'} });Ошибка находится в сигнатуре функции greet. Свойства age и birth отсутствуют в переданном объекте, поэтому они должны быть опциональными в определении типа.
Хотя свойство birth деструктурируется с значением по умолчанию, определение типа требует его присутствия. Чтобы пометить свойство как опциональное в TypeScript, следует использовать оператор ?.
Обратите внимание, что birth?: { place?: string } не то же самое, что birth: { place?: string } | undefined.
Теперь с присваиванием (обратите внимание на переменные f, l и p)
'use strict';function greet( { name: {first: f = "N/A", last: l = "N/A"}, birth: {place: p = "N/A"} = {}, age = -1, }: { name: {first?: string, last?: string}; birth?: {place?: string}; age?: number; }) { console.log(`Hi ${f} ${l} from ${place}`); // What will 👆 do?}greet({ name: {first: 'Dan', last: 'Levy'}, birth: {place: 'Cape Town'},});Еще одна ошибка! Вы начинаете угадывать, не так ли?!
Трудно читать слои деструктуризации с дефолтами, присваиванием и типами!
Как только place переназначается в переменную p, она больше не определена в области видимости оператора console.log.
console.log(`Hi ${f} ${l} from ${place}`); // ❌// to:console.log(`Hi ${f} ${l} from ${p}`); // ✅