Comparing Arrays by Value in JavaScript
I didn't know I didn't know the answer when my friend showed me a simple piece of JavaScript code and asked me what would happen.
1// {1}
2const a = [1, 2]
3const b = [1, 2]
4a == b // what will happen next? 🕶
He said this caused a bug in a project he was working on and it took him hours to debug. Since I knew arrays in JavaScript are objects. And objects are only equal when they refer to the same thing in memory. If you do it as shown in ex. {2}, the result will certainly be true
, because x
and y
indeed refer to the same thing.
1// {2}
2const x = [1, 2]
3const y = x
4x == y // will certainly be true
In ex. {1}, a
and b
are defined differently. And I felt very clever when I correctly said the code on line 🕶 will produce a false
. But —
“If we compare a.valueOf() == b.valueOf()
, the result will surely be true
!”
— And I was wrong. This would still give you a false
!
But my guess was not without a reason.
Take the code below for an example. k1
and k2
are objects. They are not directly comparable. But you can compare their values using valueOf()
method. And they are equal.
1const k1 = new Object('wrapping a string literal with an object')
2const k2 = new Object('wrapping a string literal with an object')
3
4k1 == k2 // false; because you can't compare objects to objects
5k1.valueOf() == k2.valueOf() // true; because the values of k1 and k2 are both strings
It came as surprise to me when it turned out arrays, as objects, do not behave like string objects.
The valueOf()
method of an array will return an array, which is, again, at the same time an object. Now we are back to this: “you can't compare objects to objects”.
1const m1 = new Object([1, 2, 3])
2
3m1 // [ 1, 2, 3 ]
4m1 instanceof Array //true
5m1 instanceof Object // true
6m1.valueOf() instanceof Array //true
7m1.valueOf() instanceof Object // true
Then I wrote a method that checks if two arrays are equal in value. This method checks recursively the equality in value of nested arrays.
1// Definition
2Array.prototype.equalsInValue = function (that) {
3 if (! this && that) {
4 return false
5 } else if (this.length !== that.length) {
6 return false
7 }
8
9 for (let i=0;i<this.length;i++) {
10 if (this[i] instanceof Array && that[i] instanceof Array) {
11 if (!this[i].equalsInValue(that[i])) {
12 return false
13 }
14 continue
15 }
16 if (this[i] !== that[i]) {
17 return false
18 }
19 }
20
21 return true
22}
23
24// Test
25const x = [1, 2, [[3], [4, [5, 6], ['hello', 'world'] ]]]
26const y = [1, 2, [[3], [4, [5, 6], ['hello', 'world'] ]]]
27console.log('comparing x, y: ', x.equalsInValue(y))
28// comparing x, y: true
I ignored the case where objects are nested inside arrays though. Before I began to take into account objects, I found out someone on StackOverflow had already done that many years ago.
On the other hand, comparing lists in Python is rather uninteresting:
1x = [1, 2, [3], [4, [5, 6]]]
2y = [1, 2, [3], [4, [5, 6]]]
3print(x==y) # True
4
5x1 = [1, 2, [3], [4, [5, 6], {'hello': 'world'}]]
6y1 = [1, 2, [3], [4, [5, 6], {'hello': 'world'}]]
7print(x1==y1) # True
8
9class MyObj:
10 def __init__(self, val):
11 self.val = val
12 def valueOf(self):
13 return self.val
14
15j = MyObj([1, 2, 3])
16k = MyObj([1, 2, 3])
17print(j == k) # False
18print(j.valueOf() == k.valueOf()) # True