如何在 ES2015 中编写命名箭头函数?
Posted
技术标签:
【中文标题】如何在 ES2015 中编写命名箭头函数?【英文标题】:How do I write a named arrow function in ES2015? 【发布时间】:2015-03-14 16:16:25 【问题描述】:我有一个函数,我正在尝试将其转换为 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 中是否有新的方法可以做到这一点?
【问题讨论】:
箭头语法不是不给函数命名吗? 不一定,箭头函数保持词法范围,因此使用简写生成具有词法范围的命名函数(对堆栈跟踪很有用)将非常有用 第二个sn-p有什么问题? 你当然可以在函数体内引用一个指定的名称: var sayHello = (name) => console.log(sayHello); ;在递归函数的情况下,您通常会这样做。不是一个超级有用的例子,但这里有一个函数,如果它没有得到它的参数,它会返回自己: var sayHello = (name) => name?console.log(name+ ' says hello'):sayHello; sayHello()('坦率'); //-> "坦率地说你好" 问题的标题与其内容相比极大地具有误导性,因为您已经排除了命名箭头函数的方式(这是第二个 sn-p)。 【参考方案1】:如何在 ES2015 中编写命名箭头函数?
您按照您在问题中排除的方式进行操作:您将其放在赋值或属性初始化程序的右侧,其中变量或属性名称可以合理地用作 javascript 引擎的名称。没有其他方法可以做到这一点,但这样做是正确的,并且完全被规范所涵盖。
根据规范,这个函数有一个真实的名字,sayHello
:
var sayHello = name =>
console.log(name + ' says hello');
;
这目前在Assignment Operators > Runtime Semantics: Evaluation 中定义,它执行抽象NamedEvalution 操作(当前步骤1.c.i)。 (您可以通过将鼠标悬停在标题中的 NamedEvalution 上并单击“参考”来查看适用的任何地方。)(以前,在 ES2019 之前,Assignment Operators > Runtime Semantics: Evaluation 使用了the abstract SetFunctionName operation,步骤 1.e.iii,但在 ES2019 之后规范抽象已替换为 NamedEvalution。)
类似地,PropertyDefinitionEvaluation 使用 NamedEvalution,因此给这个函数一个真实的名字:
let o =
sayHello: name =>
console.log(`$name says hello`);
;
现代引擎已经为这样的语句设置了函数的内部名称。
例如,在 Chrome、Edge(基于 Chromium,v79 及更高版本)或 Firefox 中,打开 Web 控制台,然后运行此 sn-p:
"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 及更高版本(以及带有实验标志的“Legacy”Edge 13 及更高版本,或“Chromium”Edge 79 及更高版本)上,当你运行它时,你会看到:
foo.name 是:foo 错误 在 foo (http://stacksn-ps.net/js:14:23) 在 http://stacksn-ps.net/js:17:3注意foo.name is: foo
和Error...at foo
。
在 Chrome 50 及更早版本、Firefox 52 及更早版本以及不带实验标志的 Legacy Edge 上,您会看到这一点,因为它们还没有 Function#name
属性:
请注意,foo.name is:
中缺少该名称,但它显示在堆栈跟踪中。只是在函数上实际实现name
property 的优先级低于其他一些 ES2015 特性; Chrome 和 Firefox 现在都有了; Edge 有它在旗帜后面,大概它不会在旗帜后面太久了。
很明显,这个函数我定义了之后才能使用
正确。箭头函数没有函数 declaration 语法,只有函数 expression 语法,并且没有与旧式命名函数表达式中的名称等效的箭头 (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
而不在调用中将 object
的值设为 this
,或者 (0, eval)("code")
,执行eval
,但不像通常那样在当前范围内。)
(感谢 Sebastian Simon 在 cmets 中提出这一点。)
【讨论】:
如何防止名称被设置(如在旧引擎中)? @trusktr:我不明白你为什么要这样做,但如果你想阻止它:不要做上面的事情。例如,let f = () => /* do something */ ;
为函数分配名称,let g = (() => () => /* do something */ )();
没有。
这就是我最终所做的。我宁愿让我的类继承库生成的匿名类是匿名的,而不是让我的类继承库的用户不需要考虑的内部变量的名称,我希望最终用户看到一个名称仅当他们为班级提供名称时。 (github.com/trusktr/lowclass)
@trusktr:他们提出的一个例外可能适合您的目的,然后:如果您将函数分配给 existing 对象上的属性,则未设置名称:@ 987654358@ 没有给函数命名。这个was intentional防止信息泄露。
为了完整起见:为了防止名称被推断,您所要做的就是避免任何导致 NamedEvaluation 的生产(在规范文本中,将鼠标悬停在标题上并单击“参考 (18)”以查看它的使用位置)。像const sayHello = (0, (name) => console.log(`$name says hello`));
这样的东西就可以了。【参考方案2】:
没有。箭头语法是匿名函数的简写形式。匿名函数是匿名的。
命名函数使用function
关键字定义。
【讨论】:
正如@DenysSéguret 所述,它不是匿名函数的简写形式。你会得到一个硬绑定函数。你可以在这里看到转译结果bit.do/es2015-arrow 以更好地理解这意味着什么...... 这个答案的第一个词是正确的对于所提出的问题,因为 OP 排除了您命名箭头函数的方式。但答案的其余部分根本不正确。查看使用abstractSetFunctionName
operation 的规范。 let a = () => ;
根据规范和现代引擎定义了一个 named 函数(名称为 a
)(在其中抛出错误以查看);他们只是还不支持name
属性(但它在规范中,他们最终会到达那里)。
@DenysSéguret:您可以简单地命名它们,它在规范中。您按照 OP 在问题中排除的方式进行操作,这为函数(不仅仅是变量)提供了一个真实的名称。
@T.J.Crowder 感谢您提供的信息,以及您的useful answer。我不知道这个(非常非常奇怪的)功能。
这个答案不正确。下面的@T.J.Crowder 正确回答了这个问题【参考方案3】:
如果通过“命名”,您的意思是您希望设置箭头函数的 .name
属性,那么您很幸运。
如果在赋值表达式的右侧定义了箭头函数,引擎将获取左侧的名称并使用它来设置箭头函数的.name
,例如
var sayHello = (name) =>
console.log(name + ' says hello');
sayHello.name //=== 'sayHello'
话虽如此,您的问题似乎更多的是“我可以使用箭头功能来提升吗?”。恐怕这个问题的答案是“不”。
【讨论】:
在当前版本的chrome中,至少sayHello.name还是"";请注意,与所有函数一样,sayHello 是一个对象,因此您可以根据需要为其定义任意属性。您不能写入“name”,因为它是只读的,但您可以 sayHello.my_name="sayHello";不知道这会给你带来什么,因为你不能在函数体内做/引用它。 我错了:虽然 name 不是直接可变的,但您可以像这样配置它: Object.defineProperty(sayHello,'name',value:'sayHello') "如果在右侧 [..] 上定义了一个箭头函数" 这是什么来源?我在规范中找不到它。只需要引用引用。 @Dtipson:这只是因为现代引擎还没有实现在 ES2015 规范要求的任何地方设置name
属性;他们会做到的。这是 Chrome 合规性列表中的最后一项,甚至 Chrome 50 都没有(Chrome 51 最终会)。 Details。但 OP 明确排除了这样做(奇怪)。
我可以在 toString 中得到这个名字吗?我尝试覆盖它,但后来我不知道如何访问函数体。【参考方案4】:
似乎这在 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 阶段 0 语法。在我的 webpack.config.js 文件中,我更新了 babel 加载器,如下所示:
test: /\.js$/, exclude: /node_modules/, loader: 'babel?stage=0',
【讨论】:
这不完全是一个命名的箭头函数。这是在构造函数中创建实例方法的快捷方式,不知道这里有什么关系? 嗨@Bergi,我不确定这是否是原作者的意图,但我试图创建一个与对象具有相同 this 范围的命名函数。如果我们不使用这种技术,并且我们希望拥有适当的范围,我们必须在类构造函数中将其绑定。this.handleOptionsButtonClick = this.handleOptionsButtonClick.bind(this);
呃,您可以轻松地使用constructor() this.handleOptionsButtonClick = (e) => …;
,它完全等同于您答案中的代码,但已经在 ES6 中工作。无需使用.bind
。
顺便说一句,我认为您将“命名函数”与“(实例)方法”和“范围”与“this
context”混淆了
@tdecs:是的,你可以;约尔格错了。 let a = () => ;
定义了一个名为 a
的 named 箭头函数。 Details.【参考方案5】:
实际上,命名箭头函数的一种方法(至少从 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);
【讨论】:
理论上可以创建一个 babel 宏fn('yourName', ()=>)
,它可以保持 100% 正确的箭头函数语义,或者更好的是一个用于“命名箭头函数语法”的 babel 插件:fn yourName() =>
。这些中的任何一个都将编译为上面的代码。我认为命名箭头会如此 BADA55【参考方案6】:
为了编写命名箭头函数,您可以参考下面的示例,其中我有一个名为 LoginClass 的类,在这个类中我编写了一个 命名箭头函数,命名为 successAuth
类登录类
constructor()
successAuth = (dataArgs)=> //named arow function
【讨论】:
最好在您发布的代码中添加解释作为答案,这样可以帮助访问者理解为什么这是一个好的答案。 这完成了 OP 说他不想做的事情,并且依赖于 this proposal,它仅处于第 2 阶段,甚至可能不会制作 ES2017(但我认为这很可能制作 ES2018,并且编译器已经支持了几个月)。它还有效地复制了几个以前的答案,这没有用。【参考方案7】:正如Jörg 提到的,箭头函数不能命名。
如果您需要一个名称以获得更好的调用堆栈(变量名称,如来自其他答案的 const foo ...
不会出现在调用堆栈中)并且您不想使用函数关键字,请使用对象。
export const baz =
foo(bar)
alert(`I am in a $bar!`);
基本上,导入后从可读性的角度来看会更好:
baz.foo('bar')
// vs
foo('bar') // could look like a local function
【讨论】:
【参考方案8】:这是 ES6
是的,我认为你所追求的是这样的:
const foo = (depth) => console.log("hi i'm Adele")
foo -> // the function itself
foo() -> // "hi i'm Adele"
【讨论】:
我试过这个例子,效果很好。投反对票有什么好的理由吗?这种用法会产生任何不需要的副作用还是我错过了任何重要的一点?我们将不胜感激您帮助澄清我的疑问。 @GaneshAdapa:我预计反对票是因为这表明完全按照 OP 所说的去做他/她不想做的事情,而没有提供任何进一步的信息。【参考方案9】:您可以跳过函数部分和箭头部分来创建函数。示例:
class YourClassNameHere
constructor(age)
this.age = age;
foo()
return "This is a function with name Foo";
bar()
return "This is a function with name bar";
let myVar = new YourClassNameHere(50);
myVar.foo();
【讨论】:
以上是关于如何在 ES2015 中编写命名箭头函数?的主要内容,如果未能解决你的问题,请参考以下文章
小程序——ECMAScript 6(箭头函数 JSON 数据格式及作用域)