在JavaScript中比较对象的最佳方法是什么?
例子:
var user1 = {name : "nerd", org: "dev"};
var user2 = {name : "nerd", org: "dev"};
var eq = user1 == user2;
alert(eq); // gives false
我知道如果两个对象引用完全相同的对象,那么它们是相等的,但是有没有方法检查它们是否具有相同的属性值?
以下方式对我有效,但这是唯一的可能性吗?
var eq = Object.toJSON(user1) == Object.toJSON(user2);
alert(eq); // gives true
当然不是唯一的方法——您可以原型化一个方法(在这里针对Object,但我当然不建议将Object用于实时代码)来复制C#/Java风格的比较方法。
编辑,因为似乎需要一个通用示例:
Object.prototype.equals = function(x)
{
for(p in this)
{
switch(typeof(this[p]))
{
case 'object':
if (!this[p].equals(x[p])) { return false }; break;
case 'function':
if (typeof(x[p])=='undefined' || (p != 'equals' && this[p].toString() != x[p].toString())) { return false; }; break;
default:
if (this[p] != x[p]) { return false; }
}
}
for(p in x)
{
if(typeof(this[p])=='undefined') {return false;}
}
return true;
}
请注意,使用toString()测试方法绝对不够好,但一个可以接受的方法非常困难,因为空格是否有意义,更不用说同义词方法和使用不同实现产生相同结果的方法。以及针对Object的原型设计问题。
以下算法将处理自引用数据结构、数字、字符串、日期,当然还有普通的嵌套javascript对象:
当
根据==,它们完全相等(字符串和数字首先展开,以确保42等于数字(42))或者它们都是日期并且具有相同的值Of()或者它们都是同一类型且不是空的。。。它们不是对象,每个==都相等(捕获数字/字符串/布尔值)或者,忽略具有未定义值的财产,它们具有相同的财产,所有这些属性都被视为递归等价。
函数文本不认为函数相同。此测试不够,因为函数可能具有不同的闭包。只有当==这样说时,函数才被认为是相等的(但如果您选择这样做,您可以很容易地扩展等效关系)。
避免了循环数据结构可能导致的无限循环。当areEquivalent试图反驳等式并递归到对象的财产中时,它会跟踪需要进行此子比较的对象。如果等式可以被否定,那么对象之间的某些可达属性路径不同,那么必须有一条最短的可达路径,并且该最短可达路径不能包含两条路径中存在的循环;即在递归比较对象时假设相等是可以的。假设存储在属性areEquivalent_Eq_91_2_34中,该属性在使用后被删除,但如果对象图已经包含此类属性,则行为未定义。使用这样的标记属性是必要的,因为javascript不支持使用任意对象作为键的字典。
function unwrapStringOrNumber(obj) {
return (obj instanceof Number || obj instanceof String
? obj.valueOf()
: obj);
}
function areEquivalent(a, b) {
a = unwrapStringOrNumber(a);
b = unwrapStringOrNumber(b);
if (a === b) return true; //e.g. a and b both null
if (a === null || b === null || typeof (a) !== typeof (b)) return false;
if (a instanceof Date)
return b instanceof Date && a.valueOf() === b.valueOf();
if (typeof (a) !== "object")
return a == b; //for boolean, number, string, xml
var newA = (a.areEquivalent_Eq_91_2_34 === undefined),
newB = (b.areEquivalent_Eq_91_2_34 === undefined);
try {
if (newA) a.areEquivalent_Eq_91_2_34 = [];
else if (a.areEquivalent_Eq_91_2_34.some(
function (other) { return other === b; })) return true;
if (newB) b.areEquivalent_Eq_91_2_34 = [];
else if (b.areEquivalent_Eq_91_2_34.some(
function (other) { return other === a; })) return true;
a.areEquivalent_Eq_91_2_34.push(b);
b.areEquivalent_Eq_91_2_34.push(a);
var tmp = {};
for (var prop in a)
if(prop != "areEquivalent_Eq_91_2_34")
tmp[prop] = null;
for (var prop in b)
if (prop != "areEquivalent_Eq_91_2_34")
tmp[prop] = null;
for (var prop in tmp)
if (!areEquivalent(a[prop], b[prop]))
return false;
return true;
} finally {
if (newA) delete a.areEquivalent_Eq_91_2_34;
if (newB) delete b.areEquivalent_Eq_91_2_34;
}
}
我修改了上面的代码。对于我0!==false和null!==未定义。如果不需要这样严格的检查,请在代码中删除一个“=”sign-in“this[p]!==x[p]”。
Object.prototype.equals = function(x){
for (var p in this) {
if(typeof(this[p]) !== typeof(x[p])) return false;
if((this[p]===null) !== (x[p]===null)) return false;
switch (typeof(this[p])) {
case 'undefined':
if (typeof(x[p]) != 'undefined') return false;
break;
case 'object':
if(this[p]!==null && x[p]!==null && (this[p].constructor.toString() !== x[p].constructor.toString() || !this[p].equals(x[p]))) return false;
break;
case 'function':
if (p != 'equals' && this[p].toString() != x[p].toString()) return false;
break;
default:
if (this[p] !== x[p]) return false;
}
}
return true;
}
然后我用下一个对象测试了它:
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 f = {a: 'text', b:[1,0], f: function(){ this.f = this.b; }};
var g = {a: 'text', b:[1,0], f: function(){ this.f = this.b; }};
var h = {a: 'text', b:[1,0], f: function(){ this.a = this.b; }};
var i = {
a: 'text',
c: {
b: [1, 0],
f: function(){
this.a = this.b;
}
}
};
var j = {
a: 'text',
c: {
b: [1, 0],
f: function(){
this.a = this.b;
}
}
};
var k = {a: 'text', b: null};
var l = {a: 'text', b: undefined};
a==b预期为真;返回true
a==c预期为假;返回false
c==d预期为假;返回false
a==e预期为假;返回false
f==g预期为真;返回true
h==g预期为假;返回false
i==j预期为真;返回true
d==k预期为假;返回false
k==l预期为假;返回false
当然不是唯一的方法——您可以原型化一个方法(在这里针对Object,但我当然不建议将Object用于实时代码)来复制C#/Java风格的比较方法。
编辑,因为似乎需要一个通用示例:
Object.prototype.equals = function(x)
{
for(p in this)
{
switch(typeof(this[p]))
{
case 'object':
if (!this[p].equals(x[p])) { return false }; break;
case 'function':
if (typeof(x[p])=='undefined' || (p != 'equals' && this[p].toString() != x[p].toString())) { return false; }; break;
default:
if (this[p] != x[p]) { return false; }
}
}
for(p in x)
{
if(typeof(this[p])=='undefined') {return false;}
}
return true;
}
请注意,使用toString()测试方法绝对不够好,但一个可以接受的方法非常困难,因为空格是否有意义,更不用说同义词方法和使用不同实现产生相同结果的方法。以及针对Object的原型设计问题。