Introduction
Groups of data in different forms are one of the fundamental data structures in most programming languages. In many cases, groups of data expressed through different data types are referred to as Collections.
In this guide - we'll take a look at Collections in JavaScript and when to use which type of collection. The three main Collection Groups we'll be taking a look at are:
- Indexed Collections
- Keyed Collections
- DOM Collections
Indexed Collections
An indexed collection is a collection of data which is listed by their index. JavaScript collection indices are 0-based, which means they start at 0
, not 1
and go up to n-1
, n
being the number of objects in the collection. JavaScript has two kinds of indexed collections Array
s and TypedArray
s.
Array object
An Array
object in JavaScript is an ordered list, whose elements can be accessed using indices. There are multiple ways of creating an Array
object in JavaScript, and there's not much difference under the hood:
let myArray1 = [x1, x2, ... , xN];
let myArray2 = new Array(x1, x2, ... , xN);
let myArray3 = Array(x1, x2, ... , xN);
Arrays in JavaScript are not type-based, which means that you don't have to define the type of the array beforehand and you don't have to add only homogeneous elements:
let myArray = ["one", 1, "two", 2];
This is completely valid syntax, and the array is happy to store references to both strings and integers. Let's quickly re-clarify what indices of an array actually are:
let myArray = ["one", 1, "two", 2];
// 0 1 2 3 --> index values
So starting from 0
up to n-1
, where n
is the length of the array.
Each array has a property called length
. The length of an array is determined at the same time the array is initialized. So, when we create an array, a value is assigned to its length
property.
If we
console.log(myArray.length)
, the output would be4
.
Additionally, changing the length will change the array. You can easily truncate an array by shortening its length, and expand it by lengthening it:
let myArray = ["one", 1, "two", 2];
console.log(myArray);
myArray.length = 5;
console.log(myArray);
myArray.length = 1;
console.log(myArray);
This results in:
["one", 1, "two", 2]
["one", 1, "two", 2, undefined]
["one"]
In JavaScript, you can create an array without any elements, but of some length. You can think of this as something like allocating (reserving) memory in advance. This is exactly what kicks in when we expand an array by changing its length
to be greater than before.
Since there are three ways to create an array filled with elements, there are also three ways to create empty arrays with allocated memory, like so:
let myArray1 = new Array(4); // creates an array with 4 empty spaces
console.log(myArray1.length); // 4
let myArray2 = Array(4); // similar to the previous one, just without the keyword new
console.log(myArray2.length); // 4
let myArray3 = [];
myArray3.length = 4 // this one is a bit different, we assign the value to the property length
console.log(myArray3.length); // 4
Up until now, there was no difference between these three ways of creating an array, besides variations in syntax.
However, if you wish to create an array with a single element that is a Number
, you'll have to use square brackets and define the concrete elements, rather than the size of an array.
This is because if you pass a number to an Array
constructor, you will create an empty array and allocate that many spaces.
// New array with 10 spaces
let myArray1 = new Array(10)
// New array with a single element
let myArray3 = [10]
Adding Elements to an Array
We've seen how to create an Array
, be it empty or non-empty. Now let's see how to add new elements to it. Since we are working with indexed collections, we will be operating with indexes.
As we already had created an Array
of 4 empty elements, let's work with that. To add an element, all we have to do is access the element through its index and assign a value to it:
let myArray1 = new Array(4)
myArray1[0] = "one"
myArray1[1] = "two"
myArray1[2] = "three"
myArray1[3] = "four"
console.log(myArray)
This will be the output:
['one', 'two', 'three', 'four']
Even though we allocated 4 spaces for elements when creating an array, in JavaScript, Array
's are made dynamically, which means you can shrink or expand them at any time.
This means that we can add more elements to our Array
, even though we "bordered" it with 4 spaces:
myArray1[4] = "five"
myArray1[5] = "six"
console.log(myArray) // Output: ['one', 'two', 'three', 'four', 'five', 'six']
We can easily iterate through an array using a for
loop or a forEach
loop:
console.log('Traditional for loop:')
for (let i = 0; i < myArray1.length ; i++) {
console.log(myArray1[i]);
}
console.log('Functional forEach loop:')
myArray1.forEach( function (element){ console.log(element);});
This will output:
Traditional for loop:
one
two
three
four
five
six
Functional forEach loop:
one
two
three
four
five
six
Array Methods
Now that we have gotten the hang of things, let's experiment with built-in Array
methods in JavaScript. You've already seen one in the previous example - a .forEach()
loop being called on myArray1
.
Let's go over the most commonly used ones:
push()
- adds an element at the end of an array
let myArray = [1,2,3];
myArray.push(4);
console.log(myArray); // outputs [1, 2, 3, 4]
pop()
- removes the last element of an array
let myArray = [1,2,3,4];
myArray.pop();
console.log(myArray); // outputs [1, 2, 3]
concat()
- joins arrays (two or more) in a single array
// Concatenate 2 arrays
let myArray1 = [1,2,3]
let myArray2 = [4,5,6];
let finalArray1 = myArray1.concat(myArray2);
console.log(finalArray1); // [1,2,3,4,5,6]
// Concatenate 3 arrays
let myArray3 = [7,8,9];
let finalArray2 = myArray1.concat(myArray2, myArray3);
console.log(finalArray2); // [1,2,3,4,5,6,7,8,9]
join(delimiter)
- joins all of the elements into a string, delimited with adelimiter
let myArray = ["Earth", "Wind", "Fire"];
let arrayString = myArray.join(",");
console.log(arrayString); // outputs Earth, Wind, Fire
// Bonus example
console.log(arrayString + "- September"); // outputs Earth, Wind, Fire - September
reverse()
- exactly that, reverses the order of elements in the array
let myArray = [1,2,3];
let reversed = myArray.reverse();
console.log(reversed); // [3,2,1]
slice(start, end)
- copies a part of an array starting from indexstart
up to indexend-1
let myArray = [1,2,3,4,5,6];
myArray = myArray.slice(3, 5);
console.log(myArray); // [4,5]
TypedArray Object
Array
objects are perfect for working with any data type in JavaScript, since it can store different types of elements in one array and has powerful methods to manipulate those elements.
However, when there is a need to work with raw binary data - that's when TypedArray
objects come into play. Raw data is processed when manipulating audio and video for example.
Architecture of a TypedArray Object
JavaScript typed arrays are divided into buffers and views. A buffer is an object only storing a chunk of data, with no methods to access or manipulate that data. In order to achieve that, you must use a view - which provides a context, a data type that turns data into a TypedArray
.
A buffer is implemented through an ArrayBuffer
object. It is used to represent a fixed-length binary data buffer. To represent this buffer, we have to create a view - DataView
- which represents that buffer in a chosen format. There are various types of views, representing the most common numeric types:
Int8Array
- value range [-128, 127]UInt8Array
- value range [0, 255], u stands for unsignedInt16Array
- value range [-32768, 32767]UInt16Array
- value range [0, 65535]Float32Array
- value range [1.2E-38, 3.4E38]
Creating a TypedArray
When creating a TypedArray
object of a certain type, we achieve what we previously talked about - creating a buffer and a view. There is no explicit constructor for the TypedArray
object - there is no new TypedArray()
syntax - we directly instantiate the type of array we need:
let tArray = new Int8Array(8);
Here, we've created a buffer and a view for an Int8Array
with the size of 8 bytes. The assignment of values to elements is the same as for Array
object:
tArray[0] = 10;
console.log(tArray);
This will output:
Int8Array [ 10, 0, 0, 0, 0, 0, 0, 0 ]
Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!
This way, we can fill the TypedArray
with values typically present, but not limited to, when processing audio or video - but that's a topic for a whole new article.
Keyed Collections
A Keyed Collection is a collection of data represented in the key-value notation. Elements' values are accessed and manipulated through their respective keys.
In JavaScript, there are two types of keyed collections:
Map
andSet
.
Both Map
s and Set
s in JavaScript can have a single value attributed to a single key, though you could hack it by attributing a List
as a value, containing multiple elements. It's still worth noting that the List
itself is the value - not its constituent elements.
Additionally, keys have to be unique.
The Map Object
A Map
object in JavaScript is a standard map containing key-value pairs. To create a new Map
object, we simply call the constructor:
let myMap = new Map();
Adding an Element to a Map
An empty map won't do us much good. Let's add some elements to it via the set()
method, which accepts a key_name
which has to be a String, and a value
which can be of any type:
myMap.set("one", 1);
myMap.set("two", 2);
myMap.set("three", "three");
console.log(myMap);
Maps are also heterogeneous, so you don't have to have the same value type for all keys:
Map { 'one' => 1, 'two' => 2, 'three' => 'three' }
Accessing Elements of a Map
To access elements of a Map, we simply get()
them, passing in the the key_name
as these are the unique identifiers in the Map:
console.log(myMap.get("two")); // Output: 2
Since this collection is not index based, we cannot access some value using square brackets: myMap["two"]
will return an undefined
value.
However, if we call the get(key_name)
method on the non-existing key, the return value will be undefined
too.
Map Methods
The main two methods you'll be using with maps are get()
and set()
, but you'll also want to iterate over them. The Map
class also has a forEach()
that can easily be used to iterate and perform operations on all the entries. We'll be covering it in a moment.
Other than forEach()
, here are the most commonly used methods on Maps:
-
set(key_name, value)
- adds a key-value pair to theMap
. -
get(key_name)
- returns the value assigned to the passed key, if there is no such key - returnsundefined
. -
has(key_name)
- returnstrue
orfalse
depending on whether aMap
has a keykey_name
or not:
console.log(myMap.has("two")); // true
console.log(myMap.has("five")) // false
delete(key_name)
- deletes both the key and the value according to the passedkey_name
, if a non-existing key is passed - nothing happens:
myMap.delete("two")console.log(myMap);
// Output: Map { 'one' => 1, 'three' => 'three' }
myMap.delete("five")console.log(myMap);
// Output: Map { 'one' => 1, 'three' => 'three' }
clear()
- deletes every key-value pair from theMap
object:
myMap.clear();
console.log(myMap);
// Output: Map {}
There is one main property of the Map
- it's size
property. It contains a number value representing the size of a Map
object:
let myMap = new Map();
myMap.set("one", 1);
myMap.set("two", 2);
console.log(myMap.size);
// Output: 2
Iterating through a Map
Iterating through a Map
object in JavaScript is a bit Python-esque. We can use the for..of
syntax to achieve this:
for (let [k, v] of myMap) {
console.log(k + " written in number is " + v)
}
For each entry, with the key-value pair ([k, v]
) of myMap
, do ...
:
one written in number is 1
two written in number is 2
Or, we can utilize the more functional forEach()
method:
myMap.forEach(function(value) { console.log(value);});
Which results in:
1
2
three
Or, you could retrieve both the value
and key
:
myMap.forEach(function(value, key) { console.log(value, key);});
Which results in:
1 one
2 two
three three
Map Over Object
Since an Object
in JavaScript also follows the key-value notation, it may be difficult to decide which to use and when to use it.
There are a few tips on usage of these two:
- Maps should be used when keys are unknown until runtime or when all keys are the same type and all values are the same type.
- Objects should be used when there is logic that operates on individual elements, rather than on a collection of elements.
The WeakMap object
A WeakMap
object in JavaScript is a collection of key-value pairs, where keys are objects only and values can be of various types. The name weak comes from the activity where these objects are a target of garbage collection - which means that if there are no references to it, it will be removed.
The API for WeakMap
is the same as Map
's API, without any iteration methods as WeakMap
s are not iterable:
let myMap = new WeakMap();
let athlete = class Athlete{}
myMap.set(athlete, 1);
console.log(myMap.get(athlete))
This results in:
1
The Set Object
A Set
object in JavaScript is a collection of values only. These values are unique, which means there are no duplicates allowed and trying to add a duplicate element simply won't add anything.
We can also test this since printing sets prints their elements in insertion order, and adding a duplicate element at the start and end will only result in the first one being present.
Creating a Set
is as simple as calling its constructor:
let mySet = new Set();
Adding an Element to a Set
To add a new element to a set, we use the add(value)
method.
Sets can contain arbitrary values. Let's try adding some elements and deliberately add duplicates, to see how a Set
behaves:
mySet.add(1);
mySet.add("one");
mySet.add("one");
mySet.add("two");
mySet.add(1);
console.log(mySet);
Sets maintain the order of insertion, so we can easily test whether the new 1
overrides the old 1
or if it's addition is simply skipped:
Set { 1, 'one', 'two' }
The Set
recognizes the same value elements and keeps only one copy of each. Sets are great for filtering out duplicate values - you can put in a bunch of values that are supposed to be unique, and they'll be filtered.
Though, if you don't need a Set
at the end, it's better to filter a more fitting collection instead.
Set Methods
Set methods are pretty similar to Map methods, and you can easily add and remove values as well as check if some belong to the set, or clear it:
add(value)
- adds a new value to theSet
objectdelete(value)
- deletes the passedvalue
from theSet
objecthas(value)
- returnstrue
orfalse
depending on whether thevalue
is in theSet
objectclear()
- deletes all the values from theSet
object
let mySet = new Set()
// Add values
mySet.add(1);
mySet.add("two");
// Delete a value
mySet.delete("two")
// Check if the deleted value is present
console.log(mySet.has("two")) // false
// Clear all values
mySet.clear()
// Check if first value is present
console.log(mySet.has(1)) // false
WeakSet object
A WeakSet
object is a collection of objects. Same as Set
's values, WeakSet
's objects have to be unique. This refers to the objects in memory, not their fields or values.
There are some key differences between a Set
and a WeakSet
:
WeakSet
is a collection of objects, whereas aSet
is a collection of values of any type.- Same as
WeakMap
, if there is no reference to theWeakSet
object - it is deleted.
HTML DOM Collections
This type of collection is front-end web development related.
When working on a web page, we can access all the elements on the page thanks to the DOM tree. So, when accessing multiple elements at once, they are being returned as an HTMLCollection - an array-like collection of HTML elements.
If we have a web page containing multiple <p>
tags, we can retrieve them with document.getElementsByTagName("p")
- which returns a collection of all the <p>
elements on the page:
let myHTMLCollection = document.getElementsByTagName("p");
console.log(myHTMLCollection[1]);
We can now recognize that a HTMLCollection
is an "indexed" collection, since we're accessing an element from it using an index value. It's not a true indexed JavaScript collection since it's not an array, because it doesn't have the array methods, but index accessing is available.
An HTMLCollection
has the length
property, which returns its size.
Conclusion
Depending on the data you're working with, you'll be deciding whether to use Indexed Collections or Keyed Collections. If working on a web page, you will probably encounter HTMLCollections.
As a quick recap:
- Indexed collections:
- Elements are based on index values - in JavaScript starting from 0.
Array
object andTypedArray
object.
- Keyed collections:
- Elements are based on key-value pairs (JSON-like).
Map
object andSet
object.
- HTML DOM collections:
- Elements are HTML elements, based on index values, again starting from 0.