定义函数时正确使用const的方法

350

const在JavaScript中可以设置哪些类型的值?特别是对于函数,有没有限制?以下用法是否正确?尽管它能工作,但出于任何原因,它是否被认为是不良实践?

const doSomething = () => {
   ...
}

所有函数都应该在ES6中以这种方式定义吗?如果是这样的话,似乎没有得到普及。


4
您似乎提出了多个问题:1)“我想知道在 JavaScript 中使用 const 是否有任何限制?”没有。2)“这是有效的吗?”是的。3)“因为任何原因而被认为是糟糕的实践吗?”我想它还没有存在足够长的时间来做任何评价,但我不认为这应该是糟糕的实践。它与 var doSomething = <function def>; 没有太大的区别。4)“所有函数都应该使用 ES6 中的这种方式定义吗?”对我来说有些复杂。我喜欢函数声明。每个人都有自己的偏好。 - Felix Kling
1
在我看来(这是个人观点,不是事实),如果你想禁止重新定义函数,那么这是有道理的。它是否明智,或者是否具有某些功能用途 - 这是有争议的。如果你认为它适合你的使用场景,我不认为有人可以质疑你的决定并认为它是不良实践。 - Mjh
5
我想问题是你想通过 const 实现什么目的。你想防止自己重写函数吗?我假设您知道您的代码不会这样做。您想表达 doSomething 的意图,即它保存一个函数并且不更改其值吗?我认为函数声明也能清晰地表达这个意图。所以,如果你需要在运行时保护代码以防止被覆盖,那就使用 const。否则我看不出有太多好处。当然,如果您主要使用 var foo = function() {};,那么我会使用 const 替代 var - Felix Kling
7
“我会假设你知道你的代码不会这样做。” - 这是一个相当糟糕的论点。否则,使用 const 就没有任何意义了。 - meandre
3
我想强调的是,使用const代替function来定义一个“函数”会降低代码可读性。我在停止4-5年后重新开始使用JS,遇到了一堆用const声明function代码,这让我很烦恼。 - Elijah Lynn
5个回答

456

你所做的没有问题,但是你必须记得函数声明和函数表达式之间的区别。

函数声明,即:

function doSomething () {}

函数会在作用域顶部完全提升(和letconst一样,它们也是块级作用域)。

这意味着以下内容将起作用:

