likes
comments
collection
share

undefined为什么不设计为保留字?

作者站长头像
站长
· 阅读数 22

Halo Word!大家好,我是大家的林语冰(挨踢版)~

今天我们来伪科普一下下——undefined 为什么不设计为保留字?


敏感话题(省流版)

  1. 简单谈谈你对 undefined 进化史的理解?
  2. 黑科技 void 0 从何而来?
  3. undefined 为什么不是保留字?

1. undefined 进化史

大家可能不知道,JS(JavaScript) 1.0 是一门精简的语言,当时既木有 undefined,也莫得 void 0

BTW,木有 undefined 的意思是没有 undefined 这个全局属性,而不是没有 undefined 原始值。

我们之前的视频就强调过,undefinedundefined 原始值是两个一龙一猪的概念。

在 JS 中,“三位一体”的 undefined 起码有三层含义:

  • undefined 原始值
  • Undefined 原始类型
  • undefined 全局属性

《JS: The First 20 Years》曾经说过,未初始化的变量会被设置为特殊值 undefined。......在 JS 1.0 中,可以通过声明和读写未初始化变量的方式,读取到 undefined 原始值。

Uninitialized variables are set to the special value undefined....In JavaScript 1.0 the value undefined may be accessible by declaring and accessing an uninitialized variable.

换而言之,JS 横空出世之时,尚未集成 undefined 全局属性,也没有 void 0 这种黑科技,但是已经有 undefined 原始值了。

直到 ES3 时代,JS 才集成了 undefined 的全局绑定,这时候前端人才可以通过读写 undefined 属性直接使用 undefined 原始值。

虽然但是,直到 ES5 之前,undefined 属性是可重写的,ES5 之后 undefined 才变成更加鲁棒的只读属性——不可赋值、不可枚举、不可配置。

大家可能会讶异于一只小小的 undefined 的进化史竟然如此曲折。

undefined为什么不设计为保留字?

其实挨踢领域日新月异是常态,前端轮子滚滚向前,根本停不下来。

今天 Vue2 明天 Vue3,今年 ES2023 明年 ES2024,过去 Alpha 狗未来 CatCAT......

《路过人间》@郁可唯曾经说过,世上唯一不变,是人都善变。

初闻不知曲中意,再听已是曲中人。前端生态大抵如此,唯一不变的就是变化本身。今天前端已死,明天又“无为转变”、诈尸还魂了。

相信我,一只神头鬼脸的 undefined,实在不值得大精小怪。

综上所述,undefined 的编年史大抵如下:

  1. 1996 年 3 月 18 日,JS 1.0 横空出世,莫得 undefined 属性,已有 undefined 原始值
  2. 1998 年 11 月 19 日,ES3 崭露头角,集成了 undefined 的全局绑定
  3. 2009 年 12 月 3 日,ES5 渐进增强,集成了 undefined 属性的只读特性

大家可以不记得语冰的生命灵数,但建议 undefined 的“时间简史”全文背诵,面试时稍微凡尔赛一下下,相信我,当你开始如数家珍地 rap “1996 年 3 月 18 日”的时候,你就已经赢了,面试官也已经知道该 pick 谁了......

undefined为什么不设计为保留字?


2. 黑科技 void 0 的由来

大家在读开源项目的源码时,偶尔会邂逅一些代码重构的奇技淫巧,比如说 Vue 源码用 void 0 替换 undefined

undefined为什么不设计为保留字?

虽然但是,直接使用 undefined 简单粗暴地表示 undefined 原始值祂不浅显易懂吗?何必整 void 0 这个可读性较差的黑科技来重构代码呢?

语冰第一次看到这个黑科技时,当时还是第一次。祂那时还 too Young too Simple,不知道所有源码的博弈,早就在暗中修复好了 BUG。

你知道的,JS 1.0 既木有 undefined 属性,又莫得 void 操作符,那 void 0 为什么会成为读取 undefined 原始值的最佳实践呢?

《JS: The First 20 Years》曾经说过,JS 1.1 添加了 delete/typeof/void 等操作符。......void 操作符能且仅能对其操作数求值,然后返回 undefinedvoid 0 是读取 undefined(原始值)的一种惯例。

JavaScript 1.1 adds the delete, typeof, and void operators....The void operator simply evaluates its operand and then returns undefined.An idiom for accessing undefined is void 0.

