Перейти к содержанию

Деструктуризация и значения по умолчанию

В обработчиках каталога повторяются обращения course.title, course.lessons и course.topic. Деструктуризация позволяет извлечь нужные свойства в локальные имена. Она не изменяет объект и не выполняет его проверку: это способ записи доступа к значениям, а не новый формат данных.

Продолжим работу с массивом courses из урока об объектах. Его четыре записи и поля остаются прежними. Сейчас важно понять, откуда берётся извлечённое значение и когда применяется запасное значение. Слишком короткая запись без этой модели может скрыть отсутствие обязательных данных.

Извлечение именованных полей

Добавьте после исходного массива:

const course = courses[0];
const { title, lessons } = course;
console.log(`${title}: уроков ${lessons}`);

Ожидается знакомая подпись JavaScript-курса. Слева фигурные скобки перечисляют имена свойств, которые читаются из объекта справа. Порядок имён здесь не задаёт порядок полей источника. Запись { lessons, title } извлекла бы те же значения; связь определяется именами.

Деструктуризация не забирает свойство из объекта. После неё course.title остаётся доступным. Для строк и чисел локальные переменные содержат соответствующие примитивные значения, поэтому дальнейшее присваивание другому локальному имени не редактирует исходную запись. Вложенный объект сохранил бы ссылочную природу, как при обычном чтении свойства.

Если локальное имя уже занято, свойство можно переименовать:

const { title: courseTitle, lessons: lessonCount } = courses[0];
console.log(courseTitle, lessonCount);

До двоеточия находится имя свойства источника, после — создаваемое локальное имя. Не путайте эту запись с объектным литералом: там двоеточие связывало создаваемое свойство со значением, здесь оно обозначает способ извлечения. Контекст объявления показывает, какое действие выполняется.

Необязательные и обязательные значения

Рассмотрим отдельный самостоятельный пример с необязательной аннотацией:

const course = { title: 'HTML и CSS', lessons: 16 };
const { summary = 'Аннотация пока не добавлена' } = course;
console.log(summary);

Запасная строка применяется, потому что свойства summary нет и прочитанное значение было бы undefined. При явном summary: null получится null, а при summary: '' — пустая строка. Деструктуризация не трактует все эти варианты как одно отсутствие.

Поэтому обязательное название не следует скрывать через title = 'Курс'. Это сделало бы повреждённую запись визуально пригодной, хотя идентичность и содержание потеряны. Для данных каталога будем отдельно проверять обязательные поля. Запасные значения оставим там, где контракт действительно допускает отсутствие, например у параметров отбора.

Оператор ?? также выбирает запасное значение, но только при null или undefined. При 0 и пустой строке он сохраняет исходное значение. В отличие от него || реагирует на общую ложность. Если число ноль имеет предметный смысл, эти записи нельзя считать взаимозаменяемыми.

console.log(0 ?? 10);
console.log(0 || 10);

Ожидаются ноль и десять. В нашем количестве уроков ноль позднее будет запрещён явной проверкой, а не случайным подбором оператора. Такая граница помогает увидеть неправильные данные вместо незаметного превращения их в другое число.

Правила извлечения и начальных значений представлены в MDN о деструктуризации; выбор только при отсутствии описан в MDN об операторе ??.

Параметры выбора как объект

Функция может принимать объект с несколькими параметрами. Это позволяет назвать их в месте вызова и добавлять настройки без неочевидного порядка аргументов. Добавьте после общих данных:

function selectCourses(courses, { topic = 'all', sort = 'original' } = {}) {
  const selected = courses.filter(course =>
    topic === 'all' || course.topic === topic);
  if (sort === 'lessons') {
    selected.sort((a, b) => a.lessons - b.lessons);
  }
  return selected;
}
console.log(selectCourses(courses, { topic: 'publishing' }).length);
console.log(selectCourses(courses).length);

Ожидаются один и четыре. Внешнее = {} применяется при отсутствии всего второго аргумента. Внутреннее topic = 'all' применяется при отсутствии конкретного поля. Это две разные ступени: пустой объект обеспечивает возможность читать поля, а запасные значения задают начальную конфигурацию.

