Fixing the JavaScript `Array` Class
How to drag JavaScript Arrays into the modern programming world without breaking them
JavaScript is an evolving language, with new features recently added to support modern programming concepts like immutable objects, pure functions, and method chaining. This is known as a “good thing”, as these modern concepts make coding easier to write, understand, and debug.
Unfortunately, while JavaScript is evolving, it still needs to support legacy code. As a result, the standard APIs tend to use a jumbled mess of programming styles, with different patterns used within a single API.
The standard JavaScript Array
class has one such messy API. But before delving into its quirks, let me briefly mention some of the “good” modern programming concepts that it ought to be supporting.
Immutable objects
Immutable objects make it easier to write code that doesn’t have undesirable side effects. An immutable object is never modified after it is created. Whenever you want to modify an object, you must create a new one. Example:
const name = 'terry';
const nameCapitalized = capitalize(name);
// name: 'terry', nameCapitalized: 'Terry'
Since name
is immutable, we know that capitalize()
didn’t change it. This is a good thing, as we no longer have to worry that modifying name
will have an effect on some other code that uses that object.
Pure functions
A pure function always returns the same result for the same inputs, and it has no side effects. It simply takes in data and returns data. Here’s a pure function that operates on an array:
const array1 = [1, 2, 3];
const array2 = double(array1);
// array1: [1, 2, 3]
// array2: [2, 4, 6]
In this example, double()
receives an array and returns a new array with each of its element doubled. Since it’s a pure function, it did not change the input array (no side effects 👍). It also returned an array that can be further processed, which supports another useful programming concept:
Method chaining
Method chaining is the handy practice of chaining together multiple object functions (methods) to produce a new result. Method chains look something like this:
xNew = x.methodA().methodB().methodC();
Because the double()
function in the example above returns an array, it can be chained with any array method. Let’s say we want to convert every array element with this formula: x’ = (2 * x)²
. With method chaining, we can do this:
// x' = (2 * x)^2 for each element
const array1 = [1, 2, 3];
const array3 = double(array1).square();
// array3: [4, 16, 36]
double()
returns an array, which is then input to square()
, which makes a copy of that array, squares every element, and returns the array. All of this is done in a single, compact, easy to read and understand line. This is the power of method chaining.
Problems with the standard JavaScript Array class
Array
has been around since the beginning of JavaScript, and it’s used by virtually all programs. So what’s wrong with it?
In a word, plenty. Some of its built-in methods support modern concepts, others don’t. Some methods leave source arrays unchanged (immutable objects 👍), others change arrays in-place (mutable objects 👎). Some methods are pure functions (no side effects 👍), others are un-pure (side effects 👎). In addition, some methods that should return an array don’t (no method chaining 👎).
In addition, the inconsistency between Array
methods means the weary programmer must try to remember the peculiar behavior of each method. And since programmers are human, that means more bugs.
In total, Array
has 35 methods:
- 11 modify an array
- 10 return an array (method chaining 👍)
- 4 don’t return an array when they should (no method chaining 👎)
- 6 don’t modify the source array (immutable objects 👍).
- 4 modify arrays in-place (mutable objects 👎).
If only there were a way to convert these inconsistent and un-modern Array
methods into pure functions that maintain object immutability and return arrays, without breaking legacy code that relies of the peculiarities of those methods.
Well, I think there is.
The Fix: ArrayI
To fix the bad parts of Array
while keeping the good parts, I’ve created a new array class called ArrayI
(I stands for immutable) which inherits all the Array
methods.
But here’s the “good thing”: ArrayI
replaces all the Array
“un-pure” methods with pure ones. Its methods never modify source arrays in place, preserving object immutability. Methods return ArrayI
arrays where appropriate, so method chaining works.
ArrayI vs. Array
Here’s a simple example using the copyWithin()
method, first using Array
:
const myArray = new Array('a', 'b', 'c', 'd', 'e');
const newArray = myArray.copyWithin(0, 3, 4);
// myArray: [ 'd', 'b', 'c', 'd', 'e' ]
// newArray: [ 'd', 'b', 'c', 'd', 'e' ]
Notice that copyWithin()
modified myArray
—not good! Now the same example using ArrayI
:
const myArrayI = new ArrayI('a', 'b', 'c', 'd', 'e');
const newArrayI = myArrayI.copyWithin(0, 3, 4);
// myArrayI: [ 'a', 'b', 'c', 'd', 'e' ]
// newArrayI: [ 'd', 'b', 'c', 'd', 'e' ]
The ArrayI
copyWithin()
method didn’t modify the source array, maintaining the pure function/immutable object practice.
Download ArrayI
ArrayI
is implemented in a module named array-immutable, which may be downloaded from GitHub or npmjs.com.
Or simply download using npm:
$ npm install array-immutable
If you try it out, please let me know what you think of it. Hopefully, it will make your handling of arrays a little easier and more bug free.