Предисловие

Я давно хотел написать статью или пост на эту тему, поскольку заметил проблему с некорректным пониманием синтаксиса так называемой «деструктуризации» в JavaScript. Эта информация будет особенно интересна разработчикам, использующим React, где деструктуризация (например, в хуке useState) встречается повсеместно.

Меня также вдохновило на столь обширное и детальное изложение недавнее видео одного JavaScript-инженера. Он очень подробно, на уровне байт-кода, продемонстрировал, как бездумное использование синтаксического сахара, основанное на наших собственных домыслах о работе JS, может снижать скорость выполнения кода. Соответственно, некоторые примеры и тезисы я буду заимствовать из этого видео для более наглядной демонстрации проблемы (я бы назвал это так), которая существует в мире JS-разработчиков.

Общепринятое понимание

В целом, сложилось мнение, что деструктуризация — это синтаксис, который в одном случае помогает получить свойства у объекта, а в другом — элементы у массива.

То есть в первом варианте чаще всего считают, что слева мы также описываем объект, указывая свойства, которые нужно извлечь. А в случае массива — что мы описываем массив слева, указывая по индексу те значения, которые хотим получить для использования.


// Деструктуризация объекта
const someObj = { name: 'Andrii', age: '24' };

const { name, age } = someObj;
console.log(`Name: ${name}, age: ${age}`);

// Деструктуризация массива
const arr = [ 1, 2, 3, 4, 5 ];

const [ , , el3, , el5 ] = arr;
console.log(el3, el5);

Что это на самом деле за конструкции?

Конструкция, расположенная по левую сторону в первом примере, называется не Объект, а Паттерн Присваивания Объекта (Object Assignment Pattern).

const { name, age } = someObj;

А в случае массива данная конструкция называется Паттерн Присваивания Массива (Array Assignment Pattern).

const [ , , el3, , el5 ] = arr;

Что такое Assignment (Присваивание/Связывание)

Assignment — это связывание некоторого идентификатора (переменной) с определёнными данными. Вот три простых примера Assignment:

var variable = 1;
let variable2 = 2;
const variable3 = 3;

Знак = стоит воспринимать не как «variable становится равно 1», а как «идентификатор variable связывается с данными, которые в данном случае являются числом 1».

Что такое Assignment Patterns

Паттерн связывания на основе входящих данных.

Это тот же Assignment, но с расширенным синтаксисом, который позволяет разбирать входящие данные на составляющие согласно заданному алгоритму и связывать результат с одним или несколькими идентификаторами.

Посмотрим на пример:

const { name, age } = someObj;

Какие вопросы могут возникнуть:

  • Что именно здесь является паттерном?

  • О чём мы вообще говорим, когда упоминаем паттерн?

  • Чем может быть правая часть?

Рассмотрим случаи, которые могут показаться неочевидными из-за неверного понимания сути Assignment Patterns.

А что будет, если мы сделаем вот так?

const { name, age } = 1;

Результат:

  • name будет undefined.

  • age будет undefined.

Объяснение: Создаются два идентификатора, которые будут указывать на значение undefined, так как правая часть будет приведена к объекту Number. Поскольку этот объект (и его прототип) не имеет собственных свойств name или age, связывание завершится неудачей.

А если так?

const { toFixed } = 1;

Результат:

  • toFixed будет содержать функцию Number.prototype.toFixed().

Объяснение: В этом случае JavaScript также автоматически преобразует примитивное число (1) в его объектный эквивалент (new Number(1)). Объект Number наследует от Number.prototype, который содержит стандартные методы, такие как toFixed. Мы успешно связываем ссылку на этот метод с идентификатором toFixed.

А если так?

const { length } = 'abc';

Результат:

  • length будет содержать число 3.

Объяснение: Аналогично числу, примитивная строка ('abc') временно преобразуется в объект String (new String('abc')). Объект String имеет свойство length, значение которого успешно связывается с идентификатором length.

Почему это так происходит?

На самом деле Assignment Patterns — это всего лишь синтаксический сахар, очень развитый синтаксический сахар, который позволяет описать ровно то, что вы могли бы написать собственноручно, используя традиционный способ объявления идентификаторов.

То есть это своего рода своеобразный мета-язык, описывающий последовательность действий, которые нужно выполнить с идентификаторами и данными для их связывания.

Давайте рассмотрим это на примере:

const someObj = { name: 'Andrii', age: '24' };

Паттерн:

const { name, age } = someObj;

Который можно вручную переписать вот так:

const name = someObj.name;
const age = someObj.age;

То есть, pattern по сути повторяет логику, которую мы могли бы реализовать вручную, связывая каждый идентификатор построчно. По сути, pattern — это просто сокращённая запись.

