likes
comments
collection
share

JSON不是JS对象字面量,JSON爱好者的误区

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

Hello World 大家好,我是大家的林语冰(挨踢版)~

地球人都知道,JSON 是世界上最流行的数据交换格式,在前后端都人气爆棚,豆瓣评分比语冰还高。今天我们来蹭一下 JSON 的流量,伪科普一下下全栈攻城狮必知必会的 JSON 冷知识。

今日头条(TLDR 省流版)

本期讨论的前端脑洞,包括但不限于:

  1. 凭什么说 JSON 不是 JS 对象字面量?
  2. 首字母大写的 JSON 是 JS 抽象类吗?
  3. 强如 JSON 甚至不支持注释,why?
  4. JSON 属性必须用双引号,why?
  5. JSON5 不是 JSON 的第 5 版,那是什么?
  6. JSONPJSON 毫无关系,除非你硬要发生关系
  • 知识浓度:3 颗星
  • 文章字数:5_000 字水文
  • 时间成本:10 分钟

JSON 不是 JS 对象字面量

地球人都知道,JSON 读做“借肾”,AJAX 读做“啊贾克斯”,但是大家可能不知道,AJAX 的发音其实并不是“阿贾克斯”,不信可以让百度翻译给你读一下试看看。

几乎所有全栈攻城狮都和 JSON 有过交集,但当铲屎官提问 JSON 顾名思义是什么鬼物,似懂非懂的喵星人可能“顾名失忆”,选择萌混过关,理直气壮地胡说八道,JSON 是 JS 对象字面量,无序键值对的集合。

如果你觉得这个说法是标准答案,那你可能也误会了 JSON,处于似懂非懂的叠加态,事实上挨踢领域不是传统的应试教育,大多数时候没有标准答案。

“大胆假射,小心球证。”我们不烦先来柯学推理一下下:

  1. 假设 JSON 是 JS 对象字面量,那么 JSON 应该写做“JavaScript Object Literal”,即缩写为“JSOL”。

  2. 假设 JSON 是 JS 对象字面量,那意味着祂与 JS 强耦合,可能无法与其他编程语言完美兼容

反证法可得,真相只有一个,JSON 不是、也不应该是 JS 对象字面量。如果你觉得 JSON 是 JS 对象字面量,那可能是思想出了问题,好在你看见了这篇伪科普水文。

勿言推理,JSON 到底是什么鬼物呢?

JSON 全称展开读做“JavaScript Object Notation”,是“JS 对象简谱”的首字母缩写,AKA“JS 对象表示法”。

