Николай Ланец
20 нояб. 2021 г., 13:38

JavaScript. Массив - это тоже объект.

Всем привет!

Долго думал какой заголовок написать. В голову приходило в том числе шуточное "Массив - это тоже объект, только маленький еще", но на самом деле все совсем наоборот: Массив (Array) - это производное от объекта (Object). То есть в массивах, как и в объектах, можно использовать не только "скобочную нотацию", но и "точечную".

Сейчас чуть подробнее разверну вводную часть. Когда мы изучали массивы, мы проходили Доступ к данным массива, используя индексы. Пример:
const array = [50,60,70]; array[0]; // равно 50 const data = array[1]; // равно 60
Это и есть "скобочная нотация", то есть использование квадратных скобок для доступа к элементам массивов. В объектах для доступа к свойствам тоже можно использовать скобочную нотацию. Это рассматривалось в уроке Доступ к свойствам объекта с помощью скобок. А в уроке Доступ к свойствам объекта через точку рассматривалась "точечная нотация". Но вот к элементам массива вы не можете обратиться через точку, так как имена свойств в объектах (как и прочие переменные) не могут начинаться с числа. А как нам известно, у массивов индексы являются числами (во всяком случае нам четко говорили, что очередность индексов в массивах, как и в строках, начинается с числа 0). Но действительно ли только числа могут быть индексами в массивах? На самом деле нет. И прежде, чем мы рассмотрим примеры, следует вот что запомнить: все имена свойств объектов автоматически приводятся к типу "строка". Это значит, что если объекту задать в качестве имени свойства какое-либо число, оно автоматически переводится в строку. Этот момент отдельно уточняется в уроке Создание объектов JavaScript. И что же тогда получается у нас с индексами массивов? Если мы говорим, что индекс - это то же имя свойства, и если все имена свойств в объектах переводятся в строки, то и передаваемые индексы в массивах тоже переводятся в строки? Да, так и есть. И давайте убедимся в этом.
arr = [10,20,30] arr // (3) [10, 20, 30] arr[1] // 20 arr['1'] // 20
То есть здесь, как мы видим, мы в качестве индекса передали строку и получили то же самое значение, что и по числовому индексу. Но это может еще ничего не доказывать. Как и в случае с нестрогим сравнением, у нас может просто происходить преведение строки в типу Число. Но это предположение легко опровергнуть.
arr['1str'] // undefined arr['1str'] = 123; arr['1str'] // 123 arr[parseInt('1str')] // 20
Как мы видим, при первом обращении к элементу по строковому индексу, мы получаем undefined, так как такого элемента нет. А вот после мы задаем значение с таким индексом и потом успешно получаем данное значение. При этом если мы выполним парсинг числа из этой строки (в результате чего получаем 1), то успешно получаем значение элемента с индексом 1. То есть никакого приведения строки к числу не выполняется.

Но тут вопрос: вот мы добавили значение с каким-то непонятным индексом. А как теперь будет выглядеть наш массив? А вот так:
(3) [10, 20, 30, 1str: 123] 0: 10 1: 20 1str: 123 2: 30 length: 3
То есть как мы видим, в нем есть и все 3 предыдущих элемента, да еще и наш новый добавленный элемент. При этом очередность свойств сохранена. То есть это не тот случай, когда котлеты отдельно, мухи отдельно. Тут явно все общим скопом лежит.
Но обратите внимание вот на что: length у него по-прежнему 3. То есть элемента 4, а длина массива - 3. Более того, если выполнить такой код:
arr.map((n) => console.log(n))
То получим результат
10 20 30
То есть массив нам отдает только те элемены, которые добавлены с цифровым индексом. Интересное поведение, не правда ли?

А вот можно еще вот такой забавный вариант рассмотреть:
arr = Object.assign([], { 0: 0, 1: 10, str: "Sdfdsf", '2': 20, }); // Результат (3) [0, 10, 20, str: 'Sdfdsf'] 0: 0 1: 10 2: 20 str: "Sdfdsf" length: 3
Если вам не известен метод Object.assign(), обязательно изучите его. Крайне полезный и довольно часто используется. Суть его в том, что он объединяет два и более объекта в один (прибавляя к первому все последующие переданные поочередно).

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

Уже в процессе написания статьи, перепроверяя некоторые моменты и рассуждая, я задумался "А как же можно перехвотить обновление объекта?". Ведь очевидно, что на массив наложены многие обработчики, цель которых - при изменении его свойств разобраться, что отправить в общий набор элементов, а что просто как свойство применить. И тут я наткнулся на весьма занятную штуку - Proxy. Крутейшая же штука! :) Получается, есть возможность создать объект и навесить обработчики, которые будут срабатывать на всякие обновления. Пример:
const validator = { set: function(target, key, value) { console.log(`The property ${key} has been updated with ${value}`); return true; } }; const store = new Proxy({}, validator); store.a = 'hello';
Выполнив store.a = 'hello', то есть добавив объекту новое свойство со значением, вы получите сообщение "The property a has been updated with hello".

А выполнив такое:
Object.assign(store, { 1: "SDfsdf", 2: "Dfgfdg", '3': "Dfgfdg", })
Вы получите 3 сообщения:
The property 1 has been updated with SDfsdf
The property 2 has been updated with Dfgfdg
The property 3 has been updated with Dfgfdg

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

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

Спасибо! Может и практическое применение найдётся)

Добавить комментарий