JavaScript:给定的函数是否为空?

23

让我们进行一次函数调用

function doSomethingAndInvokeCallback(callback){
    // do something
    callback();
}

我可以检查给定的参数是否为函数:if(typeof callback == 'function')

如何判断给定的回调函数是函数且不为空?

例如:

doSomethingAndInvokeCallback(function(){
    //nothing here
})

2
这仍然是一个有趣的问题。 - Sid
9
我认为与其争论这个问题的有效性,我们应该像解数学题一样尝试回答它。虽然毫无意义,但很有趣。 - Sid
嗯,听起来非常有趣。 - Fatih Acet
2
@kay 是的,这是个问题,如果我们能找到解决这种函数什么也不做的方法,那就太好了。 - Marek Sebera
除了更长且难看的正则表达式之外,我想没有其他方法可以在不执行函数的情况下检查它实际上要做什么。 即使您执行它,您也不能总是知道它是否会更改本地变量等。 - pimvdb
显示剩余3条评论
6个回答

10
没有完全可靠的方法来确定函数是否为空,因为JS中有多种类型的函数,一些是用JS实现的,一些是用本地代码实现的,你无法确定传递的函数是否有任何作用。 如果您想将传递的函数限制为非常简单的JS函数,您可以使用其他答案中概述的机制(检查函数的源代码)。 但是,除非处于高度控制的情况,否则我不建议这样做,因为有很多合法的JavaScript方法可以破解它。
我建议您更改函数参数的约定,并让调用者传递null或不传递任何内容(这将使参数为undefined),而不是一个空函数。 然后,他们是否打算调用函数就会非常清楚。 如果他们传递一个空函数而不是null或undefined,则会获得函数接口指定的行为。 调用者可以选择所需的行为,您可以以更安全的方式实现函数。
此外,您问题中的一个主要假设是不正确的。 您不能安全地使用typeof x == "function"来确定某个东西是否为函数,因为在某些旧版本的IE中,对于某些类型的函数,这种方法不可靠。 如果您想学习如何检测某个东西是否为函数,则可以从jQuery这里进行学习(即使您不使用它)。 jQuery有一个它一直在内部使用的函数,称为jQuery.isFunction(),它返回一个布尔值。 它主要用于测试参数以查看是否传递了函数。
在内部,它调用:
Object.prototype.toString.call(o)

然后检查结果。如果结果中有"Function",则测试参数是一个函数。

因此,使用jQuery中使用的相同技术,您可以构建自己的简单的isFunction程序,如下所示:

function isFunction(test) {
    return(Object.prototype.toString.call(test).indexOf("Function") > -1);
}

当然,如果您有jQuery可用,您可以直接使用它的版本:
jQuery.isFunction(o)

当涉及潜在的跨浏览器兼容性问题时,我发现查看其中一个大型库如何解决问题很有帮助,即使您不打算使用该库。您可以确信这些库已经针对许多浏览器进行了审查,因此它们使用的技术是安全的。有时候,您需要取消包装他们可能使用的所有内部例程,以弄清楚他们真正在做什么(这就是这个函数的情况),但这可以为您节省大量的工作。
您可以在此处查看此功能的工作测试平台:http://jsfiddle.net/jfriend00/PKcsM/ 在现代浏览器中,typeof fn === "function",但在旧版本的IE中,一些函数会给出typeof === "object",这可能是jQuery使用此其他方法的原因,该方法在这些旧版本的IE中有效。

我最喜欢这个答案,因为它是最具描述性的。谢谢。 - Marek Sebera
1
+1 但即使在IE7中,Object.prototype.toString.call(alert) === "[object Object]",因此它仍然不是完全可靠的。 - pimvdb
@primvdb - 你说得对,IE7确实有问题。即使是jQuery也会出错。 - jfriend00

7

看起来你可以定义一个函数来检索另一个函数的主体(1)。 我编写了一个小型(非确定性)测试:

http://jsfiddle.net/6qn5P/

Function.prototype.getBody =
  function() {
    // Get content between first { and last }
    var m = this.toString().match(/\{([\s\S]*)\}/m)[1];
    // Strip comments
    return m.replace(/^\s*\/\/.*$/mg,'');
  };

function foo() {
    var a = 1, b = "bar";
    alert(b + a);
    return null;
}

console.log(foo.getBody());
console.log(foo.getBody().length);

这实际上是正确的答案。我需要类似这样的东西来检查jQuery插件回调是否是我定义的默认回调,还是由插件用户定义的自定义回调。这个函数让它成为可能。谢谢。 - ObiHill
我认为依赖于扩展函数等基本元素的原型的解决方案不应被视为正确。这是一种解决方案,但扩展函数原型应始终是最后的选择。 - hurrtz
同意。这个答案的目的是展示一种可能性。这就是为什么它有“非确定性”这个术语 :) - James Sumners

5

一种可能性是将.toString的结果与正则表达式进行匹配,以获取函数体,并进行修剪以检查它是否变为空字符串:

var f = function foo()    { 


};

/^function [^(]*\(\)[ ]*{(.*)}$/.exec(
     f.toString().replace(/\n/g, "")
)[1].trim() === ""; // true

那个丑陋的正则表达式确实考虑到了命名函数周围的空格以及名称和左括号之前的多余空格。像 foo () 中的空格似乎已被删除,因此不需要检查这些内容。

3

可能可以通过.toString()获得此信息:

var blank = function(){};
var f = function(){};
var f2 = function() { return 1; };

f.toString() == blank.toString(); // true
f2.toString() == blank.toString(); // false

但是这种方法非常容易出错:

var blank = function(){};
var f = function(){ }; // extra space!
f.toString() == blank.toString(); // false

您可以对字符串进行一些修改来尝试克服这个问题,但我怀疑这非常依赖于浏览器。如果我是您,我不会在生产环境中尝试这样做。即使您规范了空格,它仍然无法捕捉其他无操作行,包括注释、无用的var语句等。要真正解决这些问题,您可能需要一个完整的标记系统(或一个疯狂的正则表达式)。


2

对于主机函数,您无法这样做,但对于其他函数,您可以相当可靠地执行此操作。

function isEmpty(f) {
  return typeof f === "function" &&
      /^function[^{]*[{]\s*[}]\s*$/.test(
          Function.prototype.toString.call(f));
}

这并不高效,但是主要的解释器在函数中实现了toString,虽然它不适用于某些空函数的一些解释器。

function () { /* nothing here */ }
function () { ; }
function () { return; }

1
在某些实现中,您可以对函数执行toString()操作并获取其内容。尽管它包含注释等内容。
var foo = function(){ /* Comment */ };
alert(foo.toString());

1
NodeJS v0.10.28 和 Google Chrome 浏览器上进行了测试。输出结果为 'function (){ /* Comment */ }'。我认为,在这些环境下无法正常工作。 - gihanchanuka

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