【问题标题】:In Javascript, when is it necessary to assign a named function to a variable?在 Javascript 中,何时需要将命名函数分配给变量?
【发布时间】:2015-11-10 09:47:16
【问题描述】:

在 Babel JS (http://babeljs.io/repl/) 的在线 REPL 中,当我输入:

let a = (x) => x+1

它将被转译为:

"use strict";

var a = function a(x) {
  return x + 1;
};

这里的var a = function a(x) 让我有点困惑,因为据我所知,var a = function(x)function a(x) 就足够了。

是否有人知道何时以及为什么需要将命名函数分配给变量?

【问题讨论】:

  • 这不是必需的,但如果函数没有名称,它不会在某些浏览器的堆栈跟踪中显示为“函数 a”。因此命名函数有助于简化调试。否则它们会像“匿名函数”一样出现。
  • @Shilly 还有另一个区别 - 命名函数将其名称暴露给自己的范围。
  • @Alnitak:错了。仅当它是函数定义而不是函数表达式时,它才会将其名称暴露给范围(并使用提升)。这是一个(命名的)函数表达式,它只能通过变量(在这种情况下具有相同的名称)访问。但是考虑到 var 被称为x 的情况,你可以用x() 调用函数a,因为a() 是未定义的基本上它自己的行上的函数定义function lol(){} 将与var lol = function lol(){} 相同跨度>
  • @Zorgatone 我认为我们达成了激烈的协议 - 我没有看到你写的任何内容不同意我的断言,即命名函数的名称在函数自己的范围内可用。
  • 我觉得 cmets 和答案中的噪音太大了。所以这里是 tl;dr: 1) 箭头函数是表达式,而不是声明,因此我们需要使用函数表达式。 2) 在 ES6 中,函数名有时是从分配给它的变量中推断出来的,因此使用了 named 函数表达式。理想情况下,可以分配给a.name = 'a',但并非所有浏览器都支持这一点。

标签: javascript ecmascript-6


【解决方案1】:

这里确实有两个不同的问题:

  1. 定义或表达函数的不同方式有什么区别?
  2. 为什么let a = (x) => x + 1 会以这种方式转译?

为了回答 (2),我们需要了解 (1)——这已在 SO 和其他地方进行了广泛讨论。


问题 1

让我们来看看你提到的三个替代方案:

函数声明

function a(x) { ... }

在语法上,这些必须总是function (reference) 开头。它们在解析时被提升并在本地范围内创建一个命名函数。

(匿名)函数表达式

var a = function (x) { ... }

var a 本身将在解析时被提升,但在运行时执行此行之前它将是 undefined

命名函数表达式

var a = function a(x) { ... }

虽然语法使它看起来像是对函数声明的赋值,但这实际上只是一个带有名称的函数表达式。我觉得这很令人困惑,但这就是语法。

函数声明函数表达式之间最大的区别。通过声明,您可以:

a(1);
function a(x) { return x + 1; }

虽然尝试使用 函数表达式命名匿名)会导致错误。


问题 2

  1. 为什么让 a = (x) => x + 1 以这种方式转译?

我们将箭头函数(x) => x + 1 分配给具有let 的块范围变量,因此我们应该期望a 直到在运行时执行此行之后才定义。这应该是一个函数表达式,而不是一个函数声明

最后,为什么let a = (x) => x + 1 被转换为命名函数表达式而不是匿名函数表达式?有什么不同?正如 Alnitak 和其他人指出的那样:

  • 函数名称出现在调试器中,这很有帮助。
  • 命名函数定义内部的作用域引用了函数本身。这允许递归和访问包含函数的属性。

所以命名函数表达式有一些很好的属性,匿名函数表达式没有。但实际上似乎对这里应该发生的事情存在分歧。根据MDN

箭头函数总是匿名的

this answerWhy use named function expressions? 说:

“[从 ES6 开始] 许多“匿名”函数表达式创建具有名称的函数,而这早于各种现代 JavaScript 引擎,它们非常聪明地从上下文推断名称......这在整个规范中散布"

其他参考资料:

我发现解决这个问题的最佳方法是使用Babel REPL

