Header logo.
A study of bugs
HomeArchiveTagsAboutFeed

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}
const a = [1, 2]
const b = [1, 2]
a == 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.

// {2}
const x = [1, 2]
const y = x
x == 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.

const k1 = new Object('wrapping a string literal with an object')
const k2 = new Object('wrapping a string literal with an object')

k1 == k2 // false; because you can't compare objects to objects
k1.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”.

const m1 = new Object([1, 2, 3])

m1 // [ 1, 2, 3 ]
m1 instanceof Array //true
m1 instanceof Object // true
m1.valueOf() instanceof Array //true
m1.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.

// Definition
Array.prototype.equalsInValue =  function (that) {
  if (! this && that) {
    return false
  } else if (this.length !== that.length) {
    return false
  }

  for (let i=0;i<this.length;i++) {
    if (this[i] instanceof Array && that[i] instanceof Array) {
      if (!this[i].equalsInValue(that[i])) {
        return false
      }
      continue
    } 
    if (this[i] !== that[i]) {
      return false
    }
  }

  return true
}

// Test
const x = [1, 2, [[3], [4, [5, 6], ['hello', 'world'] ]]]
const y = [1, 2, [[3], [4, [5, 6], ['hello', 'world'] ]]]
console.log('comparing x, y: ', x.equalsInValue(y))
// 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:

x = [1, 2, [3], [4, [5, 6]]]
y = [1, 2, [3], [4, [5, 6]]]
print(x==y) # True

x1 = [1, 2, [3], [4, [5, 6], {'hello': 'world'}]]
y1 = [1, 2, [3], [4, [5, 6], {'hello': 'world'}]]
print(x1==y1) # True

class MyObj:
    def __init__(self, val):
        self.val = val
    def valueOf(self):
        return self.val

j = MyObj([1, 2, 3])
k = MyObj([1, 2, 3])
print(j == k) # False
print(j.valueOf() == k.valueOf()) # True