ES6中使用箭头函数和普通函数创建顶级函数有什么优缺点?

28
ES6/ES2015中创建顶层函数的不同方式有哪些优缺点?或者说这只是品味/样式指南等方面的问题吗?

选项1:

function square(n) {
   return n * n;
}

选择2:

var square = function(n) {
  return n * n;
};

选项3:

var square = (n) => {
   return n * n;
};

选项 4:

const square = (n) => {
   return n * n;
};

2个回答

29

注意:我已将此贴发布为社区维基,我们可以一起添加到列表中,澄清等等。 请勿提供个人意见保持客观。


这只是品味/样式指南等问题吗?当然,样式会有很大影响,但我们可以就选项的功能和运行时特性进行一些客观的观察,以决定哪种适用于特定用例。

Option 1:

function square(n) {
   return n * n;
}

  • Hoisted(因为它是函数声明)。
  • 在 ES2015(ES6)之前,只能在全局范围或函数顶层有效;ES2015+允许在控制流语句中使用,但规则很复杂。
  • 稍后可以通过square = ...(或稍后的函数声明)进行覆盖。
  • 创建一个对象并将其分配给square.prototype,即使我们不打算将其用作构造函数。(注意:V8团队的成员told我,至少在使用该属性的值之前,V8并不会执行此操作。其他引擎可能会执行类似的操作。所以这并不重要。)
  • 尝试将其用作构造函数(new square)将起作用,但可能不会做程序员期望的事情:新操作的结果将是一个使用square.prototype作为其原型的对象(并且从n * n返回的函数值被丢弃)。
  • 如果在全局范围内,则创建一个全局对象上的属性(因此是全局),因为它是函数声明。
  • 如果在函数内部使用this,它将由函数的调用方式确定,因为它是“普通”函数。

Option 2:

var square = function(n) {
  return n * n;
};
  • 不会被提升(因为它是一个表达式),在控制流程期间创建。
  • 直到ES2015,由于它是一个匿名函数表达式,该函数没有名称。在ES2015+中,名称来自变量的名称 (浏览器支持可能会滞后一些,似乎在ES2015支持优先级列表上排名较低)
  • 可以通过square = ...稍后进行覆盖。
  • 创建一个对象并将其分配给square.prototype,即使我们不打算将其用作构造函数。 (注意:同样,至少在V8中,直到属性被使用之前/除非属性被使用,否则不会执行此操作。)
  • 尝试将其用作构造函数(new square)将起作用,但可能不会做程序员预期的事情(请参见有关函数声明的注释)。
  • 如果在全局范围内,则创建全局对象的属性(因此是旧式的var变量)。
  • 如果在函数内使用了this,它将根据函数的调用方式确定,因为它是一个“正常”的函数。

Option 2.5: (I've added this one)

var square = function square(n) {
  return n * n;
};
与选项2完全相同,只是在ES5及之前的版本中,函数具有一个真实的名称(square)。 (请注意,名称不一定与变量名相同,尽管在此示例中它是相同的。)(在IE8及其早期版本中存在错误,会创建两个函数而不仅仅是一个; 详细信息请参见this blog post由T.J. Crowder [本答案的主要作者]编写。)

Option 3:

var square = (n) => {
   return n * n;
};

也可以写成:

var square = n => n * n;

  • 不会被提升(因为它是一个表达式),在控制流程中创建。
  • 函数名派生自变量名 (浏览器支持可能滞后一些,似乎在ES2015支持优先级列表上排名较低)
  • 可以通过 square = ... 被后续覆盖。
  • 不会创建对象并将其分配给 square.prototype(尽管JavaScript引擎可能已经避免了这种情况。)
  • 试图将其用作构造函数(new square)将失败,并显示有信息的错误(TypeError:square不是构造函数)。
  • 没有 arguments (但如果需要 arguments 功能,则可以使用rest参数替代它)。
  • 根据规范,在调用它时需要设置更少的内容,因为它没有自己的 this 并且没有 arguments。但是,现代JavaScript引擎已经优化掉了创建 arguments(如果您不使用它),而设置 this 不太可能是一个显著的成本。
  • 如果在全局范围内,则在全局对象上创建属性(因此是全局变量),因为它是旧式的 var 变量。
  • 因为它是箭头函数,所以如果在函数内部使用 this,它将使用与定义函数的代码相同的 this,因为箭头函数会封闭 this(而不是通过调用设置它)。

Option 4:

const square = (n) => {
   return n * n;
};

也可以写成:

const square = n => n * n;

  • 未提升,控制流程期间创建
  • 函数名称来源于变量名称 (浏览器支持可能滞后一些,似乎在ES2015支持优先级列表上排名较低)
  • 不能 通过square = ...稍后覆盖
  • 不会 创建对象并将其分配给 square.prototype
  • 尝试将其用作构造函数(new square)将失败,并显示一个信息性错误(TypeError: square is not a constructor)。
  • 没有arguments (请参阅Option 3的注释)。
  • 按规范,在调用它时需要设置更少的东西(请参阅Option 3的注释)。
  • 如果在全局范围内,则不会 在全局对象上创建属性(但仍然创建全局),因为它是ES2015+ const
  • 因为它是箭头函数, 如果在函数内部使用了 this,它会使用定义该函数的代码中相同的 this,因为箭头函数 闭合 this(而不是通过调用设置它)。

Option 5: (I've added this one)

let square = (n) => {
   return n * n;
};

也可以写成:

let square = n => n * n;

与选项4完全相同,只是后面可以通过square = ...进行覆盖。


1
既然这是es2015+,那么加上 export default function 怎么样? - cswl
1
@cswl:这与第一个完全相同,只是它被导出了。 - Bergi

-1

ES6箭头函数与旧函数的两个主要区别是代码长度(箭头函数减少了您的样板代码,我认为更优雅,因为您甚至可以省略返回关键字)以及这些函数与函数调用上下文的工作方式。

我会将函数写成const square = n => n*n;。但一般来说,在ES6/ES2015中创建顶级函数没有所谓的“首选方法”。


好的,我猜这种问题更适合非正式聊天,而不是stackoverflow,但还是谢谢你的回复,它确实有帮助,至少现在我知道这只是基于个人观点的 :) - Kasper
除非你已经了解了JS函数的上下文和作用域,否则请阅读我在答案中添加的文章。它可能会有用 ;) - Šimon Rozsíval
@ŠimonRozsíval,我认为这个问题非常有价值,而你的帖子并不像一个答案。我会给你点个踩,并且标记它不是一个答案。 - AmerllicA

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