【讨论】:

  • 我看不出你所做的区别 - 你提出的 Babel 输出的唯一区别是函数表达式中缺少函数名,这对 outer 范围 - 它只在 函数范围内有所不同。
  • 啊,我明白你的意思了——你对If这个词的使用让你感到困惑,因为你所描述的(几乎)是 Babel 实际所做的。更相关的是 If Babel 使用函数定义而不是函数表达式,那么您引用的代码不会产生错误,而实际上它应该产生错误。
  • 我还认为,这样做并不是为了“安全”——将 ES2015 lambda 转换为函数表达式只是 正确 的做法。
  • 哦,提升发生在 parse 时间,而不是执行时间。
  • 我觉得转译成var a = function a(){}也会报错,看这个JsFiddle(jsfiddle.net/9abuc116)
【解决方案2】:

如果你写:

function a(x) { }

然后函数被提升到封闭范围的顶部,a 在整个范围内的解析时立即可用。

但是,当你写的时候:

var a = function a(x) { }

那么var a 在实际执行此行之前,在封闭范围内不会有定义的值

但是,该函数中,不同a将作为对函数本身的本地范围引用存在。

通过使用let a = function ... 构造,Babel 与后一种形式更加一致,确保在运行时将a 分配给(命名的)函数表达式,而不是使用解析时间函数声明

【讨论】:

  • 那么var a = function (x) {}呢?
  • 这与我的第二个示例相同,除了该函数现在没有对自身的范围内引用 - 只有在外部分配给它的 a .
  • 并且调试器不会在调用堆栈中显示函数名,因为该函数是匿名的
【解决方案3】:

看来这是符合标准的(12.14.4):

AssignmentExpression[In, Yield] : LeftHandSideExpression[?Yield] = AssignmentExpression[?In, ?Yield]

1.如果 LeftHandSideExpression 既不是 ObjectLiteral 也不是 ArrayLiteral,则
一种。令 lref 为评估 LeftHandSideExpression 的结果。
湾。 ReturnIfAbrupt(lref).
C。令 rref 为评估 AssignmentExpression 的结果。
d。设 rval 为 GetValue(rref)。
e.如果 LeftHandSideExpression 的 IsAnonymousFunctionDefinition(AssignmentExpression) 和 IsIdentifierRef 都为真,则
I.  让 hasNameProperty 为 HasOwnProperty(rval, "name")。
二、 ReturnIfAbrupt(hasNameProperty)。
三、如果 hasNameProperty 为 false,执行 SetFunctionName(rval, GetReferencedName(lref))

因此,每当评估将 未命名函数表达式 分配给 命名标识符 时,函数name 应设置为标识符名称。

Babel 遵循这个过程,并生成一个兼容的 ES5 实现。
Chrome(v46.0.2490.71,V8 引擎...)不遵循此过程,在这种情况下,name 等于 ''


至于问题本身……

在 Javascript 中,何时需要将命名函数分配给变量?

答案是从不。由开发人员决定是否/何时使用命名函数。该决定归结为对名称的特定需求(例如“字符串化”函数时)或调试需求(更好的堆栈跟踪......)。

【讨论】:

  • 我不同意“从不”。递归呢?
  • @zeroflagL - 无法在 cmets 中发布代码,但... jsfiddle.
  • 您的示例实际上是错误的,因为 iefe 的返回值(无论值是什么)都不算作 anonymousFunctionDefinition。 Babel 确实正确且完整地实现了这一点。
  • @Bergi - 有趣。我误解了 IsAnonymousFunctionDefinition(AssignmentExpression),但你是对的。 AssignmentExpression解析,但未评估。谢谢!
  • 这个例子当然有效。但我可以将函数分配给另一个变量,并为a 分配一个新值。如果函数没有名称来调用自己,它就会中断。
猜你喜欢
  • 1970-01-01
  • 2013-01-21
  • 2016-12-02
  • 1970-01-01
  • 2021-11-15
  • 1970-01-01
  • 1970-01-01
  • 2018-05-11
  • 1970-01-01
相关资源
最近更新 更多