前言
之前文章有些深拷贝和浅拷贝,这篇文章简单总结常用的深度比较方法,这些方法在react中用的特别多,特别是生命周期里面prop对比。因此,react和immutable天生结合性比较好。假设你项目中没有用immutable这种第三方库,那么实现深度比较的方法,在这里总结一下!
方法一
function equalsObject (obj1,obj) {
var p;
if (obj1 === obj) {
return true;
}
// some checks for native types first
// function and sring
if (typeof(obj1) === "function" || typeof(obj1) === "string" || obj1 instanceof String) {
return obj1.toString() === obj.toString();
}
// number
if (obj1 instanceof Number || typeof(obj1) === "number") {
if (obj instanceof Number || typeof(obj) === "number") {
return obj1.valueOf() === obj.valueOf();
}
return false;
}
// equalsObject(null,null) and equalsObject(undefined,undefined) do not inherit from the
// Object.prototype so we can return false when they are passed as obj
if (typeof(obj1) !== typeof(obj) || obj === null || typeof(obj) === "undefined") {
return false;
}
function sort (o) {
var result = {};
if (typeof o !== "object") {
return o;
}
Object.keys(o).sort().forEach(function (key) {
result[key] = sort(o[key]);
});
return result;
}
if (typeof(obj1) === "object") {
if (Array.isArray(obj1)) { // check on arrays
return JSON.stringify(obj1) === JSON.stringify(obj);
} else { // anyway objects
for (p in obj1) {
if (typeof(obj1[p]) !== typeof(obj[p])) {
return false;
}
if ((obj1[p] === null) !== (obj[p] === null)) {
return false;
}
switch (typeof(obj1[p])) {
case 'undefined':
if (typeof(obj[p]) !== 'undefined') {
return false;
}
break;
case 'object':
if (obj1[p] !== null
&& obj[p] !== null
&& (obj1[p].constructor.toString() !== obj[p].constructor.toString()
|| !equalsObject(obj1[p],obj[p]))) {
return false;
}
break;
case 'function':
if (obj1[p].toString() !== obj[p].toString()) {
return false;
}
break;
default:
if (obj1[p] !== obj[p]) {
return false;
}
}
};
}
}
// at least check them with JSON
return JSON.stringify(sort(obj1)) === JSON.stringify(sort(obj));
}
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};
equalsObject(a,b) //true
equalsObject(f,g)//true
equalsObject(f,h)//false
方法二
function deepCompare(x, y) {
var i, l, leftChain, rightChain;
function compare2Objects(x, y) {
var p;
// remember that NaN === NaN returns false
// and isNaN(undefined) returns true
if (isNaN(x) && isNaN(y) && typeof x === 'number' && typeof y === 'number') {
return true;
}
// Compare primitives and functions.
// Check if both arguments link to the same object.
// Especially useful on the step where we compare prototypes
if (x === y) {
return true;
}
// Works in case when functions are created in constructor.
// Comparing dates is a common scenario. Another built-ins?
// We can even handle functions passed across iframes
if ((typeof x === 'function' && typeof y === 'function') ||
(x instanceof Date && y instanceof Date) ||
(x instanceof RegExp && y instanceof RegExp) ||
(x instanceof String && y instanceof String) ||
(x instanceof Number && y instanceof Number)) {
return x.toString() === y.toString();
}
// At last checking prototypes as good as we can
if (!(x instanceof Object && y instanceof Object)) {
return false;
}
if (x.isPrototypeOf(y) || y.isPrototypeOf(x)) {
return false;
}
if (x.constructor !== y.constructor) {
return false;
}
if (x.prototype !== y.prototype) {
return false;
}
// Check for infinitive linking loops
if (leftChain.indexOf(x) > -1 || rightChain.indexOf(y) > -1) {
return false;
}
// Quick checking of one object being a subset of another.
// todo: cache the structure of arguments[0] for performance
for (p in y) {
if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
return false;
} else if (typeof y[p] !== typeof x[p]) {
return false;
}
}
for (p in x) {
if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
return false;
} else if (typeof y[p] !== typeof x[p]) {
return false;
}
switch (typeof(x[p])) {
case 'object':
case 'function':
leftChain.push(x);
rightChain.push(y);
if (!compare2Objects(x[p], y[p])) {
return false;
}
leftChain.pop();
rightChain.pop();
break;
default:
if (x[p] !== y[p]) {
return false;
}
break;
}
}
return true;
}
if (arguments.length < 1) {
return true; //Die silently? Don't know how to handle such case, please help...
// throw "Need two or more arguments to compare";
}
for (i = 1, l = arguments.length; i < l; i++) {
leftChain = []; //Todo: this can be cached
rightChain = [];
if (!compare2Objects(arguments[0], arguments[i])) {
return false;
}
}
return true;
}
这两个方法均在项目中用过,比较好用。
方法三
function countProps(obj) {
var count = 0;
for (k in obj) {
if (obj.hasOwnProperty(k)) {
count++;
}
}
return count;
};
function objectEquals(v1, v2) {
if (typeof(v1) !== typeof(v2)) {
return false;
}
if (typeof(v1) === "function") {
return v1.toString() === v2.toString();
}
if (v1 instanceof Object && v2 instanceof Object) {
if (countProps(v1) !== countProps(v2)) {
return false;
}
var r = true;
for (k in v1) {
r = objectEquals(v1[k], v2[k]);
if (!r) {
return false;
}
}
return true;
} else {
return v1 === v2;
}
}
方法四
这个是组合方法,实际项目中也用过!
const hasOwnProperty = (obj1: any, obj2: any) => {
switch (typeOf(obj1)) {
case "object":
return Object.prototype.hasOwnProperty.call(obj1, obj2);
case "array":
return typeOf(obj2) === "number" && obj2 >= 0 && obj1.length > obj2 ;
default:
return false;
}
};
const typeOf = (value) => Object.prototype.toString.call(value).replace(/\[|\]/gi, "").split(" ")[1].toLowerCase();
const length = (object) => {
switch (typeOf(object)) {
case "array":
return object.length;
case "object":
return Object.keys(object).length;
default:
return 0;
}
};
const every = (obj, func) => typeOf(obj) === "array" ? obj.every(func) : Object.entries(obj).every(([key, value]: [any, any]) => func(value, key));
const equal = (obj1, obj2) => {
switch (true) {
case typeOf(obj1) === "function" && typeOf(obj2) === "function":
return true;
case obj1 === obj2:
return true;
case typeOf(obj1) === typeOf(obj2) && ["object", "array"].includes(typeOf(obj1)) && length(obj1) === length(obj2):
return every(obj1, (value, key) => hasOwnProperty(obj2, key) && equal(value, obj2[key]));
default:
return false;
}
};
直接通过equal(a,b)进行比较