Uncategorised

When is an Array not an Array?

Short answer: When it’s a JavaScript
arguments
object.

Long answer:

In a JavaScript package I’m writing, I have a function that returns an object.
There’s a bug in it where in certain situations it returns the JavaScript value
true.

I have a test framework set up with Mocha using the Chai assertion library. With
it, I can do

1
expect(result).to.eql(expected);

This means that the result I get back from my function should deeply equal the
expected value. “Deeply equal” means that the objects being compared are
identical — they’re the same type of object and store the same information.

In JavaScript, one would not expect an
Array to be deeply equal to an Object
because they’re different types of objects.

But in the Chai assertion library, an empty Array is deeply equal to an empty Object. This assertion passes when it shouldn’t:

1
expect({}).to.eql([]);

Similarly, but even more confusingly, both an empty Array and an empty Object
are considered to be deeply equal to the JavaScript true value:

1
2
expect(true).to.eql({});
expect(true).to.eql([]);

Both of these assertions pass.

When I was writing a test to ensure that my function wasn’t returning true,
but instead should be returning {}, I was expecting the assertion to fail. It
didn’t.

Since Chai is open source, I forked it, patched it, and submitted a pull request
to get the fix into the main Chai repository. It makes sure that when deep
equality is done, the types of both objects have to be the same.

This was over a month ago, and my pull request hasn’t been accepted.

Why? Because of
arguments.

See, JavaScript has a builtin arguments object, which is “Array-like” in that
it’s an ordered list of values. When you call a function, it’s the list of
arguments that you passed to that function:

1
2
3
4
5
function dumpArgs() {
    for (var i = 0; i < arguments.length; i++) {
        console.log(i + " : " + arguments[i]);
    }
}

This object is kind of like an Array, but the only Array method you can call on it is
length. It’s not an Array object, it’s an Arguments object.

I’m of the opinion that “deeply equals” means that if the objects are different,
they’re not deeply equal. The string "4" might look like it contains the same
value as the integer 4, but you wouldn’t expect them to be deeply equal, because
they’re different types of objects.

My patch hasn’t been pulled because the guy doing the pulling disagrees, which
you can see at the pull request comments. But
only for non-primitive types. “4” is still not deeply equal to 4, but if
arguments is [1,2,3] then that’s equal to the Array [1,2,3].

The Mozilla Developer Network docs say

The arguments object is not an Array. It is similar to an Array, but does not
have any Array properties except length. For example, it does not have the pop
method. However it can be converted to a real Array.

I could change my patch so that if the arguments object is one of the values
being tested, it could be converted to a real Array before doing the check, but
that goes against the concept of deep equality. Two objects of different types
shouldn’t be deeply equal.

So if you’re using the Chai assertion library, and you discover that an empty
Array is equal to true, you’re getting that result because one of the Chai
developers thinks that deeply equals is only applicable some of the time,
because it’s “subtle”.

Leave a Reply

Your email address will not be published. Required fields are marked *