5

I'm trying to implement custom equality and relational operations using equals and compareTo methods. However, I get wrong results. Please help me figure our where I'm doing wrong.

var Person = function () {
this.age = null;
this.name = null;
};

Person.prototype.equals = function (that) {
    if (this.age === that.age) return true;
    else return false;
    };

Person.prototype.compareTo = function (that) {
    return this.age - that.age;
    };

var p1 = new Person();
var p2 = new Person();

p1.age = 10;
p2.age = 20;

document.writeln(p1 < p2); // this should be true but evaluates to false.

2 Answers 2

7

JavaScript doesn't have operator overloading, and > doesn't call any methods on your objects, so p1 < p2 won't use your equals or compareTo.

To do that comparison, you'd use:

document.writeln(p1.compareTo(p2) < 0);

That said, you can implement valueOf and have it return age. valueOf does get called as part of > between objects:

function Person(age) {
    this.age = age;
    this.name = null;
}

Object.defineProperty(Person.prototype, "valueOf", {
    value: function () {
        return this.age;
    },
    writable: true,
    configurable: true,
});

Or in code written since ES2015 became universally-supported in non-obsolete browsers:

class Person {
    constructor(age) {
        this.age = age;
        this.name = null;
    }

    valueOf() {
        return this.age;
    }
}

Live Example:

class Person {
    constructor(age) {
        this.age = age;
        this.name = null;
    }

    valueOf() {
        return this.age;
    }
}

const p1 = new Person(10);
const p2 = new Person(20);

console.log("p1 < p2: " + (p1 < p2)); // true
console.log("p1 > p2: " + (p1 > p2)); // false

Beware though that p1 == p2 and p1 === p2 will always be false, even if age is the same:

class Person {
    constructor(age) {
        this.age = age;
        this.name = null;
    }

    valueOf() {
        return this.age;
    }
}

const p1 = new Person(10); // Same age
const p2 = new Person(10); // Same age

console.log("p1 == p2:  " + (p1 == p2));  // false!
console.log("p1 === p2: " + (p1 === p2)); // false!

valueOf is invoked when the JavaScript engine needs to convert the object to a primitive; it doesn't do that for == or === unless it has to because the other operand is a primitive. So you'd have to define equals and explicitly call it (p1.equals(p2)), or force the comparison to be between the primitives (+p1 === +p2), which is error-prone.

Sign up to request clarification or add additional context in comments.

Comments

0

Revisiting this question and working with node v18.13.0, I found that relational operators between objects invoke the valueOf method, and if not available, then invoke the toString method. A String representation can be built to allow comparison of most object types (assuming you can build the String representation as needed).

The following example illustrates all these cases by commenting in/out the two versions of toString or the valueOf method:

import assert from 'node:assert/strict';

export class ComparableDate {

    // private instance variables
    #year
    #month
    #date

    constructor(year, month, date) {
        this.#year = year;
        this.#month = month;
        this.#date = date;
    }

    // Relational operators > < invoke the toString method.
    // Observe that this fails for comparing dates in general
    // toString() {
    //     console.log("toString invoked");
    //     return ""+this.#year+"-"+this.#month+"-"+this.#date;
    // }

    // Zero-left padding, makes the string comparison work as expected
    toString() {
        console.log("toString invoked");
        return (""+this.#year).padStart(4,'0')+"-"+(""+this.#month).padStart(2,'0')+"-"+(""+this.#date).padStart(2,'0');
    }


    // valueOf called over toString if present
    valueOf() {
        console.log("valueOf invoked");
        return this.#date + this.#month*31 + this.#year*366;
    }

}


// Test dates
let d1 = new ComparableDate(2023,2,15);
let d2 = new ComparableDate(2023,10,31);

// Test cases
assert(d2>d1, `${d2} must be greater than ${d1}`);
assert(d1<d2, `${d1} must be less than ${d2}`);

2 Comments

Btw, ComparableDate is not a good example or at least has a weird name, since a basic normal Date already is comparable.
Thanks to Bergi for pointing out my typo. Fixed accordingly.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.