阅读 ECMAScript 规范
有必要先向部分初学者解释一下 JavaScript 和 ECMAScript 的区别,最开始 ECMA 仅是 European Computer Manufacturers Association (欧洲计算机制造商协会)的首字母缩写,不过随着计算机的国际化,组织的标准牵涉到很多其他国家,因此这个组织已经改名为 Ecma 国际,所以现在的 Ecma 本身就是一个名字,不再是首字母缩写。
ECMAScript 与 JavaScript 其实就是同一个东西,只是因为 JavaScript 这个名称已经被 Sun 公司注册了商标,并且不开放给 Ecma 协会使用,所以 JavaScript 的标准只能叫做 ECMAScript,而 JavaScript 可以看做是 ECMAScript 规范的一种实现。
我们为什么要学习阅读 ECMAScript 规范呢?如果你只是一名 JavaScript 的初学者,你确实没有太大的必要去阅读 ECMAScript 规范,只需要通过阅读 MDN 文档就能学习如何编写 JavaScript,但随着 JavaScript 水平的提升,我们会越来越不满足于使用,我们会想要知道更多内部的细节,然而并不是所有的 JavaScript 细节都会在 MDN 文档上说明。
下面我就举个在工作中真实遇到的场景,以此阐述我们为什么需要阅读 ECMAScript 规范。
考虑以下代码:
const obj = {
300: 300,
100: 100,
200: 200,
50.5: 50.5
}
for (const key in obj) {
console.log(obj[key])
}
你觉得这段代码的输出顺序是什么呢?答案是:100,200,300,50.5
。
这是为什么呢?
从 for…in 的 MDN 文档中,无法得到我们想要的答案,这时候就只能通过阅读 ECMAScript 规范:
通过以上信息我们得知,JavaScript 在遍历一个对象的时候,它将按照如下规则执行:
- 创建一个空的列表用于存放 keys
- 将所有合法的数组索引按升序的顺序存入
- 将所有字符串类型索引按属性创建时间以升序的顺序存入
- 将所有
Symbol
类型索引按属性创建时间以升序的顺序存入 - 返回 keys
PS:当然 for…in 是不会返回 Symbol 类型的属性的,需要使用 Object.getOwnPropertySymbols()。
以上源自我遇到的一个真实案例,详见《一行 Object.keys() 引发的血案》。
相信大家都已经非常清楚学习阅读 ECMAScript 规范的重要性,可 ECMAScript 规范也不是这么容易阅读的,所以这里提供了一些文章,帮助你快速学习阅读 ECMAScript 规范:
- How to Read the ECMAScript Specification
- Understanding the ECMAScript spec
- 你知道的 JavaScript 知識都有可能是錯的
总之,与本文一样带着问题去阅读,往往能够事半功倍。
《编程语言的设计与实现》—— 松本行弘
此书是 Ruby 语言的创造者 —— 松本行宏在《日经Linux》杂志上的连载整合而成,主要介绍了新语言 Streem 的设计与实现过程。作者从设计 Streem 这门新语言的动机开始讲起,由浅入深,详细介绍了新语言开发中的各个环节,以及语言设计上的纠结与取舍,其中也不乏对其他编程语言的调查与思考,向读者展示了创建编程语言的乐趣。
笔者现在刚看完第二章,不过也可以谈谈我的阅读感悟:作为一名野生前端,我对编译原理可谓是一窍不通,顶多也就写个 Babel 小玩具的水平,像《编译原理》这种专业书,我是连前十页都啃不下去,好在日系书籍有一个很大的特点就是:浅显易懂,此书也不例外,在前两章就带领读者如何通过 lex 进行词法解析,然后通过 yacc 进行语法解析,这过程还会将编译原理中的一些知识带出来,譬如 BNF(巴科斯范式)、窥孔优化等。
除了编译原理以外,我们还可以通过本书学习如何站在语言设计者的角度去思考语言的特性,为什么要这么设计,从而使我们的视野更加开阔,所以建议每一位开发者都阅读本书(对我这种野生程序员尤为重要)。
本周见闻
CSS 属性使用次数排行榜
Chrome 使用匿名使用统计数据计算每个在 Chrome 浏览器加载的页面中 CSS 属性出现的次数,数据的实时性大概在 24 小时之内。
以下截取部分排名靠前的 CSS 属性:
一些 tips
为什么 HTTP 301 后会把 POST 转为 GET?
根据 RFC 7231, section 6.4.2: 301 永久重定向 指出:
Note: For historical reasons, a user agent MAY change the request method from POST to GET for the subsequent request. If this behavior is undesired, the 307 (Temporary Redirect) status code can be used instead.
简而言之就是因为历史原因,当某些 HTTP/1.0 客户端收到该状态码时,可能会将 POST 方法改为 GET 方法,继续向新地址发出请求,这是错误的实现——故而后续标准引入了 HTTP 307。
所以最好只在 GET
或 HEAD
方法时使用 301,其他情况使用 307 或者 308 来替代 301。
JavaScript 的数字安全范围
你会如何解释这段代码:
9007199254740992 === 9007199254740993 // true
我们知道 JavaScript 的数字是用 64 bit 來存,而且遵循的规格是 IEEE 754-2019,既然用 64 bit 来存,那可以表示的数字自然是有限的。
我们可以用 Number.MAX_SAFE_INTEGER
表示 JavaScript 最大正整数的安全范围,也就是 2^53 - 1
= 9007199254740991
。
这里所说的安全指的是:能够准确区分两个不相同的值,例如 Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2
这在 JavaScript 上是成立的,但它在数学上是错误的,我们可以使用 Number.isSafeInteger()
来判断一个数字是否是一个「安全整数」。
需要注意的是,最大的安全范围不代表 JavaScript 只能存储 Number.MAX_SAFE_INTEGER
这么大的数字,其实我们最大可以存储 Number.MAX_VALUE
也就是 1.7976931348623157e+308 ,只是它不在安全范围之内罢了。
总之,对于一些比较大的数字(譬如 uuid 这类),优先考虑是否使用 String 类型,如果一定要数字类型,可以了解下 BigInt。
为什么「Enter键」要被翻译为「回车键」?
其实「回车」并不是 “Enter” 的翻译,而是 “return” 的翻译。这个 return 其实指的是 “↵+Enter” 中箭头的意思,换言之,Enter 并不是“↵”的一个解释,严格讲 “Enter” 和“↵”是这个键的两种不同的名称,也即两个不同的用途。
之所以会被翻译成「回车」,是因为现代电脑键盘是从过去的打字机上继承过来的,在过去的机械打字机上有个部件叫「字车」,每打一个字符,字车前进一格,打完一行后,我们需要让字车回到起始位置,而 “Return” 键最早就是这个作用,因此被翻译为「回车」。
有兴趣可以看看这个视频:
分享文章
DeepL Api 设计中的欺骗战术
本文作者通过逆向 DeepL 的 Windows 客户端(C#),破解了 DeepL 如何实现接口防滥用。
直接说结论,其实 DeepL 并没有使用一些常规的方法(譬如 token、签名等)去实现接口防滥用,而是通过两个非常取巧的方法去把开发者绕晕:
- timestamp 参数并不是一个真实的时间戳,而是通过时间戳和源文本的长度进行伪造的,公式是:
ts - ts % i_count + i_count
,由于与真实的时间戳仅有毫秒部分的差别,一般人无法直接看出端倪。 - id 参数就是一个随机数,只不过后续的请求会在此基础上 + 1,并且这个 id 会决定文本中一个小小的、微不足道的空格。但由于我们通过拿到结果后都会先对 JSON 进行一下格式化,所以很容易忽略这种细节。
如果不是逆向源代码,相信一般人很难发现这两点细节,不得不感叹 DeepL 工程师的脑洞。
Cloudflare 如何将网站加载时间缩短 30%
本文介绍 Cloudflare 在 2021 年发布的一个新特性:Early Hints,准确来说它是一个 Web 标准,它定义了一个新的状态码 103。
其最核心的功能是:在服务器响应 200 时,先向客户端响应 103,其响应内容包含这个网页所需呈现内容的资源提示,客户端可利用此提示加载页面速度,如下图:
在上面提到的 RFC 中可看到 HTTP 103 的响应大概长这样(其中可能会有多个 103 响应):
Client request:
GET / HTTP/1.1
Host: example.com
Server response:
HTTP/1.1 103 Early Hints
Link: </style.css>; rel=preload; as=style
Link: </script.js>; rel=preload; as=script
HTTP/1.1 200 OK
pubDatetime: Fri, 26 May 2017 10:02:11 GMT
Content-Length: 1234
Content-Type: text/html; charset=utf-8
Link: </style.css>; rel=preload; as=style
Link: </script.js>; rel=preload; as=script
<!doctype html>
[... rest of the response body is omitted from the example ...]
我们可以在 Chrome 94 及更新版本使用该特性,关于更多内容可见:early-hints。
有趣的链接
-
JavaScript 二十年:这本书纪录了从 1995 年到 2015 年这二十年的 JavaScript 历史,看完它会对 JavaScript 有不同的体会(还会知道很多冷知识),此为中译版,原版地址:JavaScript: The First 20 Years。
-
js-quirks:关于 JavaScript 的一些怪癖语法说明,对于想要实现 JavaScript 解析器的同学很有帮助和启发。
-
Moonvy 月维:探索「设计生产力」之道,与你一起, 创造设计师与开发者的必备工具。
-
QUOKKA:Quokka 是一个调试工具,可以为您正在编写的代码提供实时反馈(可惜大部分功能都要收费。
-
Free Ukraine icons:一些与乌克兰战争有关的免费 icon 图标。
-
Adobe Creative Cloud Express:Adobe 新推出的一个设计工具,可提供快速「去除背景」、「转换为 GIF」、「合并 PDF」以及更多高级操作。
-
UX Design Challenges | UX Tools:一些 UX 的挑战,帮助你学习如何提高产品的用户体验。