如何在JavaScript中比较两个函数

70

如何在JavaScript中比较两个函数? 我不是在谈论内部引用。请说。

var a = function(){return 1;};
var b = function(){return 1;};

可以比较ab吗?


由于语法错误,JavaScript 甚至无法解析...但是,您所说的“compare”是什么意思?不同的浏览器对函数对象上的“toString”支持不同,因此在您的情况下可能有效。(但在这种情况下无法解析,因此未分配任何内容给 ab)。我还认为有一些库可以“反射”JavaScript[在脚本块中]。 - user166390
由于在JS中一切都是对象,您可能可以尝试查找类似于对象比较的东西(https://dev59.com/o3NA5IYBdhLWcg3wKacx)。 - shershen
6个回答

69
var a = b = function( c ){ return c; };
//here, you can use a === b because they're pointing to the same memory and they're the same type

var a = function( c ){ return c; },
    b = function( c ){ return c; };
//here you can use that byte-saver Andy E used (which is implicitly converting the function to it's body's text as a String),

''+a == ''+b.

//this is the gist of what is happening behind the scences:

a.toString( ) == b.toString( )  

3
请注意,这适用于匿名函数(公平地说,这是OP所要求的)。如果你有 var a = function a(){.... 和 var b = function b(){...(很多人这样做,因为这样你可以在堆栈中获得更有意义的消息),那么就没有办法了。 - gotofritz
1
请注意,对于具有相同代码的两个不同函数(在现实世界中可能会发生在两个类方法中),这也将评估为“true”。 - bitsmanent
2
请注意,此方法对于具有相同语法树但不同间距的函数将失败。 - Wong Jia Hau
1
请注意,如果您的函数引用其包含范围中定义的任何变量,则 toString 相等性检查可能会返回 true,即使函数实际上并不相等。有关示例,请参见 Julian 的 答案 - Alex Ryan

31

闭包意味着在说“比较”时需要非常小心。例如:

function closure( v ) { return function(){return v} };
a = closure('a'); b = closure('b');
[a(), b()]; // ["a", "b"]

// Now, are a and b the same function?
// In one sense they're the same:
a.toString() === b.toString(); // true, the same code
// In another sense they're different:
a() === b(); // false, different answers
// In a third sense even that's not enough:
a2 = closure('a');
a() === a2(); // true
a === a2; // false, not the same object

能够访问函数之外的能力意味着通常情况下比较函数是不可能的。

然而,在实际操作中,您可以使用像Esprima或Acorn这样的Javascript解析库来取得很长的进展。这些库允许您构建一个“抽象语法树”(AST),它是您程序的JSON描述。例如,您的return 1函数的AST看起来像这样:

ast = acorn.parse('return 1', {allowReturnOutsideFunction:true});
console.log( JSON.stringify(ast), null, 2)
{
  "body": [
    {
      "argument": {
        "value": 1,              // <- the 1 in 'return 1'
        "raw": "1",
        "type": "Literal"
      },
      "type": "ReturnStatement" // <- the 'return' in 'return 1'
    }
  ],
  "type": "Program"
}
// Elided for clarity - you don't care about source positions

AST包含了您所需的所有信息,可以进行比较,它是JavaScript函数的数据形式。根据您的需要,可以规范化变量名称、检查闭包、忽略日期等等。
有许多工具和库可帮助简化此过程,但即使如此,这可能仍然需要大量的工作,而且可能并不实际,但大部分情况下是可以做到的。

1
这是一个好问题。我试图在回答中解释,结果让答案变得更糟了,所以我会在这里解释一下 - 在添加/删除事件监听器的情况下,JavaScript正在进行引用检查。它根本不比较函数,而是在问“这是同一个对象吗?”(而函数只是另一种对象)。在JavaScript中,引用可能非常微妙和令人困惑。不幸的是,这似乎是我找到的最清晰的解释:https://dev59.com/tmQo5IYBdhLWcg3wMs0L,否则请搜索“javascript references vs values”,但计划在控制台中进行一些实验。 - Julian de Bhal

8

您可以比较两个可能包含函数引用的变量,以查看它们是否引用相同的函数,但您无法真正比较两个不同的函数以查看它们是否执行相同的操作。

例如,您可以这样做:

function foo() {
    return 1;
}

var a = foo;
var b = foo;

a == b;   // true

但是,您不能可靠地做到这一点:

function foo1() {
    return 1;
}

function foo2() {
    return 1;
}

var a = foo1;
var b = foo2;

a == b;   // false

您可以在这里看到第二个示例:http://jsfiddle.net/jfriend00/SdKsu/ 有些情况下,您可以在函数上使用.toString()运算符,但这只是将您的函数的字面字符串转换与另一个函数进行比较,即使相差很少,也不会起作用。我想不出任何情况下我会推荐这种可靠的比较机制。如果您真的考虑以这种方式做,我会问为什么?您真正想要实现什么并尝试找到更强大的解决问题的方法。

6

函数的toString()方法可以返回该函数的完整声明。您可以修改jfriend00的代码进行测试。

这意味着您可以测试您的函数是否完全相同,包括您在其中添加的空格和换行符。

但首先您需要消除它们名称上的差异。

function foo1() {
    return 1;
}

function foo2() {
    return 1;
}

//Get a string of the function declaration exactly as it was written.
var a = foo1.toString();
var b = foo2.toString();

//Cut out everything before the curly brace.
a = a.substring(a.indexOf("{"));
b = b.substring(b.indexOf("{"));

//a and b are now this string:
//"{
//    return 1;
//}"
alert(a == b); //true.

正如其他人所说,这种方法不可靠,因为只要有一个空格不同就会使比较结果为false。

但是如果你把它用作一种保护措施呢?(“自从我创建函数以来,有人修改了它吗?”)那么你可能真的希望进行严格的比较。


0

使用模板字面量的ES6+干净解决方案:

const fn1 = () => {}
const fn2 = () => {}

console.log(`${fn1}` === `${fn2}`) // true

基本上意味着:

console.log(fn1.toString() === fn2.toString()) // true

-1

将函数转换为字符串,然后替换换行符和空格以进行比较:

let a = function () {
  return 1
};

let b = function () {
  return 1
};

a = a.toString().replace(/\n/g, '').replace(/\s{2}/g, ' ');
b = b.toString().replace(/\n/g, '').replace(/\s{2}/g, ' ');

console.log(a); // 'function () { return 1}'
console.log(b); // 'function () { return 1}'
console.log(a === b); // true

b = function () {
  return 2
};

b = b.toString().replace(/\n/g, '').replace(/\s{2}/g, ' ');

console.log(b); // 'function () { return 2}'
console.log(a === b); // false

b = () => 3;

b = b.toString().replace(/\n/g, '').replace(/\s{2}/g, ' ');

console.log(b); // '() => 3'
console.log(a === b); // false

附言:如果您正在使用ES6,请尝试使用let而不是var


如果这样声明函数 "function a(){return 1;}",它不起作用吗? - Tarek Salah uddin Mahmud
@TarekSalahuddinMahmud 你可以再试一次。我刚刚再次检查了一下,它正在工作。 - Tân
“let”并不总是比“var”更快。它们只是以不同的方式定义变量作用域,但通常效率相当。然而,“const”肯定更快,因为它允许JIST优化。 - Jack G

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接