如何在 ES2015 中编写命名箭头函数?

IT技术 javascript ecmascript-6 arrow-functions
2021-01-29 20:03:27

我有一个函数,我试图将其转换为ES6 中的新箭头语法它是一个命名函数:

function sayHello(name) {
    console.log(name + ' says hello');
}

有没有办法在没有 var 语句的情况下给它一个名字:

var sayHello = (name) => {
    console.log(name + ' says hello');
}

显然,我只能在定义了这个函数之后才能使用它。类似于以下内容:

sayHello = (name) => {
        console.log(name + ' says hello');
    }

ES6 中是否有一种新方法可以做到这一点

6个回答

如何在 ES2015 中编写命名箭头函数?

你按照你在问题中排除的方式来做:你把它放在赋值或属性初始值设定项的右侧,变量或属性名称可以合理地用作 JavaScript 引擎的名称。没有其他方法可以做到这一点,但这样做是正确的,并且完全包含在规范中。

根据规范,这个函数有一个真实的名字,sayHello

var sayHello = name => {
    console.log(name + ' says hello');
};

这当前在赋值运算符 > 运行时语义:评估定义,它执行抽象的NamedEvalution操作(当前步骤 1.ci)。(您可以通过将鼠标悬停在标题中的 NamedEvalution 上并单击“参考”来查看此适用的任何地方。)(以前,在 ES2019 之前,赋值运算符 > 运行时语义:评估使用了抽象的SetFunctionName操作,步骤 1.e.iii,但是在 ES2019 之后,这个规范抽象被 NamedEvalution 取代了。)

类似地PropertyDefinitionEvaluation使用 NamedEvalution 并因此给这个函数一个真实的名字:

let o = {
    sayHello: name => {
        console.log(`${name} says hello`);
    }
};

现代引擎已经为这样的语句设置了函数的内部名称。

例如,在 Chrome、Edge(基于 Chromium,v79 或更高版本)或 Firefox 中,打开 Web 控制台,然后运行以下代码段:

"use strict";
let foo = () => { throw new Error(); };
console.log("foo.name is: " + foo.name);
try {
    foo();
} catch (e) {
    console.log(e.stack);
}

在 Chrome 51 及更高版本和 Firefox 53 及更高版本(以及带有实验标志的“传统”Edge 13 及更高版本,或“Chromium”Edge 79 及更高版本)上,当您运行它时,您将看到:

