Practical JavaScript: Arrays vs. Objects
![]()
Someone asked me today: “How do you know when to use an object, and when to use an array?” I couldn’t find a resource online that gave the answer I wanted, so… I will be the change I want to see.
TL;DR Intro
Think about what your particular data represents: If it’s a single entity with named properties, you want an object. If it’s a group of entities of the same type/shape, or if order matters, you likely want an array.
If it’s still unclear, think about how you’ll be working with the data: manipulating individual properties? Object, probably. Doing manipulations over the data as a whole, or filtering and manipulating chunks of the data? An array, I’ll bet.
Also, if you’re working with existing data and it’s already an object or array, you probably wouldn’t convert it to another without good reason.
Two types of collections
Arrays and objects are two ways of collecting data into a group. The data can be primitives (strings, numbers, booleans):
…or they can be made of other arrays or objects:
So, why would you pick one over the other? At the risk of oversimplifying, it comes down to ease-of-use and performance.
Insertion, Deletion, Iteration, Updation
What do I mean by ease-of-use? When we group data together, we usually want to use it in some way. Specifically, we want to add elements, remove them, access/update them, or iterate over each of them.
Side note: the person asking was using React, so immutability is a concern, which has an impact on ease-of-use/readability. Mutable methods like push(), pop(), splice() , etc would make things simpler, but in these examples I’ll be thinking immutably. There are also a few different methods to achieve each of these examples (spread vs. concat , for instance), but I’ll just stick to one method.
Insertion
Let’s say we have this array of names:
We have a new name that we want to add one to either end. Easy!
But when we want to insert a name into the middle of an array, we need to know the index. We can’t insert something unless we know where it needs to go, so if we don’t have the index, we need to find it using Array.findIndex , which takes time to iterate though the array.
Objects, on the other hand, don’t keep track of order, so it’s simple to add properties anywhere since there’s no concept of beginning/middle/end, and fast, since we don’t need to iterate:
Deletion
What about removing items? Again, it depends! Easy to remove from the beginning or end of an array:
Also pretty easy from the middle, but again, you need to know the index you want removed (in this case index 1 ), or iterate through to filter out the value:
Like adding a property to an object, removing an object property is simple no matter where in the object (since there’s not really a concept of ‘where’ something is in an object).
Updation
Updation — not a real word. When we want to update an element in an array, we can do it by index, or if we don’t know the index we can iterate over it looking for the element based on the element’s value (or a property on the element). Updating via iteration is common, since we often deal with large sets of data where we don’t know the index, or dynamic data where the index can change.
Updating an object, once again, is much more straightforward:
Accessing Elements
If you just need to get the value of an element in an array (without updating it), it’s simple if you know the index, and not much harder if you don’t (but you know something about the element you’re looking for):
Accessing object properties is easy too:
Iteration and methods
So far, arrays have been kind of a drag compared to objects. Doing anything with a single array element requires that you know the index, or requires a bit more code. Finally, with iteration, it’s time for the array to shine. When you want do do some transformation to the elements as a batch, arrays are designed for this:
To iterate over an object, our only real option is a for. in loop, but (in my opinion) it’s often simpler/more readable to just… convert it to an array. Object.keys/values/entries iterates over the keys, values, or both, and gives us an array of that data:
Arrays also have other methods that allow you to work with the data, which objects don’t have:
You could implement any of these pretty easily with for. in , but arrays have them out of the box.
Performance
Speed isn’t always a consideration, but when it is there can be a big difference between arrays and objects. There are plenty of resources on the internet about array vs. object performance, but briefly: array manipulation is slower when you don’t know the index (linear time, or O(n)), because you have to iterate over each element until you find the one you’re looking to use. If you do know the index and immutability isn’t a concern, you don’t need to iterate and can access/update the element at that index quickly (constant time, or O(1)). Object property lookup/update/insertion/deletion happens quickly (also constant time) because the property name gives you a reference so you don’t have to go looking for the element you want.
Conclusion
The rule of thumb is: groups of similarly typed data, which you need ordered or want to manipulate as a batch are better suited for arrays, and grouped properties of a single entity are better suited for objects. Using the right data type isn’t always a clear choice, but the more you work with each data type, the more obvious it will be which one makes sense in any given situation.
Javascript: Understanding Objects vs Arrays (1 of 2)
I ran into this question several times while browsing through stackoverflow’s javascript queue so I decided to recycle my answers, elaborate a bit and make it into a blog post. If you are new to javascript, understanding these two data types is very important and could potentially save you some headache down the road.
Declaration & Augmentation
Array(s):
Arrays come with several, very useful native methods. We can add a new element to existing array instance using push() and remove the last element from the array via pop(). We can also use splice() to remove n elements and/or insert new element(s) at index i.
You can find out more about Arrays, their methods and properties at MDN.
Object(s):
Think about objects as associative arrays, i.e. list of key -> value pairs.
These keys are referred to as object properties.
Follow this pattern when declaring new objects:
Javascript is a dynamic language. You are allowed to extend/augment objects and their prototype after object has been defined as well as during runtime.
Deleting a property is very simple:
Javascript community prefers using literal notation as it makes the code cleaner and easier to understand. Whether you decide to go with literal declaration or using constructors, be consistent. Read more about Objects on MDN.
Checking if Property or Value Exists
Array(s):
Generally when we work with arrays, we care less about indexes and more about values.
One of the common operations we perform with Arrays is checking if a certain value is in the array.
This is easily accomplished using indexOf() method.
To find out more about Bitwise NOT (
) operator and why it works in this scenario, check out the good ole MDN.
Object(s):
In contrast to Arrays, we generally want to know if an Object contains a certain property. Usually we will write a function that takes Object as an argument and will expect that it contains a certain set of properties. This Object can come from an API or some other piece of code and we shouldn’t rely on it having all the properties we expect. It is always a good idea to check whether the property exists before accessing the value behind that property. Objects come with hasOwnProperty() method which allows us to do just that.
Ok folks, I have a confession to make. I was not entirely honest with you. There is a «small» detail that I have purposefully omitted when comparing these two data types. Read Part 2 of this post to find out more!
В чем разница между массивом JavaScript и объектом, кроме свойства .length?
Как я думаю, массив JS — это просто хэш-карта, которая принимает только целое значение в качестве ключа. И свойство .length просто возвращает наибольший индекс + 1.
Правильно ли это? Есть ли другие отличия?
4 ответа
Разница заключается в следующем:
Edit:
Кроме того, посмотрите этот раздел из спецификаций ECMAScript, поскольку он точно объясняет, что такое массив: http://bclary.com/2004/11/07/#a-15.4
Вы ошибаетесь; массивы могут иметь любые ключи, которые вы хотите.
Кроме того, они наследуют прототип Array .
Массив JavaScript также наследуется от Object, поэтому он получит все возможности объекта. Массивы JavaScript имеют дополнительную функциональность:
Пока вы можете добавлять целочисленные свойства к обычным объектам и добавлять нецелые свойства в массивы, это не даст объекту специальных свойств и методов, которые имеет Array, а специальная функция Array применима только к его свойствам с целыми ключами.
Хорошая ссылка на все дополнительные свойства, которые наследует Arrays, — это статья Центра разработчиков Mozilla в Array. Удостоверьтесь, что обратите внимание на небольшие «нестандартные» и «Требуется JavaScript 1.x», если вы хотите поддерживать совместимость между браузерами.
Объекты массива могут иметь любое свойство, которое может иметь объект. Единственным специальным свойством является свойство length, которое (потенциально) обновляется, когда вы устанавливаете свойство «index массива», а также может быть использовано для удаления элементов массива, если оно установлено на меньшее значение, чем его текущий.
«Индексы массива» — это строки (все свойства объекта), которые являются каноническим десятичным представлением целого числа без знака в диапазоне 0..2 ^ 32-2 (т.е. от «0» до «4294967294» ). Предел один ниже максимального значения 32-разрядного значения без знака, потому что значение поля длины всегда равно 32-битовому целочисленному значению без знака.
Массивы
Объекты позволяют хранить данные со строковыми ключами. Это замечательно.
Но довольно часто мы понимаем, что нам необходима упорядоченная коллекция данных, в которой присутствуют 1-й, 2-й, 3-й элементы и т.д. Например, она понадобится нам для хранения списка чего-либо: пользователей, товаров, элементов HTML и т.д.
В этом случае использовать объект неудобно, так как он не предоставляет методов управления порядком элементов. Мы не можем вставить новое свойство «между» уже существующими. Объекты просто не предназначены для этих целей.
Для хранения упорядоченных коллекций существует особая структура данных, которая называется массив, Array .
Объявление
Существует два варианта синтаксиса для создания пустого массива:
Практически всегда используется второй вариант синтаксиса. В скобках мы можем указать начальные значения элементов:
Элементы массива нумеруются, начиная с нуля.
Мы можем получить элемент, указав его номер в квадратных скобках:
Мы можем заменить элемент:
…Или добавить новый к существующему массиву:
Общее число элементов массива содержится в его свойстве length :
Вывести массив целиком можно при помощи alert .
В массиве могут храниться элементы любого типа.
Список элементов массива, как и список свойств объекта, может оканчиваться запятой:
«Висячая запятая» упрощает процесс добавления/удаления элементов, так как все строки становятся идентичными.
Получение последних элементов при помощи «at»
Допустим, нам нужен последний элемент массива.
Некоторые языки программирования позволяют использовать отрицательные индексы для той же цели, как-то так: fruits[-1] .
Однако, в JavaScript такая запись не сработает. Её результатом будет undefined , поскольку индекс в квадратных скобках понимается буквально.
Мы можем явно вычислить индекс последнего элемента, а затем получить к нему доступ вот так: fruits[fruits.length — 1] .
Немного громоздко, не так ли? Нам нужно дважды написать имя переменной.
К счастью, есть более короткий синтаксис: fruits.at (-1) :
Другими словами, arr.at(i) :
- это ровно то же самое, что и arr[i] , если i >= 0 .
- для отрицательных значений i , он отступает от конца массива.
Методы pop/push, shift/unshift
Очередь – один из самых распространённых вариантов применения массива. В области компьютерных наук так называется упорядоченная коллекция элементов, поддерживающая два вида операций:
- push добавляет элемент в конец.
- shift удаляет элемент в начале, сдвигая очередь, так что второй элемент становится первым.
Массивы поддерживают обе операции.
На практике необходимость в этом возникает очень часто. Например, очередь сообщений, которые надо показать на экране.
Существует и другой вариант применения для массивов – структура данных, называемая стек.
Она поддерживает два вида операций:
- push добавляет элемент в конец.
- pop удаляет последний элемент.
Таким образом, новые элементы всегда добавляются или удаляются из «конца».
Примером стека обычно служит колода карт: новые карты кладутся наверх и берутся тоже сверху:
Массивы в JavaScript могут работать и как очередь, и как стек. Мы можем добавлять/удалять элементы как в начало, так и в конец массива.
В компьютерных науках структура данных, делающая это возможным, называется двусторонняя очередь.
Методы, работающие с концом массива:
Удаляет последний элемент из массива и возвращает его:
И fruits.pop() и fruits.at(-1) возвращают последний элемент массива, но fruits.pop() также изменяет массив, удаляя его.
Добавляет элемент в конец массива:
Вызов fruits.push(. ) равнозначен fruits[fruits.length] = . .
Методы, работающие с началом массива:
Удаляет из массива первый элемент и возвращает его:
Добавляет элемент в начало массива:
Методы push и unshift могут добавлять сразу несколько элементов:
Внутреннее устройство массива
Массив – это особый подвид объектов. Квадратные скобки, используемые для того, чтобы получить доступ к свойству arr[0] – это по сути обычный синтаксис доступа по ключу, как obj[key] , где в роли obj у нас arr , а в качестве ключа – числовой индекс.
Массивы расширяют объекты, так как предусматривают специальные методы для работы с упорядоченными коллекциями данных, а также свойство length . Но в основе всё равно лежит объект.
Следует помнить, что в JavaScript существует 8 основных типов данных. Массив является объектом и, следовательно, ведёт себя как объект.
Например, копируется по ссылке:
…Но то, что действительно делает массивы особенными – это их внутреннее представление. Движок JavaScript старается хранить элементы массива в непрерывной области памяти, один за другим, так, как это показано на иллюстрациях к этой главе. Существуют и другие способы оптимизации, благодаря которым массивы работают очень быстро.
Но все они утратят эффективность, если мы перестанем работать с массивом как с «упорядоченной коллекцией данных» и начнём использовать его как обычный объект.
Например, технически мы можем сделать следующее:
Это возможно, потому что в основе массива лежит объект. Мы можем присвоить ему любые свойства.
Но движок поймёт, что мы работаем с массивом, как с обычным объектом. Способы оптимизации, используемые для массивов, в этом случае не подходят, поэтому они будут отключены и никакой выгоды не принесут.
Варианты неправильного применения массива:
- Добавление нечислового свойства, например: arr.test = 5 .
- Создание «дыр», например: добавление arr[0] , затем arr[1000] (между ними ничего нет).
- Заполнение массива в обратном порядке, например: arr[1000] , arr[999] и т.д.
Массив следует считать особой структурой, позволяющей работать с упорядоченными данными. Для этого массивы предоставляют специальные методы. Массивы тщательно настроены в движках JavaScript для работы с однотипными упорядоченными данными, поэтому, пожалуйста, используйте их именно в таких случаях. Если вам нужны произвольные ключи, вполне возможно, лучше подойдёт обычный объект <> .
Эффективность
Методы push/pop выполняются быстро, а методы shift/unshift – медленно.
Почему работать с концом массива быстрее, чем с его началом? Давайте посмотрим, что происходит во время выполнения:
Просто взять и удалить элемент с номером 0 недостаточно. Нужно также заново пронумеровать остальные элементы.
Операция shift должна выполнить 3 действия:
- Удалить элемент с индексом 0 .
- Сдвинуть все элементы влево, заново пронумеровать их, заменив 1 на 0 , 2 на 1 и т.д.
- Обновить свойство length .
Чем больше элементов содержит массив, тем больше времени потребуется для того, чтобы их переместить, больше операций с памятью.
То же самое происходит с unshift : чтобы добавить элемент в начало массива, нам нужно сначала сдвинуть существующие элементы вправо, увеличивая их индексы.
А что же с push/pop ? Им не нужно ничего перемещать. Чтобы удалить элемент в конце массива, метод pop очищает индекс и уменьшает значение length .
Действия при операции pop :
Метод pop не требует перемещения, потому что остальные элементы остаются с теми же индексами. Именно поэтому он выполняется очень быстро.
Аналогично работает метод push .
Перебор элементов
Одним из самых старых способов перебора элементов массива является цикл for по цифровым индексам:
Но для массивов возможен и другой вариант цикла, for..of :
Цикл for..of не предоставляет доступа к номеру текущего элемента, только к его значению, но в большинстве случаев этого достаточно. А также это короче.
Технически, так как массив является объектом, можно использовать и вариант for..in :
Но на самом деле это – плохая идея. Существуют скрытые недостатки этого способа:
Цикл for..in выполняет перебор всех свойств объекта, а не только цифровых.
В браузере и других программных средах также существуют так называемые «псевдомассивы» – объекты, которые выглядят, как массив. То есть, у них есть свойство length и индексы, но они также могут иметь дополнительные нечисловые свойства и методы, которые нам обычно не нужны. Тем не менее, цикл for..in выведет и их. Поэтому, если нам приходится иметь дело с объектами, похожими на массив, такие «лишние» свойства могут стать проблемой.
Цикл for..in оптимизирован под произвольные объекты, не массивы, и поэтому в 10-100 раз медленнее. Увеличение скорости выполнения может иметь значение только при возникновении узких мест. Но мы всё же должны представлять разницу.
В общем, не следует использовать цикл for..in для массивов.
Немного о «length»
Свойство length автоматически обновляется при изменении массива. Если быть точными, это не количество элементов массива, а наибольший цифровой индекс плюс один.
Например, единственный элемент, имеющий большой индекс, даёт большую длину:
Обратите внимание, что обычно мы не используем массивы таким образом.
Ещё один интересный факт о свойстве length – его можно перезаписать.
Если мы вручную увеличим его, ничего интересного не произойдёт. Зато, если мы уменьшим его, массив станет короче. Этот процесс необратим, как мы можем понять из примера:
Таким образом, самый простой способ очистить массив – это arr.length = 0; .
new Array()
Существует ещё один вариант синтаксиса для создания массива:
Он редко применяется, так как квадратные скобки [] короче. Кроме того, у него есть хитрая особенность.
Если new Array вызывается с одним аргументом, который представляет собой число, он создаёт массив без элементов, но с заданной длиной.
Давайте посмотрим, как можно оказать себе медвежью услугу:
Как мы видим, в коде, представленном выше, в new Array(number) все элементы равны undefined .
Чтобы избежать появления таких неожиданных ситуаций, мы обычно используем квадратные скобки, если, конечно, не знаем точно, что по какой-то причине нужен именно Array .
Многомерные массивы
Массивы могут содержать элементы, которые тоже являются массивами. Это можно использовать для создания многомерных массивов, например, для хранения матриц:
toString
Массивы по-своему реализуют метод toString , который возвращает список элементов, разделённых запятыми.