JSON 官网如是说,JSON 是一种轻量级的数据交互格式,既易于读写,也易于解析和生成。祂是 ES3 筑基的一个子集。JSON 采用完全独立于语言的文本格式,但也使用了类 C 语言家族的习惯(包括 C, C++, C#, Java, JavaScript,Python等)。这些特性使 JSON 成为理想的数据交换语言。

换而言之,JSON 的设计动机是为了不同语言的交互,所以不会局限于仅支持 JS 对象的语法。这也是 JSON 在前后端都能吸粉的原因,因为祂只是一种格式,与编程语言没有强耦合。

不管什么编程语言,只要格式正确,文件后缀名是 json,都可以和 JSON 对上电波,无障碍沟通。

不幸的是,JSON 因为名字里带有 JS Object 的标签,所以经常被误解,就好像 JavaScript 被当成 Java 的小老弟一样离谱,历史总是惊人地相似。这正好提醒我们,我们不应该随便给别人贴标签,也不应该随便相信别人身上被贴的标签。

那为啥取名叫 JS 对象简谱捏?这风格也太误解向了吧。

其实在 JSON 破蛋之前,大家更多使用的是 XML。BTW,HTML 的全称叫“超文本标记语言”就与其有关。

JSON 起初也是类似,叫“JSML”(JS 标记语言),但由于和 Java 生态的同名产物有命名冲突,所以才改名 JSON,这原本是想体现 JSON 语法格式的设计灵感源自 JS 对象字面量,结果反而弄巧成拙。如果 JSON 当初顺利取名为“JSML”,或许今天就能减少好多误会,那也就没有语冰的这篇伪科普水文了。

JSON 的写法十分精简,就是无序属性的键值对,尤其 JS 用户开箱即用,我们来举个粒子。

JSON不是JS对象字面量,JSON爱好者的误区

如你所见,JSON 就是一个阉割版的 JS 对象字面量写法,但没有完全阉割。

为什么说 JSON 是阉割版的 JS 对象字面量?

因为一些合法的 JS 数据被 JSON 阉割了,比如 JS 对象字面量支持 Undefined 基本类型,但 JSON 并不支持。

注意,上面的 JSON 并没有支持 undefined,因为语冰写的是 'undefined' 字符串。

举一反一,JSON 支持数字类型,但没有完全支持,一些奇葩值也被 JSON 阉割了,比如 NaN,因为 NaN 是一种表示错误语义的数据,而 JSON 是一种数据通信格式,所以不需要支持,因为我们不鼓励使用错误的数据。

假设语冰的女粉丝数量是 NaN,那数据该怎么解释?再假设猫猫的年龄是 NaN,又该如何解释?既然没法解释,那就干脆不支持,产品经理们听懂了吗?

但为什么又说没有完全阉割?

因为 JSON 不支持 JS 对象字面量的一些简写技巧,包括但不限于 ES6 的对象属性简写等语法。(事实上当时还没有 ES6 呢!)比如 JSON 的属性必须和 "" 双引号贴贴,绝对不能阉割,否则解码时会挂掉。

换而言之,合法的 JSON 一定是合法的 JS 对象字面量,但合法的 JS 对象字面量不一定是合法的 JSON,这好像叫做“充分非必要条件”(不要问我什么是“充分非必要条件”,语冰是数学笨蛋)。

由于 JSON 读做“JS 对象简谱”,又可以写做 JS 对象字面量,所以好多人下意识地以为两者“图灵等价”。我再重生一遍,JSON 并不是 JS 对象字面量。你可以在 JS 中写 JSON,但你可能不能在 JSON 中写 JS。

事实上,JSON 可以看做一个“严格模式”的 JS 对象字面量——即严格遵循 JSON 格式的 JS 对象字面量,多写一个逗号就会报错,少写一个双引号就会挂掉。

语冰偷偷共享一种面试时的凡尔赛措辞——JSON 是 JS 对象字面量的严格子集(不要问我什么是“严格子集”,语冰是数学笨蛋)。

JS 中的 JSON API

不知道大家有没有发现,JSON 首字母是大写的(甚至祂喵地整个 JSON 都是大写的)。

语冰偷偷共享一个小众的冷知识,编程的有一个潜规则是,大写的变量要么是常量,要么是类,要么是接口,要么是其他东西,或者都不是。

那么大写的 JSON 是不是类呢?我们可以尝试 new 实例化一下。

JSON不是JS对象字面量,JSON爱好者的误区

如你所见,试试就逝世,JSON 无法实例化,所以 JSON 不是类,对吗?

不对,《ES6 标准入门教程》中提到一种不能被实例化的类,术语是“抽象类”。抽象类不能 new 实例化,但可以被继承,所以 JSON 可能是抽象类。(抽象类在面向对象编程中常见,不信可以试试 TS。)

举个粒子,我们来尝试证明 JSON 是抽象类。

JSON不是JS对象字面量,JSON爱好者的误区

如你所见,试试就逝世,JSON 无法被继承,所以 JSON 也不是抽象类。

部分编程语言里还有静态类的概念,那么 JSON 是静态类吗?这是一个好问题。(语冰不懂静态类。)

JSON 没有 prototype 属性,做不成类也很正常。

AnyWay,大写的东东除了类/接口,还有常量,不如把 JSON 当做一个对象/命名空间理解就欧了。

举个粒子,先不谈类,尝试证明 JSON 是一个对象。

JSON不是JS对象字面量,JSON爱好者的误区

如你所见,我只能说一个字——perfect,JSON 怎么看都是一个对象实锤了。

至于 JSON 的解码/编码的 API,其实与另一个术语“序列化”相关,我们日后再说,大家可以先点一波关注,免得错过。

JSON 根本不需要注释

因为 JSON 不是 JS 对象字面量,所以 JS 的注释在 JSON 中是非法的,会解码错误。

作为配置文件使用时,JSON 被初学者诟病的一大痛点是这个格式明明超强却无法注释,所以当我们在学习配置的时候,学习成本和学习难度就起来了。

但这其实并不是 JSON 的“阿喀琉斯之踵”(命门),JSON 不支持注释是事出有因,甚至是故意为之。

JSON 的设计遵循“最小化原则”,Less is More.(少即是多。)早期的 JSON 有考虑支持注释,毕竟祂来源于 JS 对象字面量,所以有注释的想法很正常。但是,注释的内容往往可能包含一些特殊的解析指令,这不利于推广 JSON

举个粒子,JS 中我们可以使用一些指令去关闭 TS/ESlint 的代码检查。

JSON不是JS对象字面量,JSON爱好者的误区

如你所见,这个写法在前端开发中很常见,但其他语言的攻城狮看到这个注释就一脸懵逼,因为不同语言的注释语法未必相同,注释中加指令的需求也客观存在,但这些都与“数据”无关,所以这不利于 JSON 的推广,损害了 JSON 的通用性。

另外,JSON 的解码器可以被其他语言实现,因为 JSON 本身足够精简,所以解码器不会太过复杂。但是如果要支持注释,那么解码器会更加复杂,这对性能也有损耗。

最重要的是,JSON 是表示数据的格式,注释往往是不需要/不可用的数据,如果后端给前端一个数据,前端还要数据中过滤注释,筛选出有意义的数据,那可太麻烦了。

JSON 破蛋之前,XML 是数据通信的格式霸权,据说 XML 格式就需要自己编程从接收的 XML 文档中提取数据,不像 JSON 是“纯粹的数据”。(语冰不懂 XML,不知道 XML 支不支持注释。)

人类是一种趋易避难的生物,如果 JSON 也像 XML 一样需要自己编程处理数据,那我们为什么不直接 pick XML,还要自己反复造轮子,重新发明一个大同小异的 JSON?私以为 JSON 之所以能够流行,正是因为祂是纯粹且精简的数据文本。

再者说,注释的本质也是一种数据——供读者参考的数据,所以 JSON 完全可以用自己来表示自己,不需要提供额外的注释语法支持,而是使用额外的 JSON 键值对充当原本数据的注释即可。

编程语言的编译器可以由该编程语言来实现,即我用自己编程写我自己,TS 编译器就是用 TS 编程实现的。举一反一,JSON 的数据也可以表示自己数据的注释,作为元数据。

举个粒子,npm 包通常都有对应的 package.json 文件,用来表示第三方模块的信息。

JSON不是JS对象字面量,JSON爱好者的误区

如你所见,要用 JSON 打败 JSON,即使 JSON 不提供额外的注释语法,也可以用来达到注释的目的。唯一的问题是,用作注释的冗余字段在通信过程会消耗更多的网络资源,比如带宽/流量等,所以这种写法也蛮少见的。

BTW,JSON 不支持注释,也是让 JSON 成为 YAML 子集的一种妥协,这大约就是“曲线救国”吧。

机智如你可能已经发现,我祂喵地在 VSCode 里写 JSON 是支持注释的,VSCode 不愧是地表最强 IDE。

这其实是 VSCode 集成的一种 jsonc 模式,意思是“JSON with Comments”,简而言之就是 VSCode 会把 .json 文件关联为 jsonc 模式,除了完美兼容 JSON 的标准格式,还提供了 JS 注释语法的支持。

换而言之,此时是 VSCode 内置的解码器在帮我们解码 JSON 数据,而不是我们自己使用运行时 API 解码。

注意,这个使用场景一般是在配置文件中使用,方便大家理解该属性对应的配置含义,而且依赖于 IDE 的支持集成。

这意味着此时的 JSON 数据不是应用于前后端数据交互的场景,如果你使用 JSON.stringify() API 去解码,照样试试就逝世。

为什么选择了双引号?

除了不支持注释,JSON 还要求属性名必须使用双引号裹挟。

为什么 JSON 不能像 JS 对象字面量一样,属性名不使用引号直接写就完事了?

因为有其他的格式已经实现了类似的语法,但这不是主要的原因。主要的原因是不同的编程语言有不同的保留字/关键字,这些单词出现在源码文本中会被解释为自己的特殊语义。在 JSON 破蛋的年代,JS 的标准并不稳定,比如 ES3 的保留字规范不支持一些保留字作为标识符,包括但不限于变量名、属性名等。

举个粒子,使用 ES3 来写对象字面量。

JSON不是JS对象字面量,JSON爱好者的误区

如你所见,试试就逝世。这还只是 ES3 的问题,即使后来 ES5 修复了,但其他编程语言也有自己的保留字/关键字,总不能为了 JSON 把 Python2 改成 Python3 吧?那就本末倒置了。

JSON 的初衷就是为了实现前后端数据交互的大一统,打败 XML 的格式霸权。所以最直接的方式就是使用双引号包裹属性名,把属性变成字符串文本,避免数据的二义性,将数据纯文本化。

为什么 JSON 不选择单引号呢?双引号还要多按一个 shit 键才能打出来,实在是太麻烦了。

其实单引号也不是不行,但有的编程语言是使用双引号表示字符串/属性名的,考虑到作为数据格式的通用性,JSON 自然打开格局,使用双引号更容易吸粉,更容易兼容其他编程语言。

为什么不同时兼容单双引号两全其美呢?

因为 JSON 遵循“最小化原则”,不想整那些花里胡哨的语法支持,追求简单易用。

假设让你向一只猫猫布道 JSON 的语法,又要讲单引号又要讲双引号,实在是太麻烦了。所以不如直接绝育,像我这样专一的铲屎官不多了。

好在“JSON 之父”D.C. 也懂这个道理,所以只选择支持双引号。

为什么不选择反引号呢?

当时不是 ES6 时代,JS 自己都不支持反引号的模板字符串。JSON 的格式设计借鉴的是 JS 对象字面量语法,JS 当时不支持的语法特性谈何借鉴。

话说回来,如果不考虑双引号的人气,使用反引号说不定是正确的选择,因为 JS 代码常常和网络安全联系在一起,字符串可能带来恶意攻击,比如各种乱七八糟的注入,所以当时使用不同的边界符来写属性或许也不错。

话再说回来,一切已尘埃落定,除非我们重新发明一次 JSON。但这是不可能事件,因为 JSON 没有版本的人设,没法像 Python2 进化到 Python3 一样,搞个 JSON2,但是 JSON5 确实是存在的。

JSON5 不是 JSON 的第 5 版

除了在 VSCode 中可以用 JSON 写注释,JSON 的生态还有其他类 JSON 扩展,比如 JSON5,也致力于提供更加人性化的 JSON 语法。

JSON5 是流行的 JSON 文件格式的扩展,旨在更易于手动编写和维护(例如配置文件)。

JSON5 是 JSON 的超集,是 ES5 的子集。换而言之,合法的 JSON5 一定是合法的 ES5 代码,正如合法的 JSON 一定是合法的 JSON5。

举个粒子,JSON5 扩展了更多 JS 语法来写 JSON

JSON不是JS对象字面量,JSON爱好者的误区

如你所见,JSON5 看起来更加亲民,更准确地说,亲 ES5 用户。JSON5 的设计哲学是“JSON for Humans”,意思是人性化的 JSON,或者写给人看的 JSON

注意,JSON5 的 5 不是 JSON 的第 5 版,这里的 5 是 ES5 完美兼容 5。

事实上,JSON 有且仅有一个稳定的标准版本,没有其他增量更新的语义化版本号,这是为了规避“版本地狱”和“兼容性税”。

BTW,JSON5 的自我介绍就说了,这个数据格式的用户使用场景是更加人性化的配置文件,不是用来通信交互的,所以千万别给前端发送 JSON5,否则电波不对是会爆炸的。

前端三大地狱臭名昭著(我编的):

  1. 回调地狱
  2. 版本地狱
  3. npm 依赖地狱

JSON 为了规避“版本地狱”,没有具体的版本号信息,这样就不会出现 Python2 到 Python3 的破坏性增量迭代。

“兼容性税”则是基于用户产品思维考虑,一只懂得 JSON 语法的大猫不一定懂得 JSON5 语法,正如一只精通 Vue2 的大猫不一定精通 Vue3。其中的心智负担和学习成本就是转嫁给用户的认知税,而且增加新特性还要重新实现新的编码/解码器。

JSONP 不是 JSON

粉丝们有福利啦,最后语冰还要偷偷共享一道前端回头率超高的面试兼笔试题,谈谈你对 JSONP 的理解/阐释一下 JSONP 的实现原理,正常面试一般不会变态到让你实现,但中国特色的前端笔试又不能让我把话说死,而且这道题不算难实现,而且考点比较丰富,需要你对前后端开发有基本认知。

首先要知道 JSONP 是什么鬼物?初学者尤其容易听错题,铲屎官问的可能是 JSONP 而不是 JSON,注意不要听错了,虽然只是多了一个 P,但失之毫厘差之千里,离题万里则喵同汪讲。

JSONP 其实和 JSON 一龙一猪,之所以面试回头率超高,是因为面试时可能聊到前后端的通信和浏览器的同源策略,以及经典的前后端交互的跨域方案。一个有经验的前端/全栈攻城狮一定有邂逅这个问题,因为有经验的开发者多多少少和后端小姐姐有过前后端交互的开发经历。

源码具体实现不是今天分享的主要内容,这里语冰直接安利一个 npm 模块——jsonp,开源真香,大家直接看源码就欧了。这个库的源码不是用 TS 写的,所以大家不会有太大的心智负担,而且可能出于兼容性考虑,甚至是用 ES5 实现的,所以 ES6 入门者也能看得懂。

我们之后有机会再专门写一个 ES6 版本的实现,感兴趣的前端人赶紧关注一波,最好多点几下,粉丝数过 9 个 9 立即更新,或者在评论区许愿,日后再说。

JSON 之所以为 JSON

JSON 之父”D.C. 如是说,JSON 最大的 BUG 就是祂名为 JS 对象简谱。除了会让人误会 JSON 是 JS 对象字面量之外,还有人以为 JSON 也是 ES6 标准的一部分,但其实 JSON 自己有自己独立的标准,而且还特别好记——ECMA-404。

我想,大约是标准自带 404,所以大家才看不见这个标准......(不知道 404 什么意思,去千万别去面试!)

另外,对象这个术语也让人容易误解,因为在传统面向对象语言中,对象指的是类的实例,而在 JS 中对象可以是简单的键值对字面量、无序属性的集合。而这正是 JSON 语法格式的由来。

举个粒子,如果按照面向对象的思想来写 JSON,可能会出大问题。

JSON不是JS对象字面量,JSON爱好者的误区

如你所见,这就是面向对象风格的 JSON,结果要么报错,要么丢失面向对象编程的信息。

高能总结(TLDR 省流版)

  1. JS 对象简谱不是 JS 对象字面量,而是其严格子集
  2. JS 中的 JSON 不是抽象类,JSON 本质是一种数据交互格式
  3. JSON 不是不能支持注释,而是不想支持注释
  4. JSON 属性采用双引号是为了断舍离
  5. JSON5 不是 JSON 的第 5 版,JSON 有且仅有一个标准版本
  6. JSONPJSON 毫无关系,除非你硬要发生关系

深度学习