foo.name 是: foo
错误
    在 foo (http://stacksnippets.net/js:14:23)
    在 http://stacksnippets.net/js:17:3

注意foo.name is: fooError...at foo

在 Chrome 50 及更早版本、Firefox 52 及更早版本以及没有实验标志的 Legacy Edge 上,您会看到这一点,因为它们还没有该Function#name属性:

foo.name 是: 
错误
    在 foo (http://stacksnippets.net/js:14:23)
    在 http://stacksnippets.net/js:17:3

需要注意的是名字是从丢失foo.name is:,但它在堆栈跟踪中。只是实际在函数上实现name 属性的优先级低于其他 ES2015 特性;Chrome 和 Firefox 现在拥有它;Edge 有它在旗帜后面,大概它不会在旗帜后面太久了。

很明显,我只能在定义之后才能使用这个函数

正确的。箭头函数没有函数声明语法,只有函数表达式语法,并且没有与旧式命名函数表达式 ( var f = function foo() { };) 中的名称等效的箭头所以没有等价于:

console.log(function fact(n) {
    if (n < 0) {
        throw new Error("Not defined for negative numbers");
    }
    return n == 0 ? 1 : n * fact(n - 1);
}(5)); // 120

你必须把它分成两个表达式(我认为你应该这样做)

const fact = n => {
    if (n < 0) {
        throw new Error("Not defined for negative numbers.");
    }
    return n == 0 ? 1 : n * fact(n - 1);
};
console.log(fact(5));

当然,如果你必须把它放在需要单个表达式的地方,你总是可以......使用箭头函数:

console.log((() => {
    const fact = n => {
        if (n < 0) {
            throw new Error("Not defined for negative numbers.");
        }
        return n == 0 ? 1 : n * fact(n - 1);
    };
    return fact(5);
})()); // 120

我不是说这很漂亮,但如果您绝对需要一个单一的表达式包装器,它就可以工作。


旁注:如果您不希望函数从您分配给的标识符中获取其名称怎么办?也就是说,假设你希望example.name"example"在这里吗?

const example = () => {};
console.log(example.name); // "example"

您可以通过使用任何不使用 NamedEvaluation 的表达式来避免它。可能最流行的做这种事情的方法是逗号运算符:

const example = (0, () => {});
//              ^^^−−−−−−−−−^
console.log(example.name); // ""

0你想有可以是任何东西,它的评估,然后扔掉所以0是一个受欢迎的选择。通过逗号运算符传递函数会中断赋值和函数表达式之间的直接链接,从而阻止 NamedEvaluation 提供example函数的名称(这类似于逗号操作符,像其他著名用途(0, object.example)()这就要求object.example 没有做出objectvaluethis的通话中,或者(0, eval)("code"),它确实的eval,但不是在目前的范围,因为它通常会。)

(感谢Sebastian Simon在评论中提出这一点。)

如何防止名称被设置(就像在旧引擎中一样)?
2021-03-16 20:03:27
@trusktr:我不明白你为什么想要,但如果你想阻止它:不要做上面的事情。例如,虽然let f = () => { /* do something */ };为函数分配了一个名称,let g = (() => () => { /* do something */ })();但没有。
2021-03-17 20:03:27
@trusktr:他们提出的一个例外可能适合您的目的,然后:如果您将函数分配给现有对象的属性,则未设置名称:o.foo = () => {};不为函数命名。为了防止信息泄露。
2021-03-18 20:03:27
这就是我最终做的。我宁愿让我的类继承库生成的匿名类是匿名的,也不愿拥有我的类继承库的用户不需要考虑的内部变量名称,并且我希望最终用户看到一个名称仅当他们为类提供名称时。( github.com/trusktr/lowclass )
2021-03-24 20:03:27
只是为了完整性:为了防止名称被推断出来,您所要做的就是避免任何导致NamedEvaluation 的产生(在规范文本中,将鼠标悬停在标题上并单击“参考(18)”以查看位置它被使用)。类似的事情const sayHello = (0, (name) => console.log(`${name} says hello`));会做。
2021-04-08 20:03:27

不。箭头语法是匿名函数的缩写。匿名函数是匿名的。

命名函数是用function关键字定义的

这个答案的第一个词对于提出的问题是正确的因为 OP 排除了您命名箭头函数的方式。但是答案的其余部分是完全不正确的。在规范中查看抽象SetFunctionName操作的使用位置。根据规范和现代引擎let a = () => {};定义命名函数(名称为a)(在其中抛出错误以查看);他们只是不支持该name属性(但它在规范中,他们最终会到达那里)。
2021-03-17 20:03:27
@DenysSéguret:您可以简单地命名它们,它在规范中。您按照 OP 在问题中排除的方式进行操作,这为函数(不仅仅是变量)提供了真实名称。
2021-04-01 20:03:27
这个答案是不正确的。下面的@TJCrowder 正确回答了这个问题
2021-04-04 20:03:27
@TJCrowder 感谢您提供的这些信息,以及您的有用回答我不知道这个(非常非常奇怪的)功能。
2021-04-08 20:03:27
正如@DenysSéguret 所说,不是匿名函数的缩写你会得到一个硬绑定的函数。您可以在此处查看转译结果bit.do/es2015-arrow以更好地理解这意味着什么......
2021-04-09 20:03:27

如果通过“命名”,您的意思是您希望.name设置箭头函数属性,那么您很幸运。

如果在赋值表达式的右侧定义了箭头函数,则引擎将采用左侧的名称并使用它来设置箭头函数的.name,例如

var sayHello = (name) => {
    console.log(name + ' says hello');
}

sayHello.name //=== 'sayHello'

话虽如此,您的问题似乎更像是“我可以得到一个箭头函数来提升吗?”。恐怕这个答案是一个很大的“不”。

@Dtipson:那只是因为现代引擎尚未实现name在 ES2015 规范要求的任何地方设置属性;他们会做到的。这是 Chrome 合规性列表中的最后一件事,甚至 Chrome 50 也没有(Chrome 51 最终会有)。详情但是 OP 特别排除了这样做(奇怪的是)。
2021-03-17 20:03:27
“如果在右侧定义了一个箭头函数 [..]” 这是什么来源?我在规范中找不到它。只需要引用引用。
2021-03-24 20:03:27
我可以在 toString 中获取这个名称吗?我尝试覆盖它,但后来我不知道如何访问函数体。
2021-03-25 20:03:27
至少在当前版本的 chrome 中, sayHello.name 仍然是 ""; 请注意,与所有函数一样,sayHello 是一个对象,因此您可以根据需要为其定义任意属性。您不能写入“name”,因为它是只读的,但您可以 sayHello.my_name="sayHello"; 不知道这会给你带来什么,因为你不能在函数体内执行/引用它。
2021-04-04 20:03:27
我错了:虽然 name 不是直接可变的,但您可以像这样配置它: Object.defineProperty(sayHello,'name',{value:'sayHello'})
2021-04-04 20:03:27

ES7 似乎可以做到这一点:https ://babeljs.io/blog/2015/06/07/react-on-es6-plus#arrow-functions

给出的例子是:

class PostInfo extends React.Component {
  handleOptionsButtonClick = (e) => {
    this.setState({showOptionsModal: true});
  }
}

ES6 箭头函数的主体与围绕它们的代码共享相同的词法 this,由于 ES7 属性初始值设定项的作用域方式,这会得到我们想要的结果。

请注意,要使用 babel 实现这一点,我需要启用最具实验性的 ES7 stage 0 语法。在我的 webpack.config.js 文件中,我像这样更新了 babel 加载器:

{test: /\.js$/, exclude: /node_modules/, loader: 'babel?stage=0'},
这不完全是一个命名的箭头函数。这是在构造函数中创建实例方法的快捷方式,我想知道它在这里有什么关系?
2021-03-12 20:03:27
@tdecs:是的,你可以;约尔格错了。let a = () => {};定义了一个名为 的命名箭头函数a详情
2021-03-22 20:03:27
呃,您可以轻松使用constructor() { this.handleOptionsButtonClick = (e) => {…}; }它,它与答案中的代码完全等效,但已经在 ES6 中有效。无需使用.bind.
2021-04-06 20:03:27
顺便说一句,我认为您将“命名函数”与“(实例)方法”和“范围”与“this上下文”混淆了
2021-04-06 20:03:27
嗨@Bergi,我不确定这是否是原作者的意图,但我试图创建一个与对象具有相同范围的命名函数。如果我们不使用这种技术,并且我们想要拥有适当的范围,我们必须在类构造函数中绑定它。this.handleOptionsButtonClick = this.handleOptionsButtonClick.bind(this);
2021-04-07 20:03:27

实际上,命名箭头函数的一种方法(至少从 chrome 77 开始......)是这样做的:

"use strict";
let fn_names = {};
fn_names.foo = () => { throw new Error(); };
console.log("foo.name is: " + foo.name);
try {
  foo();
} catch (e) {
  console.log(e.stack);
}

在此处输入图片说明

理论上可以创建一个通天的宏fn('yourName', ()=>{}),这可能保持100%的正确方向功能的语义,或更好,但对于“命名的箭头函数的语法”通天插件:fn yourName() => {}其中任何一个都会编译为上面的代码。我认为命名箭头会如此 BADA55
2021-03-29 20:03:27