严格相等运算符将告诉您两个对象类型是否相等。然而,是否有一种方法来判断两个对象是否相等,就像Java中的哈希码值一样?
堆栈溢出问题JavaScript中有hashCode函数吗?类似于这个问题,但需要一个更学术的答案。上面的场景说明了为什么有必要有一个,我想知道是否有等效的解决方案。
严格相等运算符将告诉您两个对象类型是否相等。然而,是否有一种方法来判断两个对象是否相等,就像Java中的哈希码值一样?
堆栈溢出问题JavaScript中有hashCode函数吗?类似于这个问题,但需要一个更学术的答案。上面的场景说明了为什么有必要有一个,我想知道是否有等效的解决方案。
当前回答
短功能deepEqual实现:
function deepEqual(x, y) {
return (x && y && typeof x === 'object' && typeof y === 'object') ?
(Object.keys(x).length === Object.keys(y).length) &&
Object.keys(x).reduce(function(isEqual, key) {
return isEqual && deepEqual(x[key], y[key]);
}, true) : (x === y);
}
编辑:版本2,使用jib的建议和ES6的箭头函数:
function deepEqual(x, y) {
const ok = Object.keys, tx = typeof x, ty = typeof y;
return x && y && tx === 'object' && tx === ty ? (
ok(x).length === ok(y).length &&
ok(x).every(key => deepEqual(x[key], y[key]))
) : (x === y);
}
其他回答
不同。如果对象中键的顺序不重要,我不需要知道所述对象的原型。使用总是有用的。
const object = {};
JSON.stringify(object) === "{}" will pass but {} === "{}" will not
下面是一个使用ES6+的解决方案
// this comparison would not work for function and symbol comparisons
// this would only work best for compared objects that do not belong to same address in memory
// Returns true if there is no difference, and false otherwise
export const isObjSame = (obj1, obj2) => {
if (typeof obj1 !== "object" && obj1 !== obj2) {
return false;
}
if (typeof obj1 !== "object" && typeof obj2 !== "object" && obj1 === obj2) {
return true;
}
if (typeof obj1 === "object" && typeof obj2 === "object") {
if (Array.isArray(obj1) && Array.isArray(obj2)) {
if (obj1.length === obj2.length) {
if (obj1.length === 0) {
return true;
}
const firstElemType = typeof obj1[0];
if (typeof firstElemType !== "object") {
const confirmSameType = currentType =>
typeof currentType === firstElemType;
const checkObjOne = obj1.every(confirmSameType);
const checkObjTwo = obj2.every(confirmSameType);
if (checkObjOne && checkObjTwo) {
// they are primitves, we can therefore sort before and compare by index
// use number sort
// use alphabet sort
// use regular sort
if (firstElemType === "string") {
obj1.sort((a, b) => a.localeCompare(b));
obj2.sort((a, b) => a.localeCompare(b));
}
obj1.sort((a, b) => a - b);
obj2.sort((a, b) => a - b);
let equal = true;
obj1.map((element, index) => {
if (!isObjSame(element, obj2[index])) {
equal = false;
}
});
return equal;
}
if (
(checkObjOne && !checkObjTwo) ||
(!checkObjOne && checkObjTwo)
) {
return false;
}
if (!checkObjOne && !checkObjTwo) {
for (let i = 0; i <= obj1.length; i++) {
const compareIt = isObjSame(obj1[i], obj2[i]);
if (!compareIt) {
return false;
}
}
return true;
}
// if()
}
const newValue = isObjSame(obj1, obj2);
return newValue;
} else {
return false;
}
}
if (!Array.isArray(obj1) && !Array.isArray(obj2)) {
let equal = true;
if (obj1 && obj2) {
const allKeys1 = Array.from(Object.keys(obj1));
const allKeys2 = Array.from(Object.keys(obj2));
if (allKeys1.length === allKeys2.length) {
allKeys1.sort((a, b) => a - b);
allKeys2.sort((a, b) => a - b);
allKeys1.map((key, index) => {
if (
key.toLowerCase() !== allKeys2[index].toLowerCase()
) {
equal = false;
return;
}
const confirmEquality = isObjSame(obj1[key], obj2[key]);
if (!confirmEquality) {
equal = confirmEquality;
return;
}
});
}
}
return equal;
// return false;
}
}
};
这个问题已经有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。如果两者都不可能,选择第二条,最后是第一条。
这是我的版本。它正在使用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 } }));
纯JS方法:我的答案是基于生成一个返回相同值的字符串,无论属性顺序是否相同。设置对象可用于切换大小写和空白是否重要。(为了避免失去焦点,我没有包括那些支持函数,或者我猜应该在任何实用程序集中的isObject。)
这里也没有显示,但为了减少字符串比较时间,如果对象很大,你想加快比较,你也可以散列字符串和比较子字符串;这只适用于非常大的对象(当然也有很小的机会出现错误的相等)。
然后你可以比较genObjStr(obj1) ?= genObjStr(obj2)
function genObjStr(obj, settings) {
// Generate a string that corresponds to an object guarenteed to be the same str even if
// the object have different ordering. The string would largely be used for comparison purposes
var settings = settings||{};
var doStripWhiteSpace = defTrue(settings.doStripWhiteSpace);
var doSetLowerCase = settings.doSetLowerCase||false;
if(isArray(obj)) {
var vals = [];
for(var i = 0; i < obj.length; ++i) {
vals.push(genObjStr(obj[i], settings));
}
vals = arraySort(vals);
return vals.join(`,`);
} else if(isObject(obj)) {
var keys = Object.keys(obj);
keys = arraySort(keys);
var vals = [];
for(var key of keys) {
var value = obj[key];
value = genObjStr(value, settings);
if(doStripWhiteSpace) {
key = removeWhitespace(key);
var value = removeWhitespace(value);
};
if(doSetLowerCase) {
key = key.toLowerCase();
value = value.toLowerCase();
}
vals.push(value);
}
var str = JSON.stringify({keys: keys, vals: vals});
return str
} else {
if(doStripWhiteSpace) {
obj = removeWhitespace(obj);
};
if(doSetLowerCase) {
obj = obj.toLowerCase();
}
return obj
}
}
var obj1 = {foo: 123, bar: `Test`};
var obj2 = {bar: `Test`, foo: 123};
console.log(genObjStr(obj1) == genObjStr(obj1))