DanLevy.net

Тест: Прелести деструктуризации

Вы мастер деструктуризации?

Или это ваша Симфония разрушения?

Этот тест проверит ваши знания деструктуризации в 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() // Unknown
displayPlace({ name: "Dan" }) // N/A
displayPlace({ 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}`); // ✅