DEV Community

loading...
Cover image for Why Is {} > [] ?

Why Is {} > [] ?

amyshackles profile image Amy Shackles Updated on ・4 min read

TLDR version

Relational Comparisons

In JavaScript, the result of a relational comparison is determined by the Abstract Relational Comparison algorithm. The algorithm converts both sides of a comparison to primitive values and then returns the result of the comparison between those two primitive values.

ToPrimitive¹

The Abstract Relational Comparison algorithm calls ToPrimitive twice, once for each operand, passing 'number' as the second argument. This tells the ToPrimitive function that if there are multiple primitive types the operand could convert to, and number is one of them, it should convert the value to a number instead of a different type.

OrdinaryToPrimitive²

If the value being passed to ToPrimitive is an object, it then calls OrdinaryToPrimitive with the same two arguments, value and type hint. OrdinaryToPrimitive generates a list of methods to call to convert the value to a primitive.

If "string" is passed in as the type hint, the method order becomes toString followed by valueOf. In this case, since "number" was passed, the method order is valueOf followed by toString. It's important to note that while all values that get to this point are objects, not every value will use the valueOf and toString methods on the Object prototype.

If the first method results in a value of type "object", the result of calling the second method returned. If the first method does not return a value of type "object", the result of the first method is returned.

OrdinaryToPrimitive( {} )

In the case of {}, the only prototype being looked at is Object, so it first tries calling valueOf on the object using Object.prototype.value()³, but that returns {}. Since typeof {} === "object", it moves to the next method. It then calls Object.prototype.toString()
; If Object.prototype.toString() is called on a value that is an object, the builtinTag is set to "Object". The return value of Object.prototype.toString() is the concatenation of "[object ", tag, "]". The return value for passing in an empty object, then, is "[object Object]"

OrdinaryToPrimitive( [] )

In the case of [], there are two prototypes to take into consideration -- Array and Object. If a method exists on the Array prototype, that is the method called. If, however, it does not exist on the Array prototype, it looks for the method on the Object prototype. The Array prototype does not contain a method for valueOf, so it first tries calling Object.prototype.valueOf(). That returns [], and since typeof [] === "object", it moves on to the next method.

The Array prototype does have a toString() method, so It then calls Array.prototype.toString()⁵.

Array.prototype.toString() returns the value of the join method on the array. As there are no elements in the array, the return value of Array.prototype.toString() on an empty array is an empty string.

Comparison

Now that both sides are converted to their primitive values, it's time to compare them in relation to each other.

"[object Object]" > ""
Enter fullscreen mode Exit fullscreen mode

A string of any length is going to be greater in value than the value of an empty string.

Follow-up

The way that JavaScript evaluates abstract equality when one operand is of type String/Number/Symbol/BigInt and the other operand is an object is to call the same ToPrimitive on the object and then check equality⁶.

Therefore, we can also sanity check that {} is actually converted to "[object Object]" and [] is converted to an empty string by performing abstract equality checks.

console.log({} == "[object Object]") // true
console.log([] == "") // true
Enter fullscreen mode Exit fullscreen mode

Why does {} > [] error in browser?

Shout out to Martijn Imhoff for asking this question.

The way that the specification for JavaScript is written, block statements are evaluated before expressions, so when the interpreter sees curly braces when not in an expression context, it interprets them as a block rather than an object literal. That's why you get an error when you attempt to run those expressions in the browser. The way to force the interpreter to see {} as an object literal instead of as a block is to wrap it in parentheses.

({}) > [] // true; ({}) < [] // false; ({}) == "[object Object]" // true

If you were to open a Node console rather than a browser console, you would see:

{} > [] // true; > {} < [] // false; {} == "[object Object]" // true

This is because Node made a change to evaluate input as expressions before evaluating them as statements. That change can be seen here.

TLDR Version

{} is converted to "[object Object]"

[] is converted to ""

"[object Object]" > ""


References:

¹ ToPrimitive specification

² OrdinaryToPrimitive specification

³ Object.prototype.valueOf() specification

Object.prototype.toString() specification

Array.prototype.toString() specification

Abstract Equality Comparison algorithm

Discussion

pic
Editor guide
Collapse
zashirah profile image
Zach Shirah

Thanks for this. We often joke about the weird quirks of JS but rarely get explanations

Collapse
amyshackles profile image
Amy Shackles Author

I'm honestly kind of tempted to go through the entire infamous Wat video and break down all the different parts of it.

Also, I think you read this before the most recent update that should clear up a few of the lingering questions one might have (such as "But if they're both objects, why does one evaluate to "[object Object]" and the other evaluate to an empty string? What's the heck?!"

Collapse
stereobooster profile image
stereobooster
// object to string
({}) instanceof Object // true
Object.prototype.toString.call({}) // "[object Object]"
([]) instanceof Array // true
Object.prototype.toString.call([]) // "[object Array]"
// "inheritance"
Array instanceof Object // true
Object instanceof Array // false
// array to string
[1,2].join(",") // "1,2"
[1,2].toString() // "1,2"
Enter fullscreen mode Exit fullscreen mode
Collapse
stereobooster profile image
stereobooster

I wish I didn't need to know this... Nice and detailed article

Collapse
amyshackles profile image
Amy Shackles Author

I actually totally neglected to explain why an empty object would evaluate to "[object Object]" while an empty array, while still being an object, would evaluate to an empty string. I've since cut out a lot of the repetition and answered that lingering question. Hope it helps! :)

Collapse
promikecoder2020 profile image
ProMikeCoder2020

Amazing article since There are not a lot of Devs explaining the inner parts of JavaScript. Hope you continue making this awesome content