Assignment Patterns: Вложенность

Таким образом, мы начинаем прослеживать логику: синтаксис Assignment Pattern — это иная форма записи определённой логики работы с входящими данными.

Насколько сложен этот язык?

const someObj = { user: { name: 'Andrii', age: '24' } };
const { user: { name: userName } } = someObj;

Важно отметить: если мы посмотрим на эти две строки, то увидим, что синтаксис очень схож. Из-за этого, зная, что в первой строке мы создаём объект и с помощью : связываем значения (определяем свойства), мы можем интуитивно думать, что во второй строке это также объект, где : означает присваивание и т. д. Но на самом деле во второй строке : — это синтаксис, который указывает или показывает, куда мы двигаемся дальше по структуре.

Если мы рассмотрим описанный паттерн, то увидим, что можем использовать вложенные паттерны для связывания данных с определённым идентификатором на разных уровнях вложенности.

Assignment Patterns: Идентификаторы

Рассмотрим примеры, которые демонстрируют, что это на самом деле мета-язык, а не просто описание объекта.

Два идентификатора для одних и тех же данных:

const someObj = { user: { name: 'Andrii', age: '24' } };
const { user: { name: userName }, user: { name: userName2 } } = someObj;

То есть мы можем сформировать Assignment Patterns таким образом, что одни и те же данные будут связаны с любым необходимым нам количеством идентификаторов.

Один и тот же идентификатор:

const { user: { name: userName }, user: { age: userName } } = someObj;

Мы можем использовать один и тот же идентификатор, и в конечном итоге наш идентификатор userName будет связан с данными '24' (значением, присвоенным последним в процессе деструктуризации).

Assignment Pattern: Промежуточные выводы

На примерах мы увидели, что Assignment Pattern — это своего рода редактор, который описывает правила связывания идентификаторов с данными.

Он позволяет перезаписывать уже существующие идентификаторы, дублировать связи и использовать вложенность.

Кроме этого, он обладает возможностью проверки значения на undefined и соответствующей реакцией на это событие:

const { user: { status: userName = 'boh' } } = someObj;

Если результат связывания идентификатора userName возвращает undefined, то подобная конструкция (= 'boh') приводит к тому, что наш идентификатор будет связан со значением 'boh'.

То есть, это равносильно тому, как если бы мы вручную проверили вложенность через if, и если бы status был undefined, то связали бы userName со значением 'boh'Важно отметить: это не означает какое-то дефолтное значение или что-то в этом роде, это означает, что если мы получили undefinedто мы свяжем наш идентификатор со значением, указанным справа от =.

Object и Array Assignment Pattern

Важно отметить, что Object и Array Assignment Pattern — это один и тот же механизм, за одним существенным отличием. Названия могут вводить в заблуждение, и мы далее рассмотрим, чем именно они являются на самом деле.

На основе стартовых примеров, рассмотрим, как выглядят эти паттерны:

const someObj = { name: 'Andrii', age: '24' };

const { name, age } = someObj;
console.log(`Name: ${name}, age: ${age}`);

const arr = [ 1, 2, 3, 4, 5 ];

const [ , , el3, , el5 ] = arr;
console.log(el3, el5);

До этого все примеры описывались с использованием Object Assignment Pattern, поскольку он проще для понимания. Теперь перейдём к дополнительной структуре, чтобы объяснить разницу между ними.

Object и Array Assignment Pattern: Разница

Object и Array Assignment Pattern: Разница

В чём же разница? В механизме определения того, какие данные связываются с идентификатором.

  • Object — оперирует ключом/свойством.

  • Array — оперирует итератором.

Когда мы используем Object Assignment Pattern, мы всегда оперируем тем, как ключ связывается с идентификатором (всегда ключ-идентификатор).

А Array Assignment Pattern оперирует номером итерации при выполнении соответствующего итератора. Если посмотреть на пример, то начинает казаться, что мы указываем позицию, связанную с порядком в массиве, то есть el3 просто связывается с 3-м значением или 2-м по индексу — но это не так!

const arr = [ 1, 2, 3, 4, 5 ];

const [ , , el3, , el5 ] = arr;

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

Array Assignment Pattern и Итератор

Это не связь по индексу в массиве, это связь с определённым шагом итерации:

const arr = [ 1, 2, 3, 4, 5 ];
const [ , , el3, , el5 ] = arr;

Это не связь по индексу в массиве, это связь с определённым шагом итерации:

function* fun1() {
  yield 1;
  yield 2;
  yield 3;
}

const [ one, two, three ] = fun1();

То есть, Array Assignment Pattern — это не про массивы, это про итераторы.