doSomething() // works!
function doSomething() {}
一个函数表达式,即:
[const | let | var] = function () {} (or () =>

创建一个匿名函数 (function () {}),再创建一个变量并将该匿名函数分配给该变量。

因此,在作用域内的通常变量提升规则中,块级作用域变量 (letconst) 不会像 undefined 一样提升到其块级作用域顶部。

这意味着:

if (true) {
    doSomething() // will fail
    const doSomething = function () {}
}

由于doSomething未定义,将无法成功执行(会抛出ReferenceError)。

如果您切换为使用var,则可以获得变量的提升,但它的值将初始化为undefined,因此仍无法使上面的代码块正常工作(这将抛出一个TypeError ,因为在调用时doSomething不是函数)。

就标准实践而言,您应该始终使用适当的工具来完成工作。

Axel Rauschmayer在其关于范围和提升的文章中介绍了ES6语义学:Variables and Scoping in ES6


13
只想补充一下,ES6 类内部使用 const 来保护类名不被重新分配。 - user2342460
8
a function a(){console.log(this);}b const a=_=>{console.log(this);} 之间的一个微妙区别是,如果您像这样调用它 a.call(someVar);,在 a 中,它将打印 someVar,而在 b 中,它将打印 window - user1663023

204
虽然使用const定义函数看起来像个技巧,但它具有一些优势,使它在我看来更好:
  1. 这使得函数成为不变量,因此您不必担心该函数被其他代码更改。

  2. 您可以使用箭头函数语法,这样更短且更清晰。

  3. 使用箭头函数会自动处理this绑定。

使用function的示例:

// define a function
function add(x, y) { return x + y; }

// use it
console.log(add(1, 2)); // 3

// oops, someone mutated your function
add = function (x, y) { return x - y; };

// now this is not what you expected
console.log(add(1, 2)); // -1

使用const的相同示例

// define a function (wow! that is 8 chars shorter)
const add = (x, y) => x + y;

// use it
console.log(add(1, 2)); // 3

// someone tries to mutate the function
add = (x, y) => x - y; // Uncaught TypeError: Assignment to constant variable.
// the intruder fails and your function remains unchanged


125
  1. 不可变性确实是一个真正的好处,但实际上很少有人会覆盖一个函数。
  2. 粗箭头语法不比function f(x, y) {更短,除非你的函数可以成为表达式。 const f = (x, y) => { 是21个字符,比function f(x, y) {多3个字符。
  3. 只有在方法内部(或其他有意义的this的函数内部)定义函数时,保留此绑定才有意义。在顶层脚本中没有意义。
我并不是说你错了,只是你提到的原因并不是非常相关。
- Nakedible
59
传统函数语法的一个优点是,在调试期间它有一个名称。 - user949300
9
我会使用代码检查工具来防止重新绑定函数声明。 - Franklin Yu
2
@Nakedible - 我也更喜欢使用 function,但在你的比较中,你忽略了 function 需要 return,而另一种方法则不需要。(除非我不知道有一个快捷方式?) - ashleedawg
3
"你可以使用更短、更简洁的箭头语法。" - 有时候,如果这会让其他开发人员难以理解代码,那么更短、更简洁并不一定更好。良好的编程不仅仅是编写能够运行的短代码,还包括编写易于维护的代码等其他方面。 - Baba
显示剩余4条评论

86

这个问题被提出已经三年了,但我现在才遇到它。由于这个回答在堆栈中的位置很靠后,请允许我重复一遍:

问:我想知道在JavaScript中使用const关键字设置值的类型有没有限制,特别是函数。这样做是否有效?尽管它确实起作用,但这是否被认为是不良行为?

在观察到一个热衷于使用const语句来定义functions的JavaScript程序员之后,我被激发去做一些研究,即使没有明显的原因/好处。

对于“是否因为任何原因被认为是不良行为?”让我说,我认为,至少在使用function语句方面有一些优点。

在我看来,这主要是一种偏好和风格的问题。上面提出了一些很好的论点,但没有像这篇文章中那样清晰明了:

Constant confusion: why I still use JavaScript function statements,作者是medium.freecodecamp.org的Bill Sourour,JavaScript大师、咨询师和教师。

我敦促每个人都阅读这篇文章,即使你已经做出了决定。

以下是主要内容:

函数声明相对于 [const] 函数表达式有两个明显的优势:

优势 #1: 明确的意图

在一天扫描数千行代码时,能够尽快轻松地理解程序员的意图非常有用。

优势 #2: 声明顺序 == 执行顺序

理想情况下,我希望以预期执行的顺序更或多或少地声明我的代码。

这对我来说是一个绝妙的问题:使用 const 关键字声明的任何值在执行到它之前都是不可访问的。

我刚才描述的强制我们编写看起来上下颠倒的代码。我们必须从最低层函数开始,并逐步向上工作。

我的大脑不是那样运转的。我希望先了解背景再了解详情。

大多数代码都是由人编写的。因此,按照代码执行的顺序,大多数人的理解顺序大致相同。


1
你能稍微详细解释一下意图的清晰度吗?我理解第二点,但据我所知,第一点只是对第二点的(预先?)重申。我可以想到一个情况,即函数具有自己的defaultErrorHandler常量,分配为匿名函数,然后我可以从promise处理程序中调用它。这将使我能够根据需要在函数内部可选地“覆盖”此默认错误处理程序。有些只需要返回错误对象,而其他一些则需要返回http响应,不同级别的详细程度。但代码结构可以是熟悉的模式。 - Jake T.
也许我的想法太复杂了!我正在理解一些新的实践,还没有花太多时间去实现它们。我发现我非常喜欢.then( res.success, res.error ),比起我之前声明匿名函数来调用res.success(value);。有一个.then( ..., defaultErrorHandler)常见模式可能很好,有一个顶层定义的defaultErrorHandler函数,并且可以选择在函数范围内声明一个const defaultErrorHandler = error => { ... } - Jake T.
6
@JakeT。关于“意图的清晰度”,您能否稍微多评论一下?首先,这个声明不是我发表的,而是由freecodecamp.org/Bill Sourour这篇文章的作者发表的。但是,这对我来说非常有道理。如果我读到“function tomorrow()”,那么我立刻知道它是一个函数。但是如果我读到“const tomorrow = () =>”,我必须停下来,花一点时间在我的头脑中解析语法,最终确定,好的,它是一个函数。 - JMichaelTX
1
另外,如果我有一个由别人编写的长脚本,并且我想查看所有函数,我可以快速搜索“function”以找到它们。或者更好的是,我可以编写一个快速的JS正则表达式来提取所有函数。在我看来,“const”语句仅用于不会更改的数据(而不是函数)。 - JMichaelTX
@JMichaelTX - 我明白你所说的阅读 function tomorrow() { }const tomorrow = () => { } 的区别,但这只是习惯于看到其中一种而不是另一种的情况吧?过了一两天后,您应该能够适应发现新模式的能力... 此外,如果您在搜索或正则表达式中需要函数,则将 function 替换为 ) => { 同样好,甚至更好,因为您避免了任何带有 function 一词的注释。 - Jack_Hu

16

使用const有一些非常重要的好处,有人会说应该尽可能地使用它,因为它是多么明确和指示性。就我所知,在JavaScript中,它是最具指示性和可预测性的变量声明之一,并且也是最有用的之一,因为它非常受限制。为什么呢?因为它消除了varlet声明中的一些可能性。

当你读到一个const时,你可以推断出以下所有内容,而不需要扫描其他对该变量的引用:

  • 该值绑定到该变量(虽然其底层对象不是深度不可变的)
  • 它无法在其立即包含的块之外访问
  • 由于时间死区(TDZ)规则,绑定在声明之前永远不会被访问。

以下引用来自一篇文章,主张使用letconst的好处。它还更直接地回答了您关于关键字约束/限制的问题:

使用像 letconst 这样的约束条件是使代码更易于理解的一种强大方式。尽可能在编写代码时积累这些约束条件。限制一段代码可能意味着什么的声明性约束越多,人类在未来阅读、解析和理解代码就会变得越容易和快速。

当然,const 声明比 var 声明有更多的规则:块级作用域、TDZ、声明时赋值、不可重新分配。而 var 语句只表示函数作用域。然而,计算规则并不能提供太多见解。更好的方法是根据复杂性权衡这些规则:该规则增加还是减少了复杂性?在 const 的情况下,块级作用域意味着比函数作用域更窄的范围,TDZ 意味着我们不需要从声明开始向后扫描范围以查找先于声明的使用,并且赋值规则意味着绑定将始终保留相同的引用。

语句受到的约束越多,一段代码就变得越简单。随着我们增加对语句可能意味着什么的约束,代码变得更加可预测。这是为什么静态类型程序通常比动态类型程序更易于阅读的最大原因之一。静态类型对程序编写者施加了很大的限制,但它也对程序的解释方式施加了很大的限制,使其代码更易于理解。

考虑到这些论点,建议您在可能的情况下使用 const,因为它是给我们最少思考可能性的语句。

来源: https://ponyfoo.com/articles/var-let-const


12
这个问题涉及到functionconst的区别,而不是varconst。这个回答本身就表明了在现代Javascript中,使用const定义一个function并不能提高可读性,实际上反而会降低可读性。因为这个回答已经将它与变量混淆了起来,而不是函数。 - Elijah Lynn
现在没有人使用"var"了。那些使用它的人通常是初学者或已经沉睡了20年的人。 - Welcor

9

有一些特殊情况,箭头函数无法解决:

  1. 如果我们需要改变外部API的方法并需要对象的引用。

  2. 如果我们需要使用仅适用于function表达式的特殊关键字: arguments, yield, bind等。 欲了解更多信息: 箭头函数表达式的限制

示例:

我将这个函数指定为Highcharts API中的事件处理程序。 它由库触发,因此this关键字应该匹配特定的对象。

export const handleCrosshairHover = function (proceed, e) {
  const axis = this; // axis object
  proceed.apply(axis, Array.prototype.slice.call(arguments, 1)); // method arguments
};

使用箭头函数时,this会匹配声明作用域,我们将无法访问API对象:

export const handleCrosshairHover = (proceed, e) => {
  const axis = this; // this = undefined
  proceed.apply(axis, Array.prototype.slice.call(arguments, 1)); // compilation error
};


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