【问题标题】:How to convert a recursive function using a global variable in to a pure function?如何将使用全局变量的递归函数转换为纯函数?
【发布时间】:2018-11-07 03:24:57
【问题描述】:

我有一个程序对提供给它的 2 个对象进行深度比较,并使用递归来这样做。我的问题是,由于我使用全局变量来保留信息,因此我必须每次在对函数进行任何后续调用之前重置它。除了使用全局变量之外,我还有什么方法可以维护变量值并让它不那么麻烦吗?

let isEqual = true;
function deepEqual(object1, object2) {

  if (!((typeof(object1) == 'object' && typeof(object2) == 'object') || (object1 && object2))) {

    return isEqual = object1 === object2;

  } else if (typeof(object1) == 'object' && typeof(object2) == 'object') {

    if ((object1 && object2)) {

      let object1Keys = Object.keys(object1);

      let object2Keys = Object.keys(object2);
    
      if (object1Keys.length == object2Keys.length) {
        for (let index = 0; index < object1Keys.length; index++) {
          if (isEqual) {
            if (!(typeof(object1[object1Keys[index]]) == 'object' && typeof(object2[object2Keys[index]]) == 'object')) {
             isEqual = (object1[object1Keys[index]] === object2[object2Keys[index]]) && (object1Keys[index] === object2Keys[index]);
            } else {
              deepEqual(object1[object1Keys[index]], object2[object2Keys[index]]);
            }

          } else {
            return isEqual = false;
          }
        }
      }
    }

  }

  return isEqual;
}

let obj1 = {
  a: 'somestring',
  b: 42,
  c: {
    1: 'one',
    2: {
      4: 'Three'
    }
  }
};

let obj2 = {
  a: 'somestring',
  b: 42,
  c: {
    1: 'one',
    2: {
      3: 'Three'
    }
  }
};
console.log("obj1 == obj2 : ");
console.log(deepEqual(obj1, obj2));


let obj3 = {
  a: 'somestring',
  b: 42,
  c: {
    1: 'one',
    2: {
      3: 'Three'
    }
  }
};

let obj4 = {
  a: 'somestring',
  b: 42,
  c: {
    1: 'one',
    2: {
      3: 'Three'
    }
  }
};
console.log("obj3 == obj4 : ");
isEqual = true;
console.log(deepEqual(obj3, obj4));
let obj = {name: {gender: "F"}, age: 20};
isEqual = true;
console.log(deepEqual(obj, {name: {gender: "F"}, age: 20}));

【问题讨论】:

  • JSON.stringify(obj1) === JSON.stringify(obj2) // JSON.stringify(obj3) === JSON.stringify(obj4)
  • @RandyCasburn 谢谢!有没有办法通过纯递归函数来实现它。我想学习递归,或者说是实现它的好方法。 :)
  • recursive diffrecursive union - 我认为这两个问答将向您展示其他需要考虑的重要事项。
  • 比较密钥的长度是不可靠的。 { a: 1 }{ b: 2 } 都有一个键,但这并不能告诉你什么是平等的。
  • { a: /foo/ }{ a: /foo/ } 是否被视为相等? { a: someFunc }{ a: someFunc } 怎么样?也可以考虑其他复杂的对象,例如 HTMLElement。您必须提出并回答更多问题,才能使您的对象相等函数变得健壮可靠。

标签: javascript recursion


【解决方案1】:

你根本不需要使用它:你可以通过递归来完成整个事情:

function deepEqual(o1, o2){
  if (typeof o1 != typeof o2)
    return false;

  if (typeof o1 != 'object' || o1 === null || o2 === null)
    return o1 === o2;

  for (var k in o1){
   if (!deepEqual(o1[k], o2[k]))
    return false;
  }
  for (var k in o2){
    if (!(k in o1))
      return false;
  }
  return true;
}

【讨论】:

  • 这太完美了!我确实使用了递归,但是我试图继承变量值,这是我想知道如何不做的。你的方法好多了,也少了很多混乱:)
  • 看看它,我想如果 o1 不是,您还想测试 o2 是否为 null,但它至少是一个开始探索的地方。
  • 是的,绝对的。
  • deepEqual({a:1}, {a:1,b:2}) // true
【解决方案2】:

