I propose something new for object comparison called Symbol.equal.
The == operator should call the Symbol.equal method if the left hand side is an object or only the right hand side is an object.
example:
class Box{
#value=0;
constructor(value){
this.#value=value;
}
get value(){
return this.#value;
}
[Symbol.equals](other){
return other instanceof Box && other.value == this.value;
}
}
assert(new Box(0) == new Box(0))
Some previous related discussions:
opened 04:22AM - 02 Mar 21 UTC
I feel this is ripe for a symbol that allows extending and modifying the equalit… y check. Library precedent is rather lacking (unless you consider [Fantasy Land's Setoid spec](https://github.com/fantasyland/fantasy-land#setoid) and its implementers as precedent), but there is a common `.equals` idiom that's emerged that I've noticed.
For language precedent, it's pretty strong. Here's a few right off the top of my head:
- Java: `public boolean equals(Object other)`
- C++: `boolean operator==(T other)`
- Rust: `std::cmp::Eq<T>`, used by its `==` operator
- Haskell: [`Data.Eq`](https://hackage.haskell.org/package/base-4.12.0.0/docs/Data-Eq.html)
For naming, I propose `Symbol.equals`. This goes along with the idiom most are already using. And for standard library containers:
- Error objects: identical name and message and deep equal own expando properties. Stack should not be considered for the purposes of this.
- RegExp objects: identical pattern and equivalent flags (`/gi` and `/ig` are considered equivalent).
- Arrays: identical length and deep-equal entries, but ignore expando properties
- Maps: size is identical, each key of `this` is deep-equal with a key in `other` and their corresponding entries are also deep-equal
- The idea is that these should be compared deeply in the same way objects are, just instead of string equality, it's deep object equality.
- Algorithm note: [I've tried to distill that into something sufficiently fast here](https://github.com/isiahmeadows/clean-assert/blob/master/lib/match.js#L246-L312). Either you'll need to encode similar in the spec or leave it implementation-defined for reasonable performance.
- Sets: size is identical and every key in `this` is deep-equal with a key in `other`
- The idea is that these should be compared deeply in the same way objects are, just instead of string equality, it's deep object equality.
- Algorithm note: [I've tried to distill that into something sufficiently fast here](https://github.com/isiahmeadows/clean-assert/blob/master/lib/match.js#L314-L383). Either you'll need to encode similar in the spec or leave it implementation-defined for reasonable performance.
- Array buffers, shared array buffers, and array buffer views: equal byte data
- Promises: identical status and deep-equal value if settled, reference identity if pending
The default behavior should still of course be the standard algorithm, and it's left implied that types of course also need to be equal.
-----
I could also see HTML wanting to hook into this as well:
- Event: `detail` deep-equal, `eventPhase` and mutable properties ignored, all other properties identical
- Node: children deep-equal
- Element: attributes and children deep-equal
- CharacterData: data equal
- Range: equivalent contents
- Request: equivalent target/headers/etc.
- And so on
1 Like
ljharb
September 13, 2026, 10:44pm
3
Why? What's the problem you're trying to solve?
Other libraries might have different equality method names which makes it inconsistent and you might end up trying to call undefined or being too strict
btw: the other proposals solve this issue too and I don't care if you do mine or not
ljharb
September 13, 2026, 11:06pm
5
Considering that == is aggressively discouraged in every form except perhaps == null, why would it be a good idea to begin encouraging its use?
What would be the benefit of a consistent way of comparing arbitrary objects? Two different kinds of objects are only comparable if they know about each other, in which case they'd need to both participate in the API, which they could do just by agreeing on a method name.
code verbosity for general purpose library usage
ljharb
September 14, 2026, 3:09am
7
x == y invisibly calling a protocol seems extremely less readable than x.equals(y) or the equivalent, to me.
well, at least its less verbose
valueOf enters the conversation:
({ valueOf: () => 123 }) == 123
I think anything more elegant than that would be great and because indeed == is rare out there and old code won't be affected, I believe this idea is not that bad, just the name (maybe?) as I'd go for Symbol.eqeq instead or Symbol.equality as described on MDN Equality (==) - JavaScript | MDN
edit I also agree x.equals(y) doesn't need to check for Symbol.equality in obj to branch == from === but still, == via symbol would be cool but I also think for null or undefined comparison that protocol should not be called or it will break working code with good intents.
1 Like
On a second though, wouldn't Object.equal(a, b) help solving the operator hidden gotcha and help comparing via:
returns true if Object.is(a, b)
returns true if a == b
returns true if both objects have a Symbol.equal attached or inherited and a[Symbol.equal](b) and b[Symbol.equal](a)) are both true
returns false otherwise
... or maybe not?