summaryrefslogtreecommitdiff
path: root/content/article
diff options
context:
space:
mode:
authoreltrac <hi@eltr.ac>2026-04-10 13:37:08 +0800
committereltrac <hi@eltr.ac>2026-04-10 13:37:08 +0800
commit390412d73e90993a3dbeff3db8806bfcb3a0429e (patch)
tree5ff48bd95bd2cd87f123e77f91b3642e2bd05af8 /content/article
parente6665c7aad7f799709329422f05140514d7f8cb3 (diff)
[article] publish /article/js-problem
Diffstat (limited to 'content/article')
-rw-r--r--content/article/js-problem.md40
1 files changed, 21 insertions, 19 deletions
diff --git a/content/article/js-problem.md b/content/article/js-problem.md
index c89ff4f8..2588c24d 100644
--- a/content/article/js-problem.md
+++ b/content/article/js-problem.md
@@ -1,19 +1,19 @@
+++
title = '什么毁了 JavaScript?'
-subtitle = 'The JavaScript Problem'
-slug = ''
-tags = []
+subtitle = 'How Did We Get Here?'
+slug = 'js-problem'
+tags = ["Web","技术","隐私","编程"]
categories = '软件杂谈'
-date = 2026-04-06T22:14:39+08:00
-draft = true
+date = 2026-04-10 13:36:44
+draft = false
toc = true
+++
-JavaScript 虽然不是我的启蒙语言,但绝对是对我影响最大的编程语言之一。半年以前,我的首选语言就是它。如今如果要做正经的前端开发工作,就不可能离得开 JS 或 TS。在 [TIOBE](https://www.tiobe.com/tiobe-index/) 排行榜,JS 稳居第六,只落后于人尽皆知的 Python 和 C 系语言,*还有万恶的 Java*。由于太过流行,这门脚本语言又被搬到后端,甚至可以用来写命令行应用。~~要是像 Rust 一样进入 Linux 内核,JS 就无所不能了~~。上一门能做到「哪里需要哪里搬」的语言貌似是 Lisp 家族。<!--more-->
+JavaScript 虽然不是我的启蒙语言,但绝对是对我影响最大的编程语言之一。半年以前,我的首选语言就是它。如今如果要做正经的前端开发工作,就不可能离得开 JS 或 TS。在 [TIOBE](https://www.tiobe.com/tiobe-index/) 排行榜,JS 稳居第六,只落后于人尽皆知的 Python 和 C 系语言,*还有万恶的 Java*。由于太过流行,这门脚本语言又被搬到后端,甚至可以用来写命令行应用。~~要是像 Rust 一样进入 Linux 内核,JS 就无所不能了~~。<!--more-->
不过,我相信所有熟悉 JavaScript 的程序都会同意:这是一门混乱不堪的语言。不仅是语言本身的设计,它背后庞大的生态也是一团乱麻,给用户和开发者带来的体验都不能说得上舒适,有时候甚至非常糟糕。
-本文要讨论的是 JavaScript 语言本身、人们如何使用它、Node.js 以及其他的运行时、NPM 生态和 JavaScript 如何塑造了今天的 Web 浏览体验。我还打算说服你默认禁用网站加载 JavaScript。
+本文要讨论的是 JavaScript 语言本身、人们如何使用它、NPM 生态如何塑造了今天的 Web 开发体验。作为用户,我还打算说服你默认禁用 JavaScript。
不过我不会写太多有关 TypeScript 的内容,因为我其实没有用过这门语言。
@@ -38,7 +38,7 @@ a.sort()
哦不,为什么排序出来的数组是这个样子?
-是因为 JavaScript 是动态类型语言吗?数组里什么类型的数据都可以塞,可以把字符串、数字和对象放在一起,所以很难考虑到是要按照字符串进行排序,还是按照数字大小顺序进行排序,对吧?
+因为 JavaScript 是动态类型语言?数组里什么类型的数据都可以塞,可以把字符串、数字和对象放在一起,所以很难考虑到是要按照字符顺序进行排序,还是按照数字大小顺序进行排序,对吧?
我们不妨把相同的代码输入 Python 解释器试试,Python 也是一门动态类型语言。只不过这门语言里不限数据类型的集合数据类型叫作元组(Tuple),使用圆括号表示。
@@ -86,7 +86,7 @@ typeof NaN
// => false
```
-我知道,我知道,这些问题都有解释。JavaScript 会把字符串转换为数字,把数字转换为字符串,所以有字符串出现在 `+` 的某一端就会被当作拼接操作而不是相加;`NaN` 是把其他类型强制转换为数字失败时显示的错误值,由于是转换为数字的结果,所以本质上还是数字,只不过是数字的错误值;而第三个问题是因为 `[]` 和 `0` 都是零值(Zero Value),所以相等,而 `"0"` 虽然会被转换为数字 `0`,但并非零值——所以 JavaScript 发明了 `===`(严格等于)。
+我知道,我知道,这些问题都有解释。JavaScript 会把字符串转换为数字,把数字转换为字符串,所以有字符串出现在 `+` 的某一端就会被当作拼接操作而不是相加;`NaN` 是把其他类型强制转换为数字失败时显示的错误值,由于是转换为数字的结果,所以本质上还是数字;而第三个问题是因为 `[]` 和 `0` 都是零值(Zero Value),所以相等,而 `"0"` 虽然会被转换为数字 `0`,但并非零值——所以 JavaScript 引入了 `===`(严格等于)。
让我们再来看看那些设计合理的动态类型语言是怎么处理这些问题的吧。
@@ -100,7 +100,7 @@ user=> (str 1 "1")
"11"
```
-也正是因为对非数字进行算数操作会直接报错,`NaN` 这个错误值就不会出现,如果真的需要表示某个数据无法以数字形式表示,那么在转换式返回空值就好,比如 `nil` 或者 `null`。
+也正是因为对非数字进行算术操作会扔出错误,`NaN` 这个错误值就不会出现,如果真的需要表示数据无法解析为数组,那么返回空值就好,比如 `nil` 或者 `null`。
```clojure
user=> (parse-long "12")
@@ -138,6 +138,8 @@ false
这门语言还有变量作用域等各方面的问题,至此,我相信可以得出结论说:JavaScript 是一门为了做到方便,而被设计得极其不符合直觉的语言。方便和符合直觉可以共存,但 JavaScript 给出的是错误答案。
+不过,随着规范化的不断推进,不少设计缺陷已经有规避方案了,在意类型安全可以使用 TypeScript,变量作用域问题也可以严格限制全局变量,多用 `let` 和 `const` 解决。尽管不至于经常因设计缺陷而感到困扰,但还是很难不在意,这些缺陷一开始为什么会存在?
+
## 十天诞生的编程语言
2000 前最流行的浏览器叫作 Netscape Navigator(前身是 Mosaic),1995 年 Netscape 决定要给浏览器添加创建交互式网页的能力,他们原本打算把 Java 或 Scheme 嵌入浏览器中(*真是两条…… 截然相反的道路*),为此雇佣了 Brenden Eich 完成 Scheme 的嵌入工作,后来他们决定造一门新语言,于是让这个新雇佣的程序员在**两周**内完成并发布了 LiveScript。由于当时 Java 很火,所以后来更名为 JavaScript,两门语言并无直接关联。
@@ -148,7 +150,7 @@ false
给这门仓促开发的编程语言打的补丁还有很多,比如 TypeScript 就硬生生把它变成了一门强类型语言(前面我已经讨论过 JS 的问题并非源自它是弱类型语言这个事实)。
-打补丁能解决的问题是有限的,为了保证兼容性(更何况是一门所有浏览器甚至服务器后端都在使用的编程语言),很多设计缺陷保留至今,加上各种补丁反而让 JavaScript 看起来像个臃肿丑陋的怪物。比如,JavaScript 自带的时间类型非常难用,最近推出的 [Temporal](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal) 解决了这个问题,解决方法是增加了新的时间类型,同时兼容旧的、有问题的时间类型。很难相信,现代化的用于处理时间和日期的基础数据类型,竟然在 2026 年还没有得到广泛支持。
+打补丁能解决的问题是有限的,为了保证兼容性(更何况是一门所有浏览器甚至服务器后端都在使用的编程语言),很多设计缺陷保留至今,加上各种补丁反而让 JavaScript 看起来像个臃肿丑陋的怪物。比如,JavaScript 自带的时间类型非常难用,最近推出的 [Temporal](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal) 解决了这个问题,解决方法是增加了新的时间类型,同时兼容旧的、有问题的时间类型。这个 API 截至目前的兼容性是「Limited Availability」,Safari 浏览器还不兼容。很难相信,现代化的用于处理时间和日期的基础数据类型,竟然在 2026 年还没有得到广泛支持。
## 无处不在的过度抽象
@@ -180,11 +182,11 @@ JavaScript 开发者还经常被嘲笑热衷于造新框架,先让我不查资
看来一双手还数不过来,一定还有我不知道的框架,如果有人能做更全面的统计就好了。此外,不少开发者还喜欢造一些小框架,如果总数加起来超过 100,我不会惊讶。
-就算手写 HTML 需要编写很多重复代码,但 [Neocities](https://neocities.org/) 社区就在没有框架的情况下创造了很多有趣的个人网站,这无疑说明很多网页需求并不需要进行了一层甚至很多层抽象的框架来解决。如果一个网站只有几个页面,或者根本不是网页应用,只展示文档内容,那根本不需要 Vue.js,使用 JavaScript 加载内容对 SEO 非常不友好(Vue.js 是用来做单页应用的,依赖前端路由,所有网页路径都会被转发到同一个入口文件,实际上整个网站都只有一个页面),而且没有加载 JS 的自动程序无法识别网页内容,像我这种默认禁用 JS 的用户也根本看不到网页。
+就算手写 HTML 需要编写很多重复代码,但 [Neocities](https://neocities.org/) 社区就在没有框架的情况下创造了很多有趣的个人网站,这无疑说明很多网页需求并不需要进行了一层甚至很多层抽象的框架来解决。如果一个网站只有几个页面,或者根本不是网页应用,只展示文档内容,那根本不需要框架。况且,使用 JavaScript 加载内容对 SEO 非常不友好(Vue.js 是用来做单页应用的,依赖前端路由,所有网页路径都会被转发到同一个入口文件,实际上整个网站都只有一个页面),而且没有加载 JS 的自动程序无法识别网页内容,像我这种默认禁用 JS 的用户也根本看不到网页。
JavaScript 是用来设计交互式网页的,必须加载 JavaScript 才能看到文本内容就是很蠢的设计。
-诚然,手写 HTML 也很蠢,应该把一部分复杂度合理地外包给计算机程序,建立**适当的抽象**,所以我们有静态网站生成器(SSG,Static Site Generator)这种东西。就算是 WordPress 和 Typecho 这类需要后端才能运行的动态博客程序,本质上也是在服务端把 HTML 网页拼接好再发送 HTML 和必要的静态资源给访客,不需要加载非必要的 JavaScript,也没有建立过度的抽象。
+诚然,手写 HTML 很低效,应该把一部分复杂度合理地外包给计算机程序,建立**适当的抽象**,所以我们有静态网站生成器(SSG,Static Site Generator)这种东西。就算是 WordPress 和 Typecho 这类需要后端才能运行的动态博客程序,本质上也是在服务端把 HTML 网页拼接好再发送 HTML 和必要的静态资源给访客,不需要加载非必要的 JavaScript,也没有建立过度的抽象。
写到这里我想我需要解释一下「抽象」这个词,抽象层级一般是用来屏蔽软件架构的复杂度的。比如,审计银行流水的业务规则不应该了解如何向数据库发送请求,如果相关的代码里既有各种校验逻辑,又有 SQL 语句,那这个软件维护起来就很困难,应该把后者分离到数据持久层。同理,如果你要编写的网络应用的 DOM 结构非常复杂,比如 Cloudflare 的仪表盘,包含侧边、顶栏、数据展示等网页元素,还要管理从后端 API 获取的用户数据,有些数据的请求还是异步的,更新后又需要修改元素,那完全不做抽象,手写 HTML 就是很蠢的,这时候就应该抽象出组件的概念,以及专门的状态管理和路由模块等等。
@@ -219,21 +221,21 @@ JavaScript 是用来设计交互式网页的,必须加载 JavaScript 才能看
剩下的就不一一列举了,因为实在是*太多了*。其实也不能全怪 NPM,隔壁 Python 的包索引 PyPi 也在最近遭到投毒,详情可以阅读刘家财写的《[全球软件供应链安全演进:从 LiteLLM 投毒事件审视 Rust 包管理及分布式架构转型](https://liujiacai.net/ai/litellm-poisoning-analysis/)》,文中还对比了 Zig、Go、Rust、Python 和 JavaScript(NPM)包管理的风险指数,其中 Zig 是风险最低也最去中心化的,而 NPM 则是另一个极端。
-供应链投毒对 JavaScript 开发者的影响是很大的,执行 `npm install` 或者其他日常使用的命令之后就可能会有恶意代码在电脑上不知不觉地执行,偷走自己的加密钱包、SSH 密钥等重要信息,开发者的数字身份都有可能受到很大威胁,不仅仅是电脑中毒那么简单。
+供应链投毒对 JavaScript 开发者的影响很大,执行 `npm install` 或者其他日常使用的命令之后就可能会有恶意代码在电脑上不知不觉地执行,偷走自己的加密钱包、SSH 密钥等重要信息,开发者的数字身份都有可能受到很大威胁,不仅仅是电脑中毒那么简单。
-显然,JavaScript 开发者很不好过,用户也没有好果子吃。
+开发者很不好过,用户也没有好果子吃。
## 网页交互的越界
我在前文多次提到,我在使用浏览器时默认禁用 JavaScript。这是为什么?NPM 投毒有风险是因为恶意代码会直接在开发者的电脑上执行,难不成恶意的 JavaScript 代码还能跃出浏览器,直接访问设备上的数据吗?就算可以,难道现代浏览器不是把系统权限都管理的很好吗?
-的确,除非手动允许网站访问文件、摄像头和外部设备等系统资源,网站并没有办法直接获取储存在本地的密钥等关键信息,但 JavaScript 的确可以在用户没有任何感知的情况下做某些事情,比如获取 Cookies、浏览器版本、安装的浏览器插件等关键信息,还能占用你的 CPU 资源进行计算。前者可以用来生成浏览器指纹跟踪用户(一般是用来投放定向广告,但也有隐私风险),如果遇到跨站脚本攻击(XSS),储存在 Cookies 里的登录凭证就可能被偷取;后者被利用的方式就很简单粗暴了,攻击者可以用你的 CPU 在后台悄悄挖矿,这叫做 Cryptojacking[^2][^3]。
+的确,除非手动允许网站访问文件、摄像头和外部设备等系统资源,网站并没有办法直接获取储存在本地的密钥等关键信息,但 JavaScript 的确可以在用户没有任何感知的情况下做某些事情,比如获取 Cookies、浏览器版本、安装的浏览器插件等,还能占用你的 CPU 资源进行计算。前者可以用来生成浏览器指纹跟踪用户(一般是用来投放定向广告,但也有隐私风险),如果遇到跨站脚本攻击(XSS),储存在 Cookies 里的登录凭证就可能被偷取;后者被利用的方式就很简单粗暴了,攻击者可以用你的 CPU 在后台悄悄挖矿,这叫做 Cryptojacking[^2][^3]。
还有一些问题并不是恶意攻击造成的,而是某些不尊重用户的开发者(有一些甚至不是开发者,只能说是*站长*吧)的流氓行为。是的,我觉得以下都是流氓行为:在用户暂时离开网页时修改浏览器标签页的图标和标题,禁用系统的右键菜单,禁用审查元素(或者说控制台)和在用户不知情的情况下自动播放音频。
无论是恶意攻击还是流氓行为,都是在用户不知情的情况下加载 JavaScript 脚本导致的。这些脚本是不道德的,比如跟踪器。既然现有的浏览器还没有办法给某些可被利用的 API 设置较高的权限要求,像是否允许网站弹出通知那样让用户自行选择,那么用户就必须另寻他路了,方法就是主动屏蔽和拦截不想要的脚本。
-最基础的做法是使用 [uBlock Origin](https://github.com/gorhill/uBlock) 这类脚本,屏蔽跟踪器。说起跟踪器,就不得不提到 Umami 和 Google Analytics 等分析工具都会利用浏览器指纹等手段跟踪用户,这对科技公司来说兴许是必要的商业举措(尽管依然不道德),但许多个人站长也使用此类分析工具。我建议使用 [GoatCounter](https://goatcounter.com/) 等不跟踪用户的 Web 统计工具。
+最基础的做法是使用 [uBlock Origin](https://github.com/gorhill/uBlock) 这类脚本,屏蔽跟踪器。说起跟踪器,就不得不提到 Google Analytics 等分析工具都会利用浏览器指纹等手段跟踪用户,这对科技公司来说兴许是必要的商业举措(尽管依然不道德),但许多个人站长也使用此类分析工具。我建议使用 [GoatCounter](https://goatcounter.com/) 等不跟踪用户的 Web 统计工具。
当然,uBlock Origin 只会屏蔽那些明显的恶意脚本,往往基于社区维护的屏蔽规则,肯定会有漏网之鱼。如果遇到突然出现的 XSS 攻击,只用浏览器插件就难以阻止了(不要觉得如今 XXS 已经不常见了,之前 Discord 还[被一张 SVG 图片黑了](https://eva.ac/blog/mintlify/))。更何况,有些脚本不是恶意,只是恶心,一般的屏蔽器没理由屏蔽它。
@@ -247,9 +249,9 @@ JavaScript 是用来设计交互式网页的,必须加载 JavaScript 才能看
## 最后
-我说了不少 JavaScript 的坏话,但不可否认的是的确有不少人正在持续推进规范化和相关标准的修订,让这门语言变得更易用。为网页添加交互能力本身是极具开创性的点子,也构成了如今万维网不可或缺的一部分,但令人唏嘘的是,这个极具开创性的点子在一开始就没能得到足够的重视,在极短的开发周期内急急忙忙地上线,留下了许多历史包袱;又由于标准库的不完善,和中心化且抗风险能力低的包管理,这门语言给开发者带来了不少依赖和安全性相关的风险,并且漏洞频发;再加上科技公司对技术的滥用,用户的使用体验也得不到保障,而用户体验就是 JavaScript 一开始被创造的原因。
-
+我说了不少 JavaScript 的坏话,但毋庸置疑,的确有不少人正在持续推进规范化和相关标准的修订,让这门语言变得更易用。为网页添加交互能力本身是极具开创性的点子,也构成了如今万维网不可或缺的一部分,但令人唏嘘的是,这个极具开创性的点子在一开始就没能得到足够的重视,在极短的开发周期内急急忙忙地上线,留下了许多历史包袱;又由于标准库的不完善,和中心化且抗风险能力低的包管理,这门语言给开发者带来了不少依赖和安全性相关的风险,并且漏洞频发;再加上科技公司对技术的滥用,用户的使用体验也得不到保障,而用户体验就是 JavaScript 一开始被创造的原因,它现在已经成为了在万维网甚至服务器后端行走的怪物。
+我不讨厌这门语言,我也希望它变得更好,但就目前而言,开发者和用户都应该谨慎对待这项多少有些失控的技术。
[^1]: 不过值得一提的是,有 [Web API](https://developer.mozilla.org/en-US/docs/Web/API) 这样的规范一直在解决 JavaScript 标准库不完善的问题,比如 axios 其实就可以用 [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) 替代了。