为什么在做了 20 年 JavaScript 开发者之后,我改用分号

56 阅读6分钟

这是我打算开始写的系列文章的第一篇(希望我会继续写下去),内容是关于我36年程序员生涯以及20多年专业Web开发者生涯的经历。(我现在老了吗?我觉得我老了。)

首先,我们在说什么?如果你是 JavaScript 新手,你可能知道 JavaScript 对语句结尾相当宽容。JavaScript 并非唯一如此。Go、Swift、Kotlin、Ruby、Python、Lua……它们都允许在语句末尾使用分号。

在 JavaScript 中,规范将此称为自动分号插入(Automatic Semicolon Insertion,简称 ASI)。简而言之,如果 JS 发现语法错误,并且认为分号可以修复,它就会自动插入一个分号。

以下是一个例子:

let one = 1 let two = 2 在 JavaScript 开发文化的早期,几乎所有在网上找到的代码示例都带有分号终止符,例如:

let one = 1; let two = 2; 无分号时代 到了 2010 年代初,这种情况开始发生变化。Web 开发作为一种文化,变得越来越复杂,这导致了更多拥有深入知识(包括 ASI 等深奥概念)的领域专家的出现。

许多 JavaScript 开发者开始说:“你看,99% 的情况下分号都是可选的。为什么不干脆省略它们,作为一个标准呢?” 对许多人来说,因为分号看起来是不必要的,所以它们只是一种视觉噪音。

大约在同一时期,为了更容易维护和减少决策,各个团队都在推广使用相同的编码风格。因此,这项新的“标准”开始被纳入到代码检查工具中,这样一来,分号终止符就不再是可选的,而是在特定代码库中被禁止使用了。

无论如何,这其中有很多可能不必要的历史,很多读者可能已经知道了。关键是,为什么我在使用了十多年没有分号的 JavaScript 之后,决定从今年(2025 年)开始放弃它?

99% 不等于 100% JavaScript在大多数情况下不需要分号,但一旦需要,错误就会变得非常不直观。让我们以这段代码示例(演示通过数组解构进行变量交换)为例,这段代码是我从这篇 Medium 文章中借鉴的:

let varOne = 'Hello' let varTwo = 'World'

[varOne, varTwo] = [varTwo, varOne] JavaScript 实际上会将第二条语句解析为:

let varTwo = 'World'[varOne, varTwo] = [varTwo, varOne] 如果我将上面的代码示例粘贴到 VSCode 中,并让 TypeScript 尝试向我解释错误,我不会得到类似“第二行之间缺少分号...”之类的错误,而是会得到以下错误:

'varTwo' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer Type 'any[]' is not assignable to type 'string' Index signature in type 'String' only permits reading. Left side of comma operator is unused and has no side effects. 当然,如果你了解 ASI 规则,经过一番思考之后,你最终可能会意识到这里必须使用分号。原因是 JavaScript 不会在行(首或[行前插入分号。

当然,您可以通过以下两种方式之一添加:第二个语句的末尾或第三个语句的开头,例如:

// Option 1 let varOne = 'Hello' let varTwo = 'World';

[varOne, varTwo] = [varTwo, varOne]

// Option 2 let varOne = 'Hello' let varTwo = 'World'

;[varOne, varTwo] = [varTwo, varOne] 有趣的是,大多数“放弃分号”的倡导者都推荐选项 2,因为您不需要为了解决第三个问题而改变第二个语句。

代码很快就会开始看起来很奇怪 尽管许多倡导者坚持认为上述示例很少见,但正如我所说,它可能会使错误变得不直观,而且,如果你在一个团队中工作,有人需要审查你的代码并且不熟悉 ASI 怪癖,上述解决方案很容易看起来像是打字错误。

此外,在 2010 年代,解构模式(包括通过解构进行重新分配)并不那么流行,因为它直到 2016 年中期才开始得到支持。

由于这些原因,随着时间的推移,我发现自己需要加入分号,因为:

我开始在自己的代码中更多地使用解构,包括通过解构重新分配。 我开始几乎只使用 www.ysdslt.com/TypeScript。 TypeScript:为 ASI 添加其他问题 为什么 TypeScript 会使这个问题变得更糟?让我们以下面的 JS 代码为例:

let input = document.querySelector('input.url') input.focus() 一切正常!但让我们把它放到 TypeScript 中:

let input = document.querySelector('input.url') input.focus() // - errors: 'input' is possibly null // Property 'focus' does not exist on type 'Element' 好的,让我们通过将其转换为我们所知的元素来解决这个问题。

let input = document.querySelector('input.url') (input as HTMLInputElement).focus() 突然间,这两行都报错,尽管我们只修改了第二行。为什么?因为 TypeScript 导入了与 JavaScript 相同的所有 ASI 特性,这意味着以 开头的一行(会继续执行该语句。

这种情况在我身上越来越常见。我修复了一个类型问题,然后需要用括号把一些内容括起来,结果又出现了新的错误,我只能抱怨,然后在语句开头插入一个看起来很别扭的分号。

首先,分号的问题是什么? 最终,我开始思考依赖 ASI 到底有什么好处。作为软件开发者,我们习惯于在语句末尾看到分号。事实上,如果你是一名 Web 开发者,你习惯于把它们放在 CSS 声明的末尾。它们其实没什么大不了的。

那么视觉噪音呢?因为它们无处不在,所以它们最终会在视觉上消失。

它们甚至不需要额外的输入。我在 VSCode 中同时启用了 ESLint 和“保存时修复”,这意味着每次保存时都会悄悄地修复任何缺失的分号。

结果我发现,使用分号比不使用分号更能让我少思考它。这确实减少了认知开销。语句以分号结尾,就像句号一样。同行语句用分号分隔。(几乎)所有语句都有分号。在解构或修复类型问题时,我不再需要考虑分号。我的代码不再会让正在审阅它的 C# 同事感到困惑。

继续前进; 无论如何,对我来说,虽然这或许算不上最大的问题,但我认为这会是一次深入探讨代码风格决策的有趣尝试。而且我猜想,同样的逻辑或许也适用于其他使用可选分号的代码,我很想听听你的看法!