TypeScript : Object Equality Comparison (Object Equals Object)

假如想象 提交于 2021-01-27 19:14:45

问题


I have found this (personally) convenient answer that fits my needs: https://stackoverflow.com/a/6713782/2678218

But since I am using TypeScript, I can have something like this with Generics:

private equals<T>(x: T, y: T) {
    if (x === y) {
        return true; // if both x and y are null or undefined and exactly the same
    } else if (!(x instanceof Object) || !(y instanceof Object)) {
        return false; // if they are not strictly equal, they both need to be Objects
    } else if (x.constructor !== y.constructor) {
        // they must have the exact same prototype chain, the closest we can do is
        // test their constructor.
        return false;
    } else {
        for (const p in x) {
            if (!x.hasOwnProperty(p)) {
                continue; // other properties were tested using x.constructor === y.constructor
            }
            if (!y.hasOwnProperty(p)) {
                return false; // allows to compare x[ p ] and y[ p ] when set to undefined
            }
            if (x[p] === y[p]) {
                continue; // if they have the same strict value or identity then they are equal
            }
            if (typeof (x[p]) !== 'object') {
                return false; // Numbers, Strings, Functions, Booleans must be strictly equal
            }
            if (!this.equals(x[p], y[p])) {
                return false;
            }
        }
        for (const p in y) {
            if (y.hasOwnProperty(p) && !x.hasOwnProperty(p)) {
                return false;
            }
        }
        return true;
    }
}

I'm sure that since we're using <T> here, we can refactor the code. One thing's for sure is by removing some if statements that are not needed anymore. But i am not confident of which to take away and also not sure if there will be a more optimal code for this. So I'll leave the question here and have everyone vote for the best answer.

In this context, what I actually mean by equality of two objects is when two objects of the same type have equal values of every property.


回答1:


@Lostfields mentions that someone could pass any for the type T, but that's not a big concern, since using any is telling the compiler not to type check anything. If this leads to undesirable behavior at runtime, I'd place the responsibility for dealing with this on the code that passed in any, instead of the code inside equals(). One use of the type system is indeed to eliminate some unnecessary runtime checks, with the caveat that you still need to do sanitize any data being passed in from untrusted sources. Are you building a library that will be used by developers who might not even be using TypeScript? Then do not relax any runtime checks. Are you building code to be used internally or by other TypeScript developers who depend on your typings? Then by all means eliminate unnecessary checks.


That being said, I don't you can remove many of the checks in that implementation. Each of the conditionals checked might be true or false at runtime, even knowing that TypeScript has decided that x and y are of the same type. (In what follows I will be treating equals() as a standalone function instead of a method. Add this or whatever the object name is as you see fit)

Let's examine each one:

  • (x === y): True for equals(x,x), false for equals(x, Object.assign({},x)). This one has to stay.

  • ((!(x instanceof Object) || !(y instanceof Object)): This one you might decide to replace with just (!(x instanceof Object)), since in practice a type in TypeScript is either an Object or it is not, and so x instanceof Object should be the same as y instanceof Object. Still, someone might do equals(0, new Number(0)) which passes the type check in TypeScript. It's up to you if you care about guarding against that.

  • (x.constructor !== y.constructor): False for two structurally identical classes, such as class A{}; class B{}; equals(new A(), new B()). If you're not worried about structurally identical but distinct classes, you can eliminate this check.

  • (!x.hasOwnProperty(p)): This check has nothing to do with TypeScript; it has to stay.

For the next case, consider

interface Foo { foo?: string, bar: number, baz?: boolean };
const x: Foo = { foo: 'hello', bar: 12 };
const y: Foo = { bar: 12, baz: false };
equals(x, y);
  • (!y.hasOwnProperty(p)) and (y.hasOwnProperty(p) && !x.hasOwnProperty(p)): These could be true or false for instances of Foo, or any type with optional properties. Or any subtype of a type without optional properties, since extra properties are allowed in TypeScript.

  • (x[p] === y[p]), (typeof (x[p]) !== 'object'), (!equals(x[p], y[p])): These can be true or false for the same reasons as above, which can be seen by passing in a type with a single property of the types mentioned above. That is, if equals(x,y) needs a runtime check, then equals({foo: x},{foo: y}) will need the same runtime check.


So, it's up to you. Feel free to leave the implementation alone. Extra runtime checks don't hurt anything, after all. Feel free to remove some checks if you don't think you'll need them; again, you're the only one who knows how crazy the users of equals() will be. For example, what would you do about this:

interface Ouroboros {
    prop: Ouroboros;
}
let x = {} as Ouroboros;
x.prop = x;

let y = {} as Ouroboros;
y.prop = y;

console.log(equals(x,y))

Do you care about circular references? If not, don't worry. If so, then you need to harden your equality check to deal with it.


Hope that helps; good luck!



来源:https://stackoverflow.com/questions/46705216/typescript-object-equality-comparison-object-equals-object

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!