严格相等运算符将告诉您两个对象类型是否相等。然而,是否有一种方法来判断两个对象是否相等,就像Java中的哈希码值一样?

堆栈溢出问题JavaScript中有hashCode函数吗?类似于这个问题,但需要一个更学术的答案。上面的场景说明了为什么有必要有一个,我想知道是否有等效的解决方案。


当前回答

这个问题已经有30多个答案了。我将总结并解释它们(用“我父亲”的比喻),并添加我建议的解决方案。

你有4+1类解


1)使用不完整的简单语句

如果你赶时间,那么99%的正确性都可以。

例如,Pratik Bhalodiya建议的JSON.stringify()或JSON。encode,或. tostring(),或其他方法将对象转换为String,然后逐个字符使用===比较两个String。

然而,缺点是在String中没有Object的全局标准唯一表示。例如:{a: 5, b: 8}和{b: 8和a: 5}是相等的。

优点:快,快。 缺点:希望有效!如果环境/浏览器/引擎记住了对象的顺序(例如Chrome/V8),并且键的顺序不同(感谢Eksapsy),它将无法工作。所以,完全不能保证。在大型对象中,性能也不会很好。

我的父亲类比

当我在谈论我的父亲时,“我高大英俊的父亲”和“我高大英俊的父亲”是同一个人!但这两个弦是不一样的。

请注意,在英语语法中,形容词的顺序其实是正确的(标准的方式),它应该是“英俊的高个子男人”,但如果你盲目地认为iOS 8 Safari的Javascript引擎也遵循同样的语法,你就是在拿自己的能力冒险!# WelcomeToJavascriptNonStandards


2)自己DIY编写递归函数

如果你正在学习,这很好。

例子是atmin的解。

最大的缺点是你肯定会错过一些边缘情况。您考虑过对象值中的自引用吗?你考虑过NaN吗?您是否考虑过具有相同ownProperties但不同原型父对象的两个对象?

我只鼓励人们在进行实践并且代码不会投入生产的情况下这样做。这是唯一一种重新发明轮子有正当理由的情况。

优点:学习机会。 缺点:不可靠。需要时间和精力。

我的父亲类比

这就像假设我爸爸的名字是“约翰·史密斯”,他的生日是“1/1/1970”,那么任何一个名字是“约翰·史密斯”,出生在“1/1/1970”的人就是我的父亲。

通常情况下是这样,但如果有两个“约翰·史密斯”在那一天出生呢?如果你认为你会考虑他们的身高,那么这就提高了准确性,但仍然不是一个完美的比较。

2.1你的范围有限DIY比较器

与其疯狂地递归检查所有属性,不如考虑只检查“有限”数量的属性。例如,如果对象是Users,您可以比较它们的emailAddress字段。

它仍然不是一个完美的解决方案,但比解决方案#2的好处是:

它是可预测的,而且不太可能崩溃。 您正在推动平等的“定义”,而不是依赖于对象的原始形式和形状及其原型和嵌套属性。


3)使用功能相同的图书馆版本

如果您需要生产级别的质量,并且您不能更改系统的设计,则很好。

例如_。等于lodash,已经在coolaj86的回答中,或者在Tony Harvey的回答中提到的Angular的回答中,或者在Node的Rafael Xavier的回答中。

优点:其他人都这么做。 缺点:外部依赖,这可能会花费你额外的内存/CPU/安全问题,甚至一点点。此外,仍然可以错过一些边缘情况(例如,是否两个对象具有相同的ownProperties但不同的原型父应该被认为是相同的。)最后,你可能无意中解决了一个潜在的设计问题;只是说!

我的父亲类比

这就像付钱给中介机构,根据他的电话、姓名、地址等找到我的生父。

这会花更多的钱,而且可能比我做背景调查更准确,但不能涵盖边缘情况,比如我父亲是移民/庇护者,他的生日是未知的!


4)在对象中使用标识符

如果你仍然可以改变系统(你正在处理的对象)的设计,并且你希望你的代码能够持久,那就太好了。

它并不适用于所有情况,而且性能可能不太好。然而,这是一个非常可靠的解决方案,如果你能做到的话。

解决方案是,系统中的每个对象都有一个唯一的标识符以及所有其他属性。标识符的唯一性将在生成时得到保证。在比较两个对象时,您将使用这个ID(也称为UUID/GUID—全局/通用唯一标识符)。即,当且仅当这些id相等时,它们相等。

id可以是简单的自动增量数字,也可以是通过库生成的字符串(建议)或一段代码。所有你需要做的是确保它总是唯一的,在auto_incremental的情况下,它可以是内置的,或者在UUID的情况下,可以检查是否所有现有的值(例如MySQL的unique列属性)或简单地(如果来自库)依赖于给出极低的冲突可能性。