我创建了一个实用程序来深入比较两个对象。它使用带有两个对象的递归调用并返回真或假。

回购的Github链接-https://github.com/maninder-singh/deep-compare

<script src="deep-compare.js"></script>

JS

    1. dc(null,null);
    2. dc("a","a");
    3. dc("a","ab");
    4. dc("a",undefined);
    5. dc(undefined,undefined);
    6. dc({},[]);
    7. dc({a:1},{});
    8. dc({a:1},{a:1});
    9. dc(true,true);
    10. dc(true,false);

【讨论】:

  • 警告:链接的仓库未经测试
  • @user633183 是否会破坏任何测试用例。如果是,请与我分享,我也很乐意处理。
【解决方案3】:

您可以使用经过测试的、防弹的对象相等方法提供我的各种 JS 库来执行对象相等测试,如下图所示

lodash图书馆:

_.isEqual(obj1, obj2)

或者
自定义测试方法

function deepCompare () {
  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;
}

参考:Object comparison in JavaScript

【讨论】:

    【解决方案4】:

    function deepEqual(object1, object2) {
      //check if the given objects have the same datatype
      if (typeof(object1) === typeof(object2)) {
        //check if the given object is a primitive datatype and how to handle null values
        if ((typeof(object1) !== 'object') && (typeof(object2) !== 'object') ||
          object1 === null || object2 === null) {
          return object1 === object2;
        } else {
          //if they are both objects
          if (object1 !== null && object2 !== null) {
            let object1Keys = Object.keys(object1);
            let object2Keys = Object.keys(object2);
    
            //check if the arrays have the same length
            if (object1Keys.length === object2Keys.length) {
              let isEqual;
              for (let index = 0; index < object1Keys.length; index++) {
                //make sure both key:value pairs match
                if (object1Keys[index] === object2Keys[index]) {
                  //check if the current value is another object
                  if (typeof(object1[object1Keys[index]]) === 'object' &&
                    typeof(object2[object2Keys[index]]) === 'object') {
                    return deepEqual(object1[object1Keys[index]], object2[object2Keys[index]]);
                  } else {
    
                    isEqual = (object1[object1Keys[index]] === object2[object2Keys[index]]);
                  }
                } else {
    
                  return false; //return false if keys dont match
                }
              }
              return isEqual;
    
            } else {
              return false; //return false if 2 arrays dont have the same length
            }
    
          }
        }
      } else {
        return false; //return false if 2 object types dont match
      }
    }
    
    let obj1 = {
      a: 'somestring',
      b: 42,
      c: {
        1: 'one',
        2: {
          3: 'Three'
        }
      }
    };
    
    let obj2 = {
      a: 'somestring',
      b: 42,
      e: {
        1: 'one',
        2: {
          3: 'Three'
        }
      }
    };
    console.log("obj1 == obj2 : ");
    console.log(deepEqual(obj1, obj2));
    
    
    let obj3 = {
      a: 'somestring',
      b: 42,
      c: {
        1: 'one',
        2: {
          4: 'Three'
        }
      }
    };
    
    let obj4 = {
      a: 'somestring',
      b: 42,
      c: {
        1: 'one',
        2: {
          3: 'Three'
        }
      }
    };
    console.log("obj3 == obj4 : ");
    
    console.log(deepEqual(obj3, obj4));
    let obj = {
      name: {
        gender: "F"
      },
      age: 20
    };
    
    console.log(deepEqual(obj, {
      name: {
        gender: "F"
      },
      age: 20
    }));
    console.log('null == obj3');
    console.log(deepEqual(null, obj3));
    console.log('5 == obj3');
    console.log(deepEqual(5, obj3));
    console.log('null == null');
    console.log(deepEqual(null, null));
    console.log('10 == 5');
    console.log(deepEqual(10, 5));
    console.log(`10 == '10'`);
    console.log(deepEqual(10, '10'));

    老实说,我更喜欢@Andrew Ridgway 的解决方案。它非常简单优雅。

    但是,我确实清理了我正在使用的函数以避免使用全局变量。

    这是同一问题的另一种解决方案,虽然有点复杂。

    我愿意接受进一步的建议。谢谢!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-01-07
      • 2016-01-06
      • 1970-01-01
      • 1970-01-01
      • 2020-09-03
      • 2016-08-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多