了解JavaScript函数表达式和函数声明,但这是什么?命名函数表达式?

31
我知道函数声明和表达式的区别,但我遇到了涉及函数名称的代码,并想了解运行该代码时会发生什么:

可能是重复问题:
JavaScript: var functionName = function() {} vs function functionName() {}
JavaScript 中函数表达式与声明的区别是什么?

var abc = function def() {
    console.log("Wait! What??");
}

我知道这不是一种JavaScript的方式,但只是想了解几件事情:
  1. abc会发生什么?为什么它能被调用而def不能,为什么?
  2. 这是一个函数声明还是表达式?
  3. defundefined - 为什么?如果应该是这样的,那么是否存在内存泄漏?
  4. 为什么abc.prototype是函数def
谢谢

1
这是一个命名函数表达式。在 函数内部 使用 def 可以引用自身。但要注意浏览器的怪异行为(IE)。 - Yoshi
1
你可能还没有阅读过这些问题及其答案:https://dev59.com/AnM_5IYBdhLWcg3w-4nw 和 https://dev59.com/s3RC5IYBdhLWcg3wUvQS - Alvin Wong
1
http://kangax.github.com/nfe/ 应该能为您提供所有答案。 - Bergi
@AlvinWong,谢谢。我看了那个,但没有看到这个第二个问题的精彩答案 - https://dev59.com/s3RC5IYBdhLWcg3wUvQS#338053。因为得票最多和被接受的答案没有解释那个。 - Om Shankar
1
我不认为这个问题是一个重复问题,因为它特别询问了变量名和函数名不同的情况(尽管它是由其他问题中的一个答案回答的)。 - Jack
1
这不是重复的问题。添加到“可能重复”的问题是关于声明和表达式的差异。这不是这里所问的。请投票重新打开。 - Om Shankar
5个回答

11

abc会发生什么?

它包含一个函数对象。如果你没有对它做任何操作,它将被垃圾回收。

为什么它有效?

为什么不呢?什么是“有效”的?

abc可以调用但def不能,为什么?

这只是从外部看来是这样,并且在IE中也不是这样的。请见下文。

这是一个函数声明还是表达式?

这是一个函数表达式。你可以很容易地看到它是一个赋值表达式的一部分;声明总是需要在顶层(函数或全局代码)。

为什么def未定义?

只有在外部才是这样的。函数表达式不会创建变量。"def"是该函数的名称,在函数内部也是对该函数的引用。这允许递归,例如,而不使用任何外部变量。

var abc = function def() {
    def === abc; // true
    def.name; // "def"
}
abc();
def; // undefined

如果有需要的话,会有内存泄漏吗?

是的,在Internet Explorer中可能会有内存泄漏。该代码会创建两个不同的函数。详情请参见http://kangax.github.com/nfe/#jscript-bugs

为什么abc.prototype是函数def?

它并不是函数def,只是一个对象。也许在您的控制台中显示为此名称,因为它属于名为“def”的函数。


+1 是因为涉及到“实际”实现问题(我知道有一些问题,但没有那么多 :-/)。 - user166390

5

这是一个命名函数表达式。可能的用途是:

var abc = function def() {
    def.test = 'Wait!'; //< sort of a static property
    console.log(def.test+" What??");
}

但是请注意


你的“beware”链接提供了更多信息,让我对差异有了很大的清晰度。因此,我接受了这个答案。 - Om Shankar

2

这是一个函数表达式2

函数表达式也可以有名称,但函数表达式的名称仅在函数体内1可见。(自ECMAScript第五版弃用了arguments.callee,这是编写递归“匿名”函数的唯一简单方法。)

由于它是一个函数表达式,因此名称可能不会1在外部作用域中引入新绑定。

另外,所有函数在JavaScript中都是对象。在f(..)中,f在“调用”(..)之前进行评估;如果f未评估为函数,则会抛出错误。这就是为什么回调(只是函数)可以由变量命名并作为参数传递的原因。

还要检查关于原型的假设/断言:

var abc = function def() {}
abc.prototype === abc // -> false
abc.name              // -> "def"

1 请参考Bergi的回答。

2 如何轻松区分二者?

语法规则允许function ..在它是SourceElement时被解析为函数声明,尽管大多数引擎仍然[错误地]将函数声明解析为Statement。SourceElement产生仅发生在程序的顶级“块”或函数的顶级“块”中。

无论如何,每当有一个function ..出现在需要Expression的地方时,它都将被解析为函数表达式。以下是所有被解析为函数表达式的示例:

// Can only assign values: Statements do not have values!
var f = function () {}        
var g = function gName () {}  // see Bergi's answer

// Can only pass values: Statements do not have values!
doIt(function () {})          // callback / "anonymous function"

// IIFE: Immediately-Invoked Function Expression
;(function () {})()           // standard IIFE
;(function () {} ())          // alternative standard IIFE
;+function () {} ()           // curious way to write an IIFE
// basically you can use any unary operator to turn it into an expression not only
//  + but also - ! ~ and so on which will modify the return value accordingly

重点是在上述每个情况中,function .. 出现在需要表达式的语法位置,因此被解析为函数表达式。(上面行首的分号避免了与自动插入分号机制的“二义性”,这种机制在我更喜欢的无分号风格中是必需的。)
然而,;function () {} ();function f () {} () 都是无效的语法——为什么呢?;-)

2

这是一个有名函数表达式。

与函数声明相反,函数表达式的标识符不是必需的。

您的函数def没有立即调用 - 整个函数被传递给abc,需要明确调用abc()

ES5规范的§13说明了如何构建命名函数表达式。请阅读第三个生成规则,了解如何构建有名函数表达式。

注意:FunctionExpression中的标识符可以从FunctionExpression的FunctionBody内部引用,以允许函数递归调用自身。但是,与FunctionDeclaration不同的是,FunctionExpression中的标识符不能被引用,也不会影响包含FunctionExpression的作用域。

所有最近的浏览器都正确处理此问题,因此您不必担心内存泄漏或其他奇怪的事情(只有旧版IE 8及以下版本处理不正确)。


1

你的例子是一个(命名的)函数表达式。

两者之间的区别在于浏览器加载它们的方式。

函数声明在任何代码执行之前被加载。
函数表达式仅在解释器到达该行代码时才被加载。

这意味着:

abc();
var abc = function() {
    console.log("Wait! What??");
}

不会起作用,但是:

def();
function def() {
    console.log("Wait! What??");
}

意思是:

现在以你的例子为例,你可以访问def,但只能在函数内部访问。

var abc = function def() {
    console.log(def);
}
abc();

// Logs:
//function def() {
//    console.log(def);
//}

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