Если передать null вместо объекта настроек, деструктуризация вызовет ошибку. Начальные значения не обязаны лечить любое произвольное значение. Наш вызывающий код будет создавать объект самостоятельно; в итоговом приложении эта предпосылка известна. На границе неизвестного внешнего ввода применяем проверку раньше извлечения.

Сортировка меняет selected, который уже создан методом filter; источник courses не переставляется. Это пример, когда отдельный вызов slice не нужен: мы уже получили новый массив. Значения его элементов всё ещё указывают на исходные объекты, поэтому свойства карточек продолжаем только читать.

Копия и изменение одного поля

Для новой версии плоской записи удобно spread-выражение:

const updated = { ...courses[0], lessons: 21 };
console.log(courses[0].lessons);
console.log(updated.lessons);

Ожидаются двадцать и двадцать один. Создаётся отдельный объект; свойства источника копируются, затем позднее записанное lessons перекрывает прежнее значение. Если поменять порядок частей, исходное свойство перекроет вашу правку. Порядок копирования является частью смысла программы, а не оформлением.

Копия поверхностная. У нашего плоского курса поля являются примитивами, однако при появлении вложенной статистики spread сохранил бы ссылку на неё. Не обещайте таким выражением полную независимость произвольного дерева данных. Для текущей схемы достаточно ограничить операции понятными плоскими записями.

Массив и остаток

У массива деструктуризация ориентируется на позиции. Например, const [first, second] = courses получает первые две записи. Имена слева свободно выбираются программистом; в отличие от объекта они не должны совпадать с именами полей источника. При пустом массиве first станет undefined, поэтому такая запись не доказывает существование карточки.

Rest-запись собирает оставшиеся элементы: const [first, ...others] = courses. Для нашего набора others содержит три записи в новом массиве. Сами объекты по-прежнему общие с исходником. Сходство записи ... со spread не должно скрывать направление действия: rest собирает несколько значений, spread раскрывает имеющееся значение в месте построения или вызова.

Опциональная цепочка course?.title позволяет получить undefined, если сам курс отсутствует. Это удобно для действительно необязательного выбора, но не заменяет проверку обязательных загруженных данных. Если каталог должен иметь корректные записи, тихое получение пустого значения от всех повреждённых карточек скроет отказ. В следующем уроке такая ситуация будет обнаруживаться явно.

Выбирайте деструктуризацию там, где она делает зависимость понятнее. Если функция использует только одно свойство один раз, обычное course.title может быть проще. Если перед объявлением приходится долго разбирать вложенные фигурные скобки, лучше разделить извлечение на два шага. Возможность короткой записи не требует использовать её во всех местах программы.

Остаток свойств объекта можно собрать через const { title, ...otherFields } = course. Новый объект содержит остальные собственные поля, но такой приём не означает проверку или сокрытие всех данных. Если источник получит дополнительное поле, оно тоже может попасть в остаток. Поэтому для проверенного результата валидатор позже перечислит разрешённые поля явно.

Это различие особенно важно для внешних данных. Короткая копия неизвестного объекта переносит всё, что в нём находится, а создание записи из известных имён задаёт более узкий контракт. В нашем примерном выводе строка и число не исполняются, однако принцип контроля модели нужен независимо от HTML.

Не пытайтесь деструктурировать результат find до проверки наличия. Если подходящей карточки нет, источник равен undefined, и извлечение свойства вызовет ошибку. Можно явно проверить результат, затем извлечь поля найденного курса. Это не отказ полезного синтаксиса: это правильный порядок действий, в котором сначала устанавливается существование нужных данных, а затем выбирается удобная форма чтения.

На следующем шаге перейдём от предположения корректности к явной проверке. Программа должна распознать неверный тип записи до чтения полей и сообщить об ошибке там, где данные входят в каталог. Деструктуризация останется удобным способом доступа после этой границы.

Оглавление курса