猫眼可见,JS 1.1 就集成了 void 操作符,而 undefined 的全局绑定则直到 ES3 才集成,而且 ES5 之前 undefined 不是只读属性。

换而言之,在那个没有鲁棒的 undefined 属性可用的年代,自然而然会出现对应的“备胎”。ES5 之前 undefined 更像是一种技术负债,一旦在代码中使用不慎,将来可能就需要重构代码来还债。

偷偷共享一个小众的冷知识——“技术负债”(Technical debt)。百度百科曾经说过,技术负债 AKA 设计/代码负债,是编程及软件工程中的一个比喻。指开发人员为了加速软件开发,在应该采用最佳方案时进行了妥协,改用了短期内能加速软件开发的方案,从而在未来给自己带来的额外开发负担。

1992 年,沃德·坎宁安首次将技术的复杂比作为负债。第一次发布代码,就好比借了一笔钱。只要通过不断重写来偿还债务,小额负债便可以加速开发。但久未偿还债务会引发危险。复用马马虎虎的代码,类似于负债的利息。

undefined为什么不设计为保留字?

就我个人而言,语冰更愿意赐名为“IT 负债”,因为这种代码负债很“挨踢”。

柯学推理,ES5 之前为了规避 undefined 的挨踢负债,前端高玩就发明了 void 0 的黑科技,就好像那个没有 Vue 的年代,就会有类似尤雨溪等高瞻远瞩的伟大前端开发者寻求风口,创造面向市场的前端产品。

语冰记得尤大在一个采访中说道,现在已经很难出现像 Vue 一样挑战“var”前端霸权(Vue + Angular + React)的现象级框架。

语冰个人比较同意,部分原因就是前端市场饱和了,没有特别迫切的刚需,其次用户的选择更多样化,前端生态百家争鸣,框架各有各的亮点和痛点,新产品未必比老框架稳定,用户的依赖和惰性也是为了产品的稳定迭代考虑。

另一方面,经典的设计仍经久不衰,比如一度让语冰辗转难眠的“闭包”,竟然是 1964 年的概念,半个世纪以来仍有生命力。

undefined为什么不设计为保留字?

你知道的,void 操作符是一个平平无奇的一元操作符(AKA“单目操作符”),跟语冰一样平平无奇,祂有且仅有一个操作数,能且仅能返回undefined 原始值。

《从前慢》@木心曾经说过,从前的日色变得慢,车,马,邮件都慢,一生只够爱一个人。

举一反一,从前的闭包变得慢,值、库、void 都慢,一次只够求一个值。

柯学推理,ES3 甚至 ES5 之前,高瞻远瞩的前端高玩自然会使用 void 0 这种鲁棒的黑科技来读取更加安全的 undefined 原始值。

举个粒子,与 undefined 编年史对应的挨踢进化史大致可以推理如下。

undefined为什么不设计为保留字?

猫眼可见,void 操作符之所以是鲁棒的,是因为祂对 undefined 一心一意——有且仅有一个返回值 undefined 原始值,就像语冰作为地球猫猫教的虔信徒,也对猫猫一心一意。

《Lemon》@米津玄师曾经说过,时至今日 你仍是我的光芒。

举一反一,时至今日,void 0 黑科技依然是开源项目的一种惯例,甚至是最佳实践。


3. undefined 为什么不是保留字?

首先要知道,保留字是什么鬼物?

ES 规范(ECMAScript Language Specification)曾经说过,保留字是不能作为标识符名的标识符。好多关键字是保留字,但不完全是,有的是上下文相关保留字。

A reserved word is an IdentifierName that cannot be used as an identifier. Many keywords are reserved words, but some are not, and some are reserved only in certain contexts.

举个粒子,null 是保留字,undefined 则不是。

undefined为什么不设计为保留字?

猫眼可见,保留字不能作为标识符名,比如说变量名等,否则程序就会报错。

undefined 可以作为变量名,反证法可得,undefined 不是保留字。

ES5.1 曾经说过,保留字由下列词法单元组成——

  • Keyword(关键字)
  • FutureReservedWord(未来保留字)
  • NullLiteral(Null 字面量)
  • BooleanLiteral(布尔字面量)

前端高能

ES6 AKA ES2015,现代前端开发基本是 ES6 筑基,按需优雅降级,ES6 破蛋已经快 10 年了,ES5.1 就更久远了。ES5.1 保留字的规范并不完全适用后 ES6 时代的前端开发,可能有看不见的 BUG,此处只是辅助理解,仅供参考。