Где мы можем увидеть работу Array Assignment Pattern, исходя из того, что уже есть в JS:

const [first] = 'abc'
console.log(first) // a

const [, second] = 'abc'
console.log(second) // b

У нас есть String, который также включает в себя итератор, и мы можем написать такой код. Он показывает, что Array Assignment Pattern работает с итераторами. Просто в данном случае он использует базовый итератор, который по умолчанию работает по порядку. Но никто не запрещает нам переопределить итератор и сделать так, чтобы на первой итерации возвращался, например, последний символ или элемент.

Assignment Pattern: Комбинирование

Как следствие — комбинирование способов связывания:

const obj = { user: { name: 'Andrii', numbers: [ 1, 2, 3, 4, 5 ] } };

var { user: { numbers: [ , secondNumber ] } } = obj;

Учитывая возможность использования вложенных паттернов, мы можем комбинировать Object Assignment Pattern и Array Assignment Pattern, создавая длинные описания вложенностей. Хотя на определённом этапе это может стать нечитабельным, важно понимать, что у нас есть возможность описывать паттерн, который позволяет работать с разными типами и уровнями вложенности.

Assignment Pattern: Основные Выводы

Постепенно должно сформироваться понимание, что Assignment Pattern (AP) — это мета-механизм, позволяющий комбинировать разного рода шаблоны связывания идентификаторов с входящей структурой данных. Это подобно тому, как мы могли бы описать это пошагово, используя «традиционное» объявление и/или связывание данных с идентификатором.

То есть, любая синтаксическая конструкция связывания чего-то с чем-то может быть реализована в синтаксисе AP.

Что делает его гибким и «монстроподобным».

V8 и Подводные Камни Производительности

Давайте рассмотрим, как V8 работает с этими двумя Assignment Patterns.

Пример с Object Assignment Pattern:

const fun1 = () => {
    const arr = [ 1, 2, 3, 4 ];

    const { 0: fist, 3: last } = arr;
}

fun1();

Напишем пример Array assignment pattern

const fun2 = () => {
    const arr = [ 1, 2, 3, 4 ];
    
    const [ fist, , , last ] = arr;
}

fun2();

Если мы посмотрим на код, сгенерированный V8 (слева для Object assignment pattern, справа для Array assignment pattern):

Сгенерированный код V8
Сгенерированный код V8

Мы увидим, что объём кода, сгенерированного для Object Assignment Pattern (слева), намного меньше, чем объём кода, сгенерированного для Array Assignment Pattern (справа).

Это прекрасный пример, демонстрирующий разницу в механизмах работы между этими двумя паттернами:

  • Array Assignment Pattern создан для того, чтобы находить значения на определённом шаге итератора.

  • Object Assignment Pattern создан для того, чтобы находить значение по ключу/индексу.

То есть, когда у нас есть массив (Array), и мы знаем индекс (который для V8 является ключом) тех данных, которые нам нужны, с точки зрения производительности получить результат будет быстрее, если указать ключ и связать его с идентификатором.

Чтобы наглядно рассмотреть этот момент, мы можем описать, как бы мы реализовали это вручную.

Если бы мы хотели получить значение по индексу 3, то написали бы что-то вроде:

const value = arr[3]

Что как раз очень похоже на то, что делает Object Assignment Pattern (доступ по ключу).

Но вряд ли мы бы писали что-то вроде этого:

for (let index=0; index<arr.length; index++) {
	if(index===3) {
		value = arr[index];
	}
}

Что в общих чертах напоминает работу механизма Array Assignment Pattern (перебор итератора).

Именно поэтому, когда мы используем Array Assignment Pattern в тех случаях, где мы знаем индекс того, что нужно получить, мы теряем минимум около 10 процентов производительности на каждую такую конструкцию.

Также хотел бы оставить информацию о человеке, чьё видео вдохновило меня на написание этой статьи об Assignment Pattern. Это очень хороший специалист своего дела, рекомендую к просмотру!

? Связь со мной и Мои Ресурсы

Спасибо, что дочитали до конца! Надеюсь, эта статья помогла вам взглянуть на синтаксис Assignment Patterns с более глубокой, V8-ориентированной точки зрения.

Подписывайтесь на мои ресурсы:
Telegram
Github

pproger@pproger.me

Также хотел бы оставить информацию о человеке, чьё видео вдохновило меня на написание этой статьи об Assignment Pattern. Это очень хороший специалист своего дела, рекомендую к просмотру!

Youtube — As For JS

Комментарии (1)


  1. TAZAQ
    13.11.2025 20:53

    Сначала я удивился. Я недавно видел что-то похожее у Мурыча. Подумал, что эта статья - плагиат. Но в конце статьи заметил ссылку.