Javascript - deepEqual Comparison

后端 未结 8 1279
囚心锁ツ
囚心锁ツ 2020-12-05 05:21

Question (From Eloquent Javascript 2nd Edition, Chapter 4, Exercise 4):

Write a function, deepEqual, that takes two values and returns true only if th

相关标签:
8条回答
  • 2020-12-05 05:43

    Although that it's more verbose, maybe this option is easier to read:

    function deepEqual(elem1, elem2) {
        if(elem1 === elem2) {
            return true;
        }
        if(typeof elem1 == 'object' && typeof elem2 == 'object' && elem1 != null && elem2 != null) {
          if(Object.keys(elem1).length == Object.keys(elem2).length) {
              for(let key of Object.keys(elem1)) {
                  if(elem2.hasOwnProperty(key) != true) {
                      return false;
                  }
              }
              for(let key of Object.keys(elem1)) {
                  if(typeof elem1[key] == 'object' && typeof elem2[key] == 'object' && typeof elem1[key] != null && typeof elem2[key] != null) {
                      return deepEqual(elem1[key], elem2[key]);
                  }
                  else {
                    if(elem1[key] !== elem2[key]) {
                        return false;
                    }
                  }
              } else {
                return false;
              }
            }
          }
        else {
            return false;
        }
        return true;
      }
    
    0 讨论(0)
  • 2020-12-05 05:43

    Based on the accepted answer by Paul Roub, I needed it to also match function values, and I wanted it to be a lot more concise, so I've refactored it.

    function deepEqual(x, y, z) {
      return x === y || typeof x == "function" && y && x.toString() == y.toString()
        || x && y && typeof x == "object" && x.constructor == y.constructor
        && (z = Object.keys(y)) && z.length == Object.keys(x).length
        && !z.find(v => !deepEqual(x[v], y[v]));
    }
    
    var myFunc = (x) => { return x*2; }
    var obj = {here: {is: "an", other: "3"}, object: 2, andFunc: myFunc};
    console.log(deepEqual(obj, obj));
    // → true
    console.log(deepEqual(obj, {here: 1, object: 2, andFunc: myFunc}));
    // → false
    console.log(deepEqual(obj, {here: {is: "an"}, object: 2, andFunc: myFunc}));
    // → false
    console.log(deepEqual(obj, {here: {is: "an", other: "2"}, object: 2, andFunc: myFunc}));
    // → false
    console.log(deepEqual(obj, {here: {is: "an", other: "3"}, object: 2, andFunc: myFunc}));
    // → true
    console.log(deepEqual(obj, {here: {is: "an", other: "3"}, object: 2, andFunc: (x) => { return x*2; }}));
    // → true
    console.log(deepEqual(obj, {here: {is: "an", other: "3"}, object: 2, andFunc: (x) => { return x*999; }}));
    // → false

    notes:

    • You pass in only 2 args: x and y (z is for internal use).
    • If one of the variables is null or undefined it returns that value instead of false, but that result is still "falsey" so I'm OK with it. To fix that you could change all occurrences of y && to (y || !1) && and x && to (x || !1) &&
    • If you definitely don't expect that functions/callbacks would be supplied in your objects then remove || typeof x == "function" && y && x.toString() == y.toString()
    0 讨论(0)
  • 2020-12-05 05:46

    I just went through this chapter and wanted to show my work, too.

    The flaw in mine (let me know if there are more) is that the object properties have to be in exact order as well. I much prefer @paul and @danni's solution.

    // Deep equal 
    const deepEqual = (x, y) => {
      const xType = typeof x;
      const yType = typeof y; 
      
      if ( xType === 'object' && yType === 'object' && ( x !== null && y !== null ) ) {
        const xKeys = Object.keys(x);
        const yKeys = Object.keys(y);
        const xValues = Object.values(x);
        const yValues = Object.values(y);  
        
        // check length of both arrays
        if ( xKeys.length !== yKeys.length ) return false;
        
        // compare keys
        for ( i = 0; i < xKeys.length; i++ )
          if (xKeys[i] !== yKeys[i]) return false;
          
        // compare values
        for ( i = 0; i < xValues.length; i++ )
          if (!deepEqual(xValues[i], yValues[i])) return false;
          
      } else {
        if ( x !== y ) return false;
      }
      return true;
    };
    
    // Objects
    let obj1 = {
      value: false,
      pets: null
    };
    
    let obj2 = {
      value: false,
      pets: null
    };
    
    
    let obj3 = {
      value: false,
      pets: {
        cat: false,
        dog: {
          better: 'yes'
        }
      }
    };
    
    let obj4 = {
      value: false,
      pets: { 
        cat: false,
        dog: {
          better: 'yes'
        }
      }
    };
    
    
    let obj5 = {
      value: false,
      dog: true
    };
    
    let obj6 = {
      value: false,
      cat: true
    };
    
    
    let obj7 = {
      value: true,
      dog: {
        cat: {
          wow: true
        }
      }
    };
    
    let obj8 = {
      value: true,
      dog: {
        cat: {
          wow: false
        }
      }
    };
    
    
    let obj9 = {
      value: true,
      dog: {
        cat: {
          wow: true
        }
      }
    };
    
    let obj10 = {
      dog: {
        cat: {
          wow: true
        }
      },
      value: true
    };
    
    // Just for building a pretty result, ignore if you'd like
    const result = (x, y) => {
      return `For: <br/>
              ${JSON.stringify(x)} <br/>
              and <br/>
              ${JSON.stringify(y)} <br/>
              <span>>> ${deepEqual(x, y)}</span>`;
    };
    
    // To print results in
    const resultDivs = document.querySelectorAll('.result');
    
    resultDivs[0].innerHTML = result(obj1, obj2);
    resultDivs[1].innerHTML = result(obj3, obj4);
    resultDivs[2].innerHTML = result(obj5, obj6);
    resultDivs[3].innerHTML = result(obj7, obj8);
    resultDivs[4].innerHTML = result(obj9, obj10);
    body {
      font-family: monospace;
    }
    
    span {
      color: #a0a0a0;
    }
    
    .result {
      margin-bottom: 1em;
    }
    <div class="result">
    </div>
    
    <div class="result">
    </div>
    
    <div class="result">
    </div>
    
    <div class="result">
    </div>
    
    <div class="result">
    </div>

    0 讨论(0)
  • 2020-12-05 05:47

    As you suspect, you're returning the match of the first property seen. You should return false if that property doesn't match, but keep looking otherwise.

    Also, return false if there's no prop property found on y (that is, the counts match, but not the actual properties).

    If all properties have matched, return true:

    var deepEqual = function (x, y) {
      if (x === y) {
        return true;
      }
      else if ((typeof x == "object" && x != null) && (typeof y == "object" && y != null)) {
        if (Object.keys(x).length != Object.keys(y).length)
          return false;
    
        for (var prop in x) {
          if (y.hasOwnProperty(prop))
          {  
            if (! deepEqual(x[prop], y[prop]))
              return false;
          }
          else
            return false;
        }
    
        return true;
      }
      else 
        return false;
    }
    

    var deepEqual = function (x, y) {
      if (x === y) {
        return true;
      }
      else if ((typeof x == "object" && x != null) && (typeof y == "object" && y != null)) {
        if (Object.keys(x).length != Object.keys(y).length)
          return false;
    
        for (var prop in x) {
          if (y.hasOwnProperty(prop))
          {  
            if (! deepEqual(x[prop], y[prop]))
              return false;
          }
          else
            return false;
        }
    
        return true;
      }
      else 
        return false;
    }
    
    var obj = {here: {is: "an", other: "3"}, object: 2};
    console.log(deepEqual(obj, obj));
    // → true
    console.log(deepEqual(obj, {here: 1, object: 2}));
    // → false
    console.log(deepEqual(obj, {here: {is: "an"}, object: 2}));
    // → false
    console.log(deepEqual(obj, {here: {is: "an", other: "2"}, object: 2}));
    // → false
    console.log(deepEqual(obj, {here: {is: "an", other: "3"}, object: 2}));
    // → true

    0 讨论(0)
  • 2020-12-05 05:47

    You can use a variable outside the for loop to keep track of the comparison:

    var allPropertiesEqual = true;
    for (var prop in x) {
        if (y.hasOwnProperty(prop)) {
            allPropertiesEqual = deepEqual(x[prop], y[prop]) && allPropertiesEqual;
        } else {
            allPropertiesEqual = false;
        }
    }
    return allPropertiesEqual;
    

    The previous example is not optimized on purpose. Because you're comparing objects, you know that you can return false as soon as you find an inequality, and you can keep looping while all the previous checked properties are equal:

    for (var prop in x) {
        if (y.hasOwnProperty(prop)) {
            if (! deepEqual(x[prop], y[prop]) )
                return false; //first inequality found, return false
        } else {
            return false; //different properties, so inequality, so return false
        }
    }
    return true;
    
    0 讨论(0)
  • 2020-12-05 05:53

    <script>
    var cmp = function(element, target){
    
       if(typeof element !== typeof target)
       {
          return false;
       }
       else if(typeof element === "object" && (!target || !element))
       {
          return target === element;
       }
       else if(typeof element === "object")
       {
           var keys_element = Object.keys(element);
           var keys_target  = Object.keys(target);
           
           if(keys_element.length !== keys_target.length)
           {
               return false;
           }
           else
           {
               for(var i = 0; i < keys_element.length; i++)
               {
                    if(keys_element[i] !== keys_target[i])
                        return false;
                    if(!cmp(element[keys_element[i]], target[keys_target[i]]))
                        return false;
               }
    		   return true;
           }
       }
       else
       {
       	   return element === target;
    
       }
    };
    
    console.log(cmp({
        key1: 3,
        key2: "string",
        key3: [4, "45", {key4: [5, "6", false, null, {v:1}]}]
    }, {
        key1: 3,
        key2: "string",
        key3: [4, "45", {key4: [5, "6", false, null, {v:1}]}]
    })); // true
    
    console.log(cmp({
        key1: 3,
        key2: "string",
        key3: [4, "45", {key4: [5, "6", false, null, {v:1}]}]
    }, {
        key1: 3,
        key2: "string",
        key3: [4, "45", {key4: [5, "6", undefined, null, {v:1}]}]
    })); // false
    </script>

    0 讨论(0)
提交回复
热议问题