注意,您还需要始终将ID存储在对象中(以保证其唯一性),并且实时计算它可能不是最好的方法。

优点:可靠,高效,不脏,现代。 缺点:需要额外的空间。可能需要重新设计一下系统。

我的父亲类比

我父亲的社保号是911-345-9283,所以有这个社保号的人就是我父亲,任何自称是我父亲的人也一定有这个社保号。


结论

就准确性和可靠性而言,我个人更喜欢解决方案#4 (ID)。如果不可能,我会选择2.1,因为它具有可预测性,然后是3。如果两者都不可能,选择第二条,最后是第一条。

其他回答

简而言之,这将检查两个变量是否相等,无论其类型如何。

function isSame (obj1, obj2) {
  const obj1Keys = Object.keys(obj1)
  const obj2Keys = Object.keys(obj2)

  return obj1Keys.length === obj2Keys.length && obj1Keys.every((key) => obj1[key] === obj2[key])
}

这是对以上所有内容的补充,而不是替代。如果需要快速浅比较对象,而不需要检查额外的递归情况。这是一个镜头。

这比较:1)自己的属性数量相等,2)键名相等,3)如果bCompareValues == true,对应的属性值及其类型相等(三重相等)

var shallowCompareObjects = function(o1, o2, bCompareValues) {
    var s, 
        n1 = 0,
        n2 = 0,
        b  = true;

    for (s in o1) { n1 ++; }
    for (s in o2) { 
        if (!o1.hasOwnProperty(s)) {
            b = false;
            break;
        }
        if (bCompareValues && o1[s] !== o2[s]) {
            b = false;
            break;
        }
        n2 ++;
    }
    return b && n1 == n2;
}

我需要模拟jQuery POST请求,因此对我来说重要的是两个对象具有相同的属性集(任何一个对象中都不缺少属性),并且每个属性值都是“相等的”(根据这个定义)。我不关心对象是否有不匹配的方法。

这是我将使用的,它应该足以满足我的特定要求:

function PostRequest() {
    for (var i = 0; i < arguments.length; i += 2) {
        this[arguments[i]] = arguments[i+1];
    }

    var compare = function(u, v) {
        if (typeof(u) != typeof(v)) {
            return false;
        }

        var allkeys = {};
        for (var i in u) {
            allkeys[i] = 1;
        }
        for (var i in v) {
            allkeys[i] = 1;
        }
        for (var i in allkeys) {
            if (u.hasOwnProperty(i) != v.hasOwnProperty(i)) {
                if ((u.hasOwnProperty(i) && typeof(u[i]) == 'function') ||
                    (v.hasOwnProperty(i) && typeof(v[i]) == 'function')) {
                    continue;
                } else {
                    return false;
                }
            }
            if (typeof(u[i]) != typeof(v[i])) {
                return false;
            }
            if (typeof(u[i]) == 'object') {
                if (!compare(u[i], v[i])) {
                    return false;
                }
            } else {
                if (u[i] !== v[i]) {
                    return false;
                }
            }
        }

        return true;
    };

    this.equals = function(o) {
        return compare(this, o);
    };

    return this;
}

像这样使用:

foo = new PostRequest('text', 'hello', 'html', '<p>hello</p>');
foo.equals({ html: '<p>hello</p>', text: 'hello' });

这是我的版本。它正在使用new Object。ES5中引入的keys特性以及+、+和+的想法/测试:

function objectEquals(x, y) { 'use strict'; if (x === null || x === undefined || y === null || y === undefined) { return x === y; } // after this just checking type of one would be enough if (x.constructor !== y.constructor) { return false; } // if they are functions, they should exactly refer to same one (because of closures) if (x instanceof Function) { return x === y; } // if they are regexps, they should exactly refer to same one (it is hard to better equality check on current ES) if (x instanceof RegExp) { return x === y; } if (x === y || x.valueOf() === y.valueOf()) { return true; } if (Array.isArray(x) && x.length !== y.length) { return false; } // if they are dates, they must had equal valueOf if (x instanceof Date) { return false; } // if they are strictly equal, they both need to be object at least if (!(x instanceof Object)) { return false; } if (!(y instanceof Object)) { return false; } // recursive object equality check var p = Object.keys(x); return Object.keys(y).every(function (i) { return p.indexOf(i) !== -1; }) && p.every(function (i) { return objectEquals(x[i], y[i]); }); } /////////////////////////////////////////////////////////////// /// The borrowed tests, run them by clicking "Run code snippet" /////////////////////////////////////////////////////////////// var printResult = function (x) { if (x) { document.write('<div style="color: green;">Passed</div>'); } else { document.write('<div style="color: red;">Failed</div>'); } }; var assert = { isTrue: function (x) { printResult(x); }, isFalse: function (x) { printResult(!x); } } assert.isTrue(objectEquals(null,null)); assert.isFalse(objectEquals(null,undefined)); assert.isFalse(objectEquals(/abc/, /abc/)); assert.isFalse(objectEquals(/abc/, /123/)); var r = /abc/; assert.isTrue(objectEquals(r, r)); assert.isTrue(objectEquals("hi","hi")); assert.isTrue(objectEquals(5,5)); assert.isFalse(objectEquals(5,10)); assert.isTrue(objectEquals([],[])); assert.isTrue(objectEquals([1,2],[1,2])); assert.isFalse(objectEquals([1,2],[2,1])); assert.isFalse(objectEquals([1,2],[1,2,3])); assert.isTrue(objectEquals({},{})); assert.isTrue(objectEquals({a:1,b:2},{a:1,b:2})); assert.isTrue(objectEquals({a:1,b:2},{b:2,a:1})); assert.isFalse(objectEquals({a:1,b:2},{a:1,b:3})); assert.isTrue(objectEquals({1:{name:"mhc",age:28}, 2:{name:"arb",age:26}},{1:{name:"mhc",age:28}, 2:{name:"arb",age:26}})); assert.isFalse(objectEquals({1:{name:"mhc",age:28}, 2:{name:"arb",age:26}},{1:{name:"mhc",age:28}, 2:{name:"arb",age:27}})); Object.prototype.equals = function (obj) { return objectEquals(this, obj); }; var assertFalse = assert.isFalse, assertTrue = assert.isTrue; assertFalse({}.equals(null)); assertFalse({}.equals(undefined)); assertTrue("hi".equals("hi")); assertTrue(new Number(5).equals(5)); assertFalse(new Number(5).equals(10)); assertFalse(new Number(1).equals("1")); assertTrue([].equals([])); assertTrue([1,2].equals([1,2])); assertFalse([1,2].equals([2,1])); assertFalse([1,2].equals([1,2,3])); assertTrue(new Date("2011-03-31").equals(new Date("2011-03-31"))); assertFalse(new Date("2011-03-31").equals(new Date("1970-01-01"))); assertTrue({}.equals({})); assertTrue({a:1,b:2}.equals({a:1,b:2})); assertTrue({a:1,b:2}.equals({b:2,a:1})); assertFalse({a:1,b:2}.equals({a:1,b:3})); assertTrue({1:{name:"mhc",age:28}, 2:{name:"arb",age:26}}.equals({1:{name:"mhc",age:28}, 2:{name:"arb",age:26}})); assertFalse({1:{name:"mhc",age:28}, 2:{name:"arb",age:26}}.equals({1:{name:"mhc",age:28}, 2:{name:"arb",age:27}})); var a = {a: 'text', b:[0,1]}; var b = {a: 'text', b:[0,1]}; var c = {a: 'text', b: 0}; var d = {a: 'text', b: false}; var e = {a: 'text', b:[1,0]}; var i = { a: 'text', c: { b: [1, 0] } }; var j = { a: 'text', c: { b: [1, 0] } }; var k = {a: 'text', b: null}; var l = {a: 'text', b: undefined}; assertTrue(a.equals(b)); assertFalse(a.equals(c)); assertFalse(c.equals(d)); assertFalse(a.equals(e)); assertTrue(i.equals(j)); assertFalse(d.equals(k)); assertFalse(k.equals(l)); // from comments on stackoverflow post assert.isFalse(objectEquals([1, 2, undefined], [1, 2])); assert.isFalse(objectEquals([1, 2, 3], { 0: 1, 1: 2, 2: 3 })); assert.isFalse(objectEquals(new Date(1234), 1234)); // no two different function is equal really, they capture their context variables // so even if they have same toString(), they won't have same functionality var func = function (x) { return true; }; var func2 = function (x) { return true; }; assert.isTrue(objectEquals(func, func)); assert.isFalse(objectEquals(func, func2)); assert.isTrue(objectEquals({ a: { b: func } }, { a: { b: func } })); assert.isFalse(objectEquals({ a: { b: func } }, { a: { b: func2 } }));

为了比较简单的键/值对对象实例的键,我使用:

function compareKeys(r1, r2) {
    var nloops = 0, score = 0;
    for(k1 in r1) {
        for(k2 in r2) {
            nloops++;
            if(k1 == k2)
                score++; 
        }
    }
    return nloops == (score * score);
};

一旦比较了键,一个简单的for. in循环就足够了。

复杂度是O(N*N), N是键的个数。

我希望/猜测我定义的对象不会拥有超过1000个属性…