我再重生一遍,我没有在开玩笑,倘若对这一部分知识点感兴趣,想登堂入室的前端高玩建议瞄一眼最新版 ES 规范。

总而言之,保留字本身一言难尽,即使是在后 ES6 时代的现代化开发中,“阉割模式”(严格模式)也会影响上下文相关保留字的界定范围。

举个粒子,大家都用过 ES6 的 let,我们来测试一下下有没有关于 let 的知识盲区,一键三连,但是灵魂拷问版。

  • let 是保留字吗?
  • let 在“阉割模式”下是保留字吗?
  • let 在“躺平模式”(Sloppy Mode)下是保留字吗?

我可太谢谢你了,本来还挺喜欢 let 的,别问了,再问炸毛。

猫眼可见,保留字自身难保,但 undefined 好像不至于绝体绝命,那为什么不将 undefined 设计为像 null 一样更加鲁棒的字面量/保留字呢?

比如说我们不烦退一步,将 undefined 设计为上下文相关的保留字。

不幸的是,这是不可能的,因为这个方案会对历史遗留的代码产生非兼容性的 breaking changes(破坏性更新)。

大家有没有想过,为什么 Vue@2.7 之后要出 Vue3 而不是 Vue@2.8?就是因为 Vue3 对于 Vue2 来说是一种破坏性更新,开源项目版本号一般会遵循语义化版本规范,这个冷知识我们日后再说。倘若你觉得我们平时安装各种 npm 包的版本号十分变态,通读这个规范或许能让我们转换视角。

不知道大家有没有听说过“天临元年”,某种意义上,该事件对于论文写作传统也是一种破坏性更新。

举个粒子,undefined 作为保留字会将旧版代码毁于一旦。

undefined为什么不设计为保留字?

猫眼可见,当我们 DIY 了第一个 undefined 变量的时候,“万恶之源”就开始了。在那个没有 undefined 属性的时代,原则上我们认为当时这并不是一种挨踢负债。

前端已死莫衷一是,前端已卷俯拾皆是,语冰万万没想到,一个 undefined 也能这么卷。过去的极客也能穿越时空压制后来的我们。

假设 undefined 被设计为保留字,我们知道保留字是不能作为变量名(标识符)的,一旦方案落地就意味着历史遗留的代码全部作废。楚人一炬,可怜焦土。undefined 一句,可怜前端。)

这种 breaking changes 就好像今天我们要改用 cat 而不是 let 声明变量一样勇。即使是 IE 的巅峰时期,祂都不敢这么嚣张,搞破而后立的大一统,当然这跟 IE 其实没有关系,我们只是举个粒子,绝对不是反复鞭尸。

(IE:“还好老子退休得早!我怀疑你祂喵地在暗示些什么!”)

你知道的,原则上即使是 ES6 筑基的现代化前端开发,ES 规范也不会去做“零和博弈”的 breaking changes,和 ES6 牛叉症的我们拼个你死我活,而是既要“渐进增强”又要“优雅降级”,优先考虑向下兼容的增量更新。

综上所述,语冰的个人心证是——

冰冻三尺非一日之寒,undefined 也不是一日集成的。undefined 之所以不是保留字,首先是犯下摆烂之罪的 undefined 自己,其次是犯下傲娇之罪的保留字,最后是犯下内卷之罪的前端人。

undefined为什么不设计为保留字?

语冰以前总觉得 undefined 要么是 BUG,要么是语法糖,现在看来,undefined 既是 BUG 也是语法糖——即有 BUG 的语法糖,AKA“历史包袱”。


免责声明

本文示例代码默认均为 ESM(ECMAScript Module)筑基测评,因为现代化前端开发相对推荐集成 ESM,其他开发环境下的示例会额外注释说明,edge cases 的解释权归大家所有。

今天的《ES6 混合理论》就讲到这里啦,我们将在本合集中深度学习若干奇奇怪怪的前端面试题/冷知识,感兴趣的前端爱好者可以关注订阅,也欢迎大家自由言论和留言许愿,共享 BUG,共同内卷。

吾乃前端的虔信徒,传播 BUG 的福音。

我是大家的林语冰,我们一期一会,不散不见,掰掰~

转载自:https://juejin.cn/post/7242258285160005688
评论
请登录