在 JavaScript 中使用(trailing commas)尾逗号的最佳实践

trailing commas,我这里直接翻译成 尾逗号 了,是在元素列表的最后一项之后插入的逗号。JavaScript 一开始就支持数组字面量中的尾后逗号,随后向对象字面量(ECMAScript 5)中添加了尾后逗号。最近(ECMAScript 2017 也称为ES8),又将其添加到函数参数中。

这似乎是一个小小的变化,但有一些难以察觉的后果。虽然大多数新的语言特性接受这样的形式,但如果你不小心,这个功能可能会给你带来麻烦。

在本指南中,我们将详细介绍尾逗号:从常见的数据类型开始,像数组和对象你可以在其中安全地将尾逗号添加到项目列表的末尾。然后,我们将继续讨论语言结构,例如参数列表、函数调用和解构赋值,在本文的最后,我们将讨论使用尾逗号的注意事项。

在数组中使用尾逗号

你可以安全地在数组中的最后一个表达式后面包含一个尾逗号,如下所示:

const arr = [
  "one",
  "two",
  "three",
];

请注意不要在末尾添加多个逗号,否则你将创建一个 undefined 的元素。例如,以下数组是完全合法的,但包含四个元素:

const arr = [
  "one",
  "two",
  "three",,
];

console.log(arr.length);    // => 4

数组不一定有从 0 开始连续的索引。你可以创建一个包含多个“间隙”的数组——这样的数组称为稀疏数组。例如,以下数组包含六个元素,其中三个是 undefined

const sparseArray = [1,,,4,5,,];

因此,请务必记住, length 属性的值并不总是指示数组中的元素数,你甚至可能有一个没有元素但长度为 1 的数组:

const arr = [,];

console.log(arr.length);    // => 1

然而,在实践中,很少需要使用稀疏数组,如果你这样做了,你现有的代码很可能会像处理包含 undefined 元素的普通数组一样处理它。

在对象中使用尾逗号

与数组类似,你可以在对象的最后一个属性后面加上逗号:

const person = {
  firstName: "John",
  lastName: "Davis",
  age: 30,
}

从 ECMAScript 5 开始,对象文本中的尾逗号是合法的。一些 JavaScript 风格指南,例如由 Airbnb 和 Google 创建的指南,甚至鼓励你养成始终包含尾逗号的习惯,这样以后在对象末尾添加新属性时,就不太可能遇到语法错误。

请注意,与数组不同,你不能创建稀疏对象,尝试这样做会导致语法错误:

const person = {
  firstName: "John",
  ,
  age: 30,
}

// logs:
// => Uncaught SyntaxError: Unexpected token ','

在参数列表和函数调用中使用尾逗号

有时,将函数的参数放在单独的行上很有用,特别是当有一长串参数或你想要容纳描述每个参数的注释时。例如:

function createRectangle(
  w,    // (number) the width of the rectangle
  h     // (number) the height of the rectangle
) { /* ... */ }

随着函数的发展,你可能会发现自己处于需要向函数添加更多参数的情况,但是对于你添加的每个新参数,你必须转到上一行并添加逗号:

function createRectangularPrism(
  w,    // (number) the width
  h,    // (number) the height
  d     // (number) the depth
) { /* ... */ }

即使是有经验的开发人员也并不总是记得在上一行添加逗号,这会导致错误,更糟糕的是 diff 会在该行中显示代码更改,仅仅是因为你后来添加了逗号(稍后会详细介绍)。

幸运的是,ES2017 也使得在函数参数中添加尾逗号是合法的:

function createRectangularPrism(
  w,    // (number) the width
  h,    // (number) the height
  d,    // (number) the depth
) { /* ... */ }

这只是编码样式的更改,不会添加未命名的参数或引起任何其他副作用。

更重要的是,ES2017更新让我们能够在函数调用中参数的末尾使用逗号。一些程序员喜欢将函数调用的每个参数放在自己的行上。如果你是其中之一,那么尾逗号将再次使你免于将来的潜在错误:

createRectangle (
  5,
  10,
)

这段代码使用两个参数调用 createRectangle() 函数。如果以后决定添加第三个参数,则不必编辑任何现有行。同样的规则也适用于类或对象的方法定义,因为它们也是函数:

const myObj = {
  createRectangle(    // defines a method
    w,
    h,
  ) { /* ... */ }
}

在解构赋值语法中使用尾逗号

解构赋值语法允许你快速将数组或对象中的值提取到不同的变量中。解构时,可以在赋值的左侧添加尾逗号。例如,以下代码解构数组:

const numbers  = [10, 20, 30];
const [n1, n2, n3,] = numbers;

console.log(n1);    // => 10

同样,你可以使用解构来“解包”对象的属性:

const car = {
    color: 'red',
    type: 'coupe',
    hp: 500
};

const {color, type, hp,} = car;

console.log(color);    // => red

但是 JSON 对象呢?它们与普通的 JavaScript 对象类似,它们可以使用逗号结尾吗?

在 JSON 中使用尾逗号

JSON 文档格式是在 2000 年代初期引入的。由于 JSON 是基于 JavaScript 的对象语法,并且是在 2009 年引入 ECMAScript 5 之前发明的,因此尾逗号不能在 JSON 中使用(请记住,对象文本中的尾逗号从 ES5 开始是合法的)。

例如,以下代码将抛出一个错误:

JSON.parse('[1, 2, 3, 4, ]');
// => Uncaught SyntaxError: Unexpected token ] in JSON at position 13

这行代码也一样:

JSON.parse('{"foo" : 1, }');
// => Uncaught SyntaxError: Unexpected token } in JSON at position 12

有许多在线工具可以帮助你解决这个问题。例如,你可以利用JSON格式化器自动查找和删除JSON代码中的尾逗号。

模块导入和导出中的尾逗号

在现代 JavaScript 中,创建由称为模块的独立代码块组成的程序是一种常见的做法。正如在经典脚本中向对象添加尾逗号是合法的一样,在模块中导出的最后一项之后使用逗号也是合法的。当你想在以后包含更多导出时,这会派上用场。例如:

// module 1
var foo = 10;
let bar = 20;
const baz = 30;

export {foo, bar, baz, };

此代码使用 export 关键字公开 foobarbaz 变量。这意味着其他模块可以使用 import 语句访问这些变量:

// module 2
import {
  foo,
  bar,
  baz,    // 注意这个逗号, 在导入标识符时是合法的
} from './module1.js'

为什么你应该开始使用尾逗号?

JavaScript 程序员过去常常避免在数组中包含尾逗号,因为早期版本的 Internet Explorer 会抛出错误(即使它从一开始就在 JavaScript 中是合法的)。但情况发生了变化。现在,许多编码样式都建议始终使用尾逗号,这是有充分理由的。

如果你经常在数组、对象或参数/参数列表的末尾添加新项,那么已经有一个尾逗号意味着你不必记住去之前的行添加逗号。

你可能还会发现自己经常剪切和粘贴属性。同样,使用尾逗号可以使重新排序条目变得不那么麻烦,并防止将来出现语法错误。

此外,由于你不需要更改曾经是最后一项的行,因此版本控制系统将产生更清晰的差异。假设你有这个功能:

function myFunction(
  p1,
  p2
) { /* ... */ }

myFunction(
  'arg1',
  'arg2'
);

如果你添加一个名为 p3 的新参数,则 diff 输出将如下所示:

function myFunction(
  p1,
- p2
+ p2, // Change this line to add a comma
+ p3  // Add p3
) { /* ... */ }

myFunction (
  'arg1',
-  'arg2'
+ 'arg2', // Change this line to add a comma
+ 'arg3'  // Add arg3
);

在这里,函数声明中有两个更改,函数调用有两个更改。让我们看看如果你的函数已经有一个尾逗号会发生什么:

function myFunction(
  p1,
  p2,
) { /* ... */ }

myFunction(
  'arg1',
  'arg2',
);

使用尾逗号后,差异输出中只有两个更改:

function myFunction(
  p1,
  p2,
+ p3  // Add p3
) { /* ... */ }

myFunction (
  'arg1',
  'arg2',
+ 'arg3'  // Add arg3
);

本节的要点是,使用尾逗号可以更轻松地向函数添加新参数以及复制/粘贴数组和对象中的属性。它还有助于产生更干净的差异输出。

但是尾逗号并非在所有地方都有效,如果你不小心,使用它们实际上可能会适得其反。

什么时候不使用结尾逗号

你可能认为你也可以在剩余参数语法中使用尾逗号,因为其他各种 JavaScript 构造中都允许尾逗号。但事实并非如此:

function sum(...theArgs,) {    // 注意这个尾逗号
  return theArgs.reduce((previous, current) => {
    return previous + current;
  });
}

console.log(sum(1, 2, 3));
// => Uncaught SyntaxError: parameter after rest parameter

剩余参数参数后使用尾逗号是非法的,即使你在解构语法中使用它也是如此:

const numbers  = [10, 20, 30];
const [n1, ...n2,] = numbers;
// => Uncaught SyntaxError: Rest element must be last element

因此,请记住,尽管在解构语法中使用尾逗号是有效的,但你不能在剩余参数之后使用它。

除了解构语法之外,还有一个地方使用尾逗号可能会给你带来麻烦:函数。请看以下示例:

function myFunction(,) { // ... }    // => Uncaught SyntaxError: Unexpected token ','

myFunction(,);     // => Uncaught SyntaxError: Unexpected token ','

该代码的第一行定义了一个没有参数但有一个逗号的函数,这会导致 SyntaxError。可以没有参数也不加逗号,也可以在参数后面加上逗号。调用函数时也是如此:你不能有一个函数调用,其唯一的参数是一个逗号。

总结

逗号符号的使用在 JavaScript 语言中经历了几次修订,每次修订,都有更多的语言结构增加了对尾部逗号的支持。在本文中,我们研究了尾部逗号在不同结构中的工作方式,包括数组、对象、JSON 对象、参数列表、函数调用以及模块导入和导出。

然后,我们了解了尾逗号在哪里使用是合法的,在哪里不合法。一般来说,当你经常复制/粘贴属性或将新项目添加到列表末尾时,你应该使用结尾逗号。你也可以利用它们来产生更干净的差异输出。但是,请记住,你不应该将它们与剩余参数语法一起使用,并且你不能拥有仅以逗号为参数的函数声明/调用。

上一篇