My guess is that because when javascript was designed, they thought element wise comparison was a rarely used feature so it was never put into the language.
This feature is fairly rare in popular languages; Java doesn't support it, C# doesn't support it, C++ stl types don't support it.
element wise comparison is fairly expensive and complicated compared to reference comparison.
In a perfect world, everything can be compared by reference, so that every two objects that have the same state would have the same reference, allowing us to really cheaply check for equality, by just comparing their internal virtual addresses with a simple number comparison.
Unfortunately, we don't live in a perfect world, and the above is only possible for strings with a string pool, along with few other relatively memory expensive caching options.
Some languages like Prolog and Haskell allow comparison by value; eg
myProgram :-
Object1 = [1, "something", true, [1,[[], []], true,[false]]],
Object2 = [1, "something", false, [1,[[], []], true,[false]]],
Object3 = [1, "something", true, [1,[[], []], true,[false]]],
Object4 = [1, "something", false, [1,[[], []], true,[false]]],
(Object1 = Object2
-> write("first test passed.")
; write("first test failed\n")),
(Object1 = Object3
-> write("second test passed!\n")
; write("second test failed!\n")),
(Object2 = Object4
-> write("third test passed!\n")
; write("third test failed!")).
You can implement your own deep-comparator in any language by using a map from constructor to a comparator for that constructor. Since JavaScript doesn't have maps from anything but string to object, and javascript clients do not have access to the internal unique identifier of objects, we need to use a table with constructor, comparator pairs, something like below.
class MyList {
constructor(a, b) {
this.head_ = a;
this.tail_ = b;
}
getHead() {
return this.head_;
}
getTail() {
return this.tail_;
}
setHead(x) {
this.head_ = x;
}
setTail(x) {
this.tail_ = x;
}
equals(other) {
if (typeof other !== 'object') {
console.log(other, 'EEP');
return false;
}
if (!(other instanceof MyList)) {
console.log(other, 'EEP');
return false;
}
var h = this.head_;
var ho = other.getHead();
var cmp1 = comparatorof(h);
if (!cmp1(h, ho)) {
return false;
}
var t = this.tail_;
var to = other.getTail();
var cmp2 = comparatorof(t);
if (!cmp2(t, to)) {
return false;
}
return true;
}
}
var object1 = {
0: "one",
1: "two",
2: ["one", "two", []],
something: {
1: [false, true]
}
};
function strictComparator(a, b) {
return a === b;
}
// given any object, tries finding a function for comparing
// that object to objects of the same kind. Kind being
// used loosely, since some objects like null resist being categorized,
// so these objects need special alteration of the comparatorof itself.
function comparatorof(x) {
if (x === null || x === undefined) {
return strictComparator;
}
x = Object(x); // promote primitives to objects
var ctor = x.constructor;
var c2ct = ctorToComparatorTable;
var n = c2ct.length;
for (var i = 0; i < n; ++i) {
var record = c2ct[i];
var keyCtor = record[0];
if (keyCtor === ctor) {
return record[1];
}
}
throw new TypeError('no comparator exists for ' + x);
}
var ctorToComparatorTable = [
[String, function(a, b) {
return a == b;
}],
[Object, function(a, b) {
for (var key in a) {
var avalue = a[key];
var bvalue = b[key];
var cmp = comparatorof(avalue);
if (!cmp(avalue, bvalue)) {
return false;
}
}
return true;
}],
[Number, function(a, b) {
return a == b;
}],
[Boolean, function(a, b) {
return a == b;
}],
[Array, function(as, bs) {
if (typeof bs !== 'object') {
return false;
}
var nAs = as.length;
var nBs = bs.length;
if (nAs !== nBs) {
return false;
}
for (var i = 0; i < nAs; ++i) {
var a = as[i];
var b = bs[i];
var cmp = comparatorof(a);
if (!cmp(a, b)) {
return false;
}
}
return true;
}],
[MyList, function(a, b) {
return a.equals(b);
}]
];
// true
console.log(comparatorof([])([new MyList(1, new MyList(2, 3))], [new MyList(1, new MyList(2, 3))]));
// true
console.log(comparatorof([])([{}, new MyList(1, new MyList(2, 3))], [{}, new MyList(1, new MyList(2, 3))]));
// false
console.log(comparatorof([])([{}, new MyList(1, new MyList(2, 3))], [{}, new MyList(1, new MyList(3, 3))]));
// true
console.log(comparatorof({})({
1: 'one',
one: '1',
x: new MyList(1, {})
}, {
1: 'one',
one: '1',
x: new MyList(1, {})
}));
// true
console.log(comparatorof(2)(
3,
3
));
//true
console.log(comparatorof(true)(
true,
true
));
//false
console.log(comparatorof([])(
[1, 2, new MyList(1, 2)], [1, 2, new MyList(4, 9)]
));
// true
console.log(comparatorof([])(
[1, 2, new MyList(1, 2), null], [1, 2, new MyList(1, 2), null]
));
// false
console.log(comparatorof(null)(
null,
undefined
));
// true
console.log(comparatorof(undefined)(
undefined,
undefined
));
// true
console.log(comparatorof(null)(
null,
null
));
One big issue is that ES6 is already full of features that are incompatible with JScript and Nashorn JJS as well as ActionScript that the language may as well be rebranded as a whole new language every few months, considering that's effectively what you get when you add new features to a language that break compatibility with old versions of it that cannot be replicated without an extra eval layer.
This problem goes back a long time where on one side you have a minimal language like Lisp which is "easier"(still can't overload ' operator) to introduce "first class" features without breaking backward compatibility, and then you have languages like Perl which cannot be extended with new first-class keywords without an extra eval layer. JavaScript chose the second approach and generally bypasses the consequences by using helper objects like Math, Object to introduce new features, but anytime it wants to add "first class constructs" it ends up breaking backward compatibility.
As a proof of concept, In Lisp you can overload == operator, whereas in JavaScript you can't, and in Perl you can but through a mechanism that reserves keywords.