如何在 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: fooError...at foo

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

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

请注意,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 = () =&gt; /* do something */ ; 为函数分配名称,let g = (() =&gt; () =&gt; /* do something */ )(); 没有。 这就是我最终所做的。我宁愿让我的类继承库生成的匿名类是匿名的,而不是让我的类继承库的用户不需要考虑的内部变量的名称,我希望最终用户看到一个名称仅当他们为班级提供名称时。 (github.com/trusktr/lowclass) @trusktr:他们提出的一个例外可能适合您的目的,然后:如果您将函数分配给 existing 对象上的属性,则未设置名称:@ 987654358@ 没有给函数命名。这个was intentional防止信息泄露。 为了完整起见:为了防止名称被推断,您所要做的就是避免任何导致 NamedEvaluation 的生产(在规范文本中,将鼠标悬停在标题上并单击“参考 (18)”以查看它的使用位置)。像const sayHello = (0, (name) =&gt; console.log(`$name says hello`)); 这样的东西就可以了。【参考方案2】:

没有。箭头语法是匿名函数的简写形式。匿名函数是匿名的。

命名函数使用function 关键字定义。

【讨论】:

正如@DenysSéguret 所述,它不是匿名函数的简写形式。你会得到一个硬绑定函数。你可以在这里看到转译结果bit.do/es2015-arrow 以更好地理解这意味着什么...... 这个答案的第一个词是正确的对于所提出的问题,因为 OP 排除了您命名箭头函数的方式。但答案的其余部分根本不正确。查看使用abstract SetFunctionName operation 的规范。 let a = () =&gt; ; 根据规范和现代引擎定义了一个 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) =&gt; …; ,它完全等同于您答案中的代码,但已经在 ES6 中工作。无需使用.bind 顺便说一句,我认为您将“命名函数”与“(实例)方法”和“范围”与“this context”混淆了 @tdecs:是的,你可以;约尔格错了。 let a = () =&gt; ; 定义了一个名为 anamed 箭头函数。 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', ()=&gt;),它可以保持 100% 正确的箭头函数语义,或者更好的是一个用于“命名箭头函数语法”的 babel 插件:fn yourName() =&gt; 。这些中的任何一个都将编译为上面的代码。我认为命名箭头会如此 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 数据格式及作用域)

es6的箭头函数转换为普通函数,以及将await/async函数转为普通函数

如何使用箭头函数(公共类字段)作为类方法?

我应该在 Angular 的类中将方法写成箭头函数吗

ES6 箭头函数是不是与 Angular 不兼容?

JavaScript ES6箭头函数指南