“()”在函数调用中的含义是什么?

19

现在,我通常像这样使用 () 调用一个不需要参数的函数:

myFunction(); //there's empty parens

除了在使用jQuery时我可以逃避这个问题之外:

$('#foo').bind('click', myFunction); //no parens

好的。但最近我在SO上看到了这个评论(链接)

"考虑使用setTimeout(monitor, 100);而不是setTimeout('monitor()', 100);。Eval很危险 :)"

天啊!我们真的正在对一个字符串进行eval()吗?我想我确实不太理解“调用”函数的重要性和影响。关于调用和引用函数的真正规则是什么?

4个回答

44

在JavaScript中,函数是一等公民的对象。这意味着您可以将函数作为参数传递给函数,或将其作为变量处理。

假设我们正在谈论一个名为hello的函数,

function hello() {
    alert('yo');
}

当我们简单地编写代码时
hello

我们指的是不执行其内容的函数。但是当我们在函数名称后面添加括号()时,

hello()

那么我们实际上是在调用函数,该函数将在屏幕上弹出“yo”的警报。

jQuery中的bind方法接受事件类型(字符串)和一个函数作为参数。在您的示例中,您正在传递类型 - “click”和实际函数作为参数。

你看过电影《盗梦空间》吗?考虑这个牵强的例子,它可能会让事情变得更清晰。由于JavaScript中的函数是一等公民对象,因此我们可以从函数内部传递和返回函数。因此,让我们创建一个在调用时返回函数的函数,并且返回的函数在调用时也返回另一个函数。

function reality() {
    return function() {
        return function() {
            alert('in a Limbo');
        }
    };
}

这里,reality是一个函数,reality()也是一个函数,reality()()同样也是一个函数。但是reality()()()不是一个函数,只是undefined,因为我们没有从最内层的函数返回一个函数(我们什么也没有返回)。
因此,对于reality函数示例,您可以将以下任何一个传递给jQuery的bind。
$('#foo').bind('click', reality);
$('#foo').bind('click', reality());
$('#foo').bind('click', reality()());

2
耶,我现在保持着使用“function”这个词语的记录,是所有 Stack Overflow 回答中使用次数最多的 - 27 次。 - Anurag
3
加一分,因为提到了《盗梦空间》(当然也因为解释得好)。 - user395760
2
var f = function(){ return f; } 这个例子怎么样?它允许你无限次地输入 f()()()()()()()()()()()()()()(),非常酷。 - Callum Rogers
@Avinash - 不需要将每个调用都包裹在括号中。 - Anurag
你的演讲非常详细和有信息量,所有提供的信息都是正确的,但我必须说实际上问题的答案是错误的。我相信你错过了在'monitor()'周围加单引号的部分,这表明它不是一个函数或被函数调用的函数引用(就像你的inception示例一样),而是一个字符串,稍后将被eval为要运行的函数。 - WORMSS

7
您的jQuery bind示例类似于setTimeout(monitor, 100);,您正在将函数对象的引用作为参数传递。
应避免向setTimeout/setInterval方法传递字符串,原因与在不必要时避免使用evalFunction构造函数相同。
作为字符串传递的代码将在全局执行上下文中评估和运行,这可能会导致“作用域问题”,请考虑以下示例:
// a global function
var f = function () {
  alert('global');
};

(function () {
  // a local function
  var f = function() {
    alert('local');
  };

  setTimeout('f()', 100); // will alert "global"
  setTimeout(f, 100);     // will alert "local"
})();

上面示例中的第一个setTimeout调用将执行全局f函数,因为评估的代码无法访问匿名函数的本地词法作用域。
如果将函数对象的引用传递给setTimeout方法(如第二个setTimeout调用中所示),则将在当前作用域中引用的完全相同的函数被执行。

3
在您的jQuery示例中,与第二个setTimeout示例不同的是,您没有执行相同的操作 - 在您的代码中,您传递了该函数并绑定了click事件。
在第一个setTimout示例中,monitor函数被直接传入并可以直接调用,在第二个示例中,字符串monitor()被传入并需要使用eval进行评估。
当传递函数时,使用函数名称。当调用它时,需要使用()。
Eval将调用传入的内容,因此成功执行函数调用需要使用()。

那么这个注释是错误的吗?他真的应该传递 setTimeout(monitor(), 100); //注意括号 吗? - Isaac Lubow
@Isaac Lubow:不,代码是正确的——只是你在每个代码片段中做了不同的事情。这两个例子并不相同。 - Sasha Chedygov
如果你想让 monitor 在 100 毫秒后执行,你需要这样做:setTimeout(monitor(), 100); //注意括号的使用? - Isaac Lubow
@Issac Lubow - setTimeout(monitor(), 100); 会立即执行 monitor 函数。setTimeout("monitor()", 100); 将在 100 毫秒后评估 monitor()。注意第二个例子中的 " - Oded
@Isaac Lubow - 我的意思只是在setTimeout的调用中简单地使用monitor()(不带引号)将立即调用该函数,然后将其返回值传递给setTimeout - Oded
我调试了一下,现在我明白了。我的困惑是由于代码中其他部分的原因,导致它似乎没有执行。谢谢! - Isaac Lubow

2
首先,“()”并不是函数名的一部分,它是用于进行函数调用的语法。
首先,你可以通过函数声明将函数绑定到标识符名称上:
function x() {
    return "blah";
}

你可以使用函数声明或函数表达式来定义一个函数:

var x = function() {
    return "blah";
};

现在,每当您想运行此函数时,您需要使用括号:
x();

setTimeout函数接受一个函数标识符或字符串作为第一个参数...

setTimeout(x, 1000);
setTimeout("x()", 1000);

如果您提供一个标识符,那么它将被调用为函数。如果您提供一个字符串,则会被评估(执行)。
首选第一种方法(提供标识符)...

如果您提供了一个标识符,它将被调用为函数。如果您提供了一个字符串,则将被评估(执行)。能否解释一下这两者之间的区别? - Isaac Lubow
@Isaac 如果您提供一个字符串,那么这个字符串将被执行为 JavaScript 代码。例如:setTimeout('x = 3;', 100)。正如您所看到的,这里没有调用任何函数。字符串 'x = 3;' 被评估为赋值表达式。另一方面,如果您提供一个标识符名称,则该名称必须指向一个函数对象(然后将调用该函数)。 - Šime Vidas

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