Getting Started with Collections in JavaScript

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 Arrays and TypedArrays.

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 be 4.

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 a delimiter
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 index start up to index end-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 unsigned
  • Int16Array - 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 ]
Free eBook: Git Essentials

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 and Set.

Both Maps and Sets 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 the Map.

  • get(key_name) - returns the value assigned to the passed key, if there is no such key - returns undefined.

  • has(key_name) - returns true or false depending on whether a Map has a key key_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 passed key_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 the Map 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 WeakMaps 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 the Set object
  • delete(value) - deletes the passed value from the Set object
  • has(value) - returns true or false depending on whether the value is in the Set object
  • clear() - deletes all the values from the Set 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 a Set is a collection of values of any type.
  • Same as WeakMap, if there is no reference to the WeakSet 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 and TypedArray object.
  • Keyed collections:
    • Elements are based on key-value pairs (JSON-like).
    • Map object and Set object.
  • HTML DOM collections:
    • Elements are HTML elements, based on index values, again starting from 0.
Last Updated: October 16th, 2023
Was this article helpful?

© 2013-2024 Stack Abuse. All rights reserved.

AboutDisclosurePrivacyTerms