diff options
| author | eltrac <hi@eltr.ac> | 2026-04-06 23:47:25 +0800 |
|---|---|---|
| committer | eltrac <hi@eltr.ac> | 2026-04-06 23:47:25 +0800 |
| commit | 91a016f7a3759c9d47454223a2f7348b3cef4417 (patch) | |
| tree | 29dabef86c57a9c34525eb952b4d7aeb8a7ebbdb /content/article | |
| parent | 948d247c6e19ab0b274acec23a7677fa8f9c868c (diff) | |
docs: new draft
Diffstat (limited to 'content/article')
| -rw-r--r-- | content/article/js-problem.md | 135 |
1 files changed, 135 insertions, 0 deletions
diff --git a/content/article/js-problem.md b/content/article/js-problem.md new file mode 100644 index 00000000..4b2d16ef --- /dev/null +++ b/content/article/js-problem.md @@ -0,0 +1,135 @@ ++++ +title = '什么毁了 JavaScript?' +subtitle = 'The JavaScript Problem' +slug = '' +tags = [] +categories = '软件杂谈' +date = 2026-04-06T22:14:39+08:00 +draft = true +toc = true ++++ + +JavaScript 虽然不是我的启蒙语言,但绝对是对我影响最大的编程语言之一。半年以前,我的首选语言就是它。如今如果要做正经的前端开发工作,就不可能离得开 JS 或 TS。在 [TIOBE](https://www.tiobe.com/tiobe-index/) 排行榜,JS 稳居第六,只落后于人尽皆知的 Python 和 C 系语言,*还有万恶的 Java*。由于太过流行,这门脚本语言又被搬到后端,甚至可以用来写命令行应用。~~要是像 Rust 一样进入 Linux 内核,JS 就无所不能了~~。上一门能做到「哪里需要哪里搬」的语言貌似是 Lisp 家族。<!--more--> + +不过,我相信所有熟悉 JavaScript 的程序都会同意:这是一门混乱不堪的语言。不仅是语言本身的设计,它背后庞大的生态也是一团乱麻,给用户和开发者带来的体验都不能说得上舒适,有时候甚至非常糟糕。 + +本文要讨论的是 JavaScript 语言本身、人们如何使用它、Node.js 以及其他的运行时、NPM 生态和 JavaScript 如何塑造了今天的 Web 浏览体验。我还打算说服你默认禁用网站加载 JavaScript。 + +不过我不会写太多有关 TypeScript 的内容,因为我其实没有用过这门语言。 + +## 1 + "1" == "11" + +我们先来谈谈 JavaScript 的优点:它无处不在,非常易用。比如,如果你是在浏览器里读这篇文章的话,你现在就可以按下 ⌘+Shift+I(或者 Ctrl + Shift + I)打开审查元素界面,找到 Console(控制台),在里面输入 JavaScript 代码执行。浏览器本身就带有 JavaScript 解释器。 + +不妨做个实验,在刚才我说的控制台里输入这样一段代码: + +```js +a = [1, 1001, 2, 3, 42, 4] +``` + +放心,这不是恶意程序,这只是定义了一个名字为 `a` 的数组。接下来,我们尝试对这个数组进行排序,排序的结果自然应该是 `[1, 2, 3, 4, 42, 1001]` 对吧?JavaScript 的另一个优点是许多操作数据的方法都被封装好了,你可以直接使用,不必自己编写。 + +现在,在控制台里输入这段代码,排序数组。 + +```js +a.sort() +// => (6) [1, 1001, 2, 3, 4, 42] +``` + +哦不,为什么排序出来的数组是这个样子? + +是因为 JavaScript 是动态类型语言吗?数组里什么类型的数据都可以塞,可以把字符串、数字和对象放在一起,所以很难考虑到是要按照字符串进行排序,还是按照数字大小顺序进行排序,对吧? + +我们不妨把相同的代码输入 Python 解释器试试,Python 也是一门动态类型语言。只不过这门语言里不限数据类型的集合数据类型叫作元组(Tuple),使用圆括号表示。 + +```python +>>> a = (1, 1001, 2, 3, 42, 4) +>>> sorted(a) +[1, 2, 3, 4, 42, 1001] +``` + +看起来 Python 用同样简洁的代码完成了任务。我们再来看看另一们动态类型语言,这次上场的是一门 Lisp 方言——Clojure。 + +```clojure +user=> (def a '(1 1001 2 3 4 42)) +#'user/a +user=> (sort a) +(1 2 3 4 42 1001) +``` + +Clojure 也没有任何问题。看来 JavaScript 的问题并非源自「它是动态类型语言」这个事实,相反,设计得优秀的动态类型语言不但不容易出现类型问题,用起来还非常讨喜。 + +给数组排序的反直觉设计或许不算大问题,给 `sort()` 方法传入一个谓词函数即可,用 `a.sort((x, y) => x - y)` 就可以得到预期的结果了。 + +那就让我们继续实验,你知道 1+1 = 2 对吧?可是 JavaScript 却不知道: + +```js +1 + "1" +// => '11' +``` + +`NaN` 的意思是 Not a Number(不是数字)对吧?可是 JavaScript 却表示反对:`NaN` 的数据类型是数字。 + +```js +typeof NaN +// => 'number' +``` + +此外,JavaScript 还证伪了等式的传递性。 + +```js +0 == "0" +// => true +0 == [] +// => true +"0" == [] +// => false +``` + +我知道,我知道,这些问题都有解释。JavaScript 会把字符串转换为数字,把数字转换为字符串,所以有字符串出现在 `+` 的某一端就会被当作拼接操作而不是相加;`NaN` 是把其他类型强制转换为数字失败时显示的错误值,由于是转换为数字的结果,所以本质上还是数字,只不过是数字的错误值;而第三个问题是因为 `[]` 和 `0` 都是零值(Zero Value),所以相等,而 `"0"` 虽然会被转换为数字 `0`,但并非零值——所以 JavaScript 发明了 `===`(严格等于)。 + +让我们再来看看那些设计合理的动态类型语言是怎么处理这些问题的吧。 + +首先,试图对字符串进行 `+` 操作应该报错,因为数字相加和字符串拼接本来就是两个语义,不应该由同一个符号表示。 + +```clojure +user=> (+ 1 "1") +;; Execution error (ClassCastException) at user/eval3 (REPL:1). +;; class java.lang.String cannot be cast to class java.lang.Number (java.lang.String and java.lang.Number are in module java.base of loader 'bootstrap') +user=> (str 1 "1") +"11" +``` + +也正是因为对非数字进行算数操作会直接报错,`NaN` 这个错误值就不会出现,如果真的需要表示某个数据无法以数字形式表示,那么在转换式返回空值就好,比如 `nil` 或者 `null`。 + +```clojure +user=> (parse-long "12") +12 +user=> (parse-long "avc") +nil +;; nil 的类型当然是 nil +user=> (type nil) +nil +``` + +同样,如果不随意转换字符串和数字,零值的问题从一开始就不会发生。 + +```clojure +user=> (= "0" 0) +false +``` + +至此,我相信可以得出结论说:JavaScript 是一门为了做到方便,而被设计得极其不符合直觉的语言。方便和符合直觉可以共存,但 JavaScript 给出的是错误答案。 + +## 十天内诞生的编程语言 + +## 无处不在的过度抽象 + + + +## 依赖地狱和供应链投毒 + +## 网页交互的越界 + + + |
