汇总常用的js对象Object的深比较方法

16859次浏览

前言

之前文章有些深拷贝和浅拷贝,这篇文章简单总结常用的深度比较方法,这些方法在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)进行比较

相关文章:

  1. Celestial
    1
    方法三对{a:2, c: undifined}和{a: 2, d = undefined}的测试为true