YDKJS 2nd 1-1 笔记

作者: 科技微讯

日期:

You Don't know JS 第二版 Get Started 第一章笔记.

TC39 是制定 ECMAScript 标准的委员会, 由 50-100 位成员组成, 这些成员来自各大科技公司, 他们通常隔一个月见一次面, 一次面通常持续三天. ECMAScript 标准的制定需要经过 5 个步骤, 从 stage 0 到 stage 4. stage 0 大概是指 TC39 的某位成员觉得某个主意很棒, 并把这个主意写成 proposal 提交给委员会.

这意味着所有普通程序员在社交网络/博客等地方提出的主意甚至都没有进入 stage 0 阶段, 除非这个主意被 TC39 的某位成员看到并认同, 且愿意去为之努力, 这个主意才进入 stage 0. 当然, TC39 在 github 列出了所有 proposal, 允许所有用户参与讨论. 从 stage 0 到 stage 4 可能要几个月到几年的时间.

JS 发展了这么多年, 已经可以运行在多种环境中, 例如浏览器, 服务器, 机器人, 甚至电灯泡等等, 但处于统治地位的依然是浏览器. TC39 制定的 JS 标准, 和运行在浏览器的 JS 几乎是一样的, 但也有一些区别.

有时候 TC39 为 JS 制定了某条标准, 但可能会影响老旧网站的运行, 这时候浏览器可能会选择不遵守这条标准. 而有时候, TC39 想制定的某条标准可能和已有网站使用的 JS 冲突, 那 TC39 可能会更改这个标准. 例如 Array 的一个叫 includes() 的方法本来想叫 contains(), 但一些老旧网站所使用的框架已经占用了 contains(), 所以才改为 includes. 类似地, 本来想叫 flatter() 的方法后来改成了 flat().

为了避免老旧网站无法正常运行, 浏览器并不会完全遵守 ECMAScript 标准, 于是 TC39 在 ECMAScript 标准上添加了一个 Appendix B 的附录, 即 "Additional ECMAScript Features for Web Browsers". 在 Appendix B 列出的 JS 功能特性并不属于 ECMAScript 的核心标准, 如果可以, 我们应该避免写那些只适用于某个运行环境的 js 代码.

所有浏览器都支持 alert(), 但它并不属于 ECMAScript 标准的一部分, 并且, 也没有出现在 Appendix B. 为什么呢?

因为 alert() 只是浏览器提供的一个 api, 这个 api 是浏览器在全局环境下提供的, 是浏览器才支持的 api, 这些 api 通常称为 web api, fetch(), getCurrentLocation() 等等都是 web api.

Node.js 也有自己的 api, 例如 fs.write() 等等.

console.log() 也不是 ECMAScript 标准的一部分, 但几乎所有 JS 运行环境, 包括浏览器, Node.js 等等, 都支持 console.log() 甚至整个 console 对象, 但是不同环境下, console.log() 的表现不完全一致.

以上 api 虽然不属于 ECMAScript 的官方标准, 但它们都是各自的运行环境使用 JS 的官方语法定义的 api, 它们本质都是 js 代码, 否则也不能正常运行, 它们只是不属于 js 官方定义的标准.

不要以为浏览器控制台看到的信息完全符合 js 标准.

编程语言的范式大概有 3 种, 分别是 procedural, OO, FP. 一些语言很明显偏向于某种范式, 例如 C 是 procedural, Java 是 OO, Haskell 是 FP, 但 JS 是多范式语言, 我们可以用 procedural 或 OO 或 FP 或三者结合写 JS 代码.

JS 是一门向后兼容的语言, 某些标准一旦被加入 ECMAScript 中, 那未来对 ECMAScript 的任何更新几乎都不会导致这个标准失效或作废, 当然也有例外, 但很少见. 所以 1995 年的网站所写的 js 代码, 如无意外至今还能运行, 这意味着作为开发者的我们只要写符合标准的 js 代码, 就可以保证这个网站可以被一直更新迭代的浏览器所支持.

但 JS 不是一门向前兼容的语言, 这意味着如果用新的 ECMAScript 标准写 js 代码运行在旧的浏览器中, 可能会出错.

总结下, 兼容性是从运行环境的视觉看, 向后兼容就是新的浏览器可以运行旧的代码, 向前兼容就是旧的浏览器可以运行新的代码, js 只向后兼容, 不向前兼容.

css 和 html 都是向前兼容但不向后兼容, 旧的浏览器可以打开新的代码, 对于不认识的新的代码, 它会直接忽略, 并不会导致网站出错, 而新的浏览器打开旧的 css/html 代码可能会导致网站出错.

JS 标准经常进化, 程序员应该用新的标准写代码, 因为新标准往往可以减少代码出错的概率, 甚至减少代码行数, 增加可读性等等. 那如何解决浏览器不向前兼容的问题呢?

对于新的语法, 可以借助 Babel 这种工具把新标准的代码 transpile 成旧标准的代码, 再交给浏览器运行. 例如有些旧版浏览器可能无法识别 let 这个声明变量的词, 于是可以用 Babel 把相关代码自动改写成用 var 声明变量.

对于新的方法或 api, 可以通过所谓的 polyfill 或 shim 解决. 例如 finally() 是 ES2019 为 promise 增加的新的方法, 旧的浏览器不认识 finally(), 当它遇到这行代码时就会出错. 解决方法是用工具把 finally() 自动改写成旧浏览器支持的代码, 通常是重新定义一个名称和新方法名称一样的方法, 对于这个例子, 就是自定义一个叫 finally 的函数, 确保它的作用和 ES2019 的 finally() 一致, 然后把这个自定义函数添加到 Promise 的 prototype 中, 这个自定义的名叫 finally 的函数就是所谓的 polyfill.

很多人认为 JS 是一门解释型 (interpreted) 语言, 但 YDKJS 的作者因为它更偏向于是一门编译型 (compiled) 语言, 他认为区分 js 是解释还是编译, 主要看它是如何处理 error.

下图是 interpreted 语言的运作模式, 一行一行地执行并马上输出结果, 只有在执行时才能发现 error.

interpreted language
interpreted language

下图是 compiled 语言的运作模式, 代码先 parsed, 即由一种形式转变成另一种形式, 最后转变成 0 1 这种二进制代码, 然后再执行输出结果.

compiled language
compiled language

YDKJS 的作者认为 js 的运行模式属于第二种, js 是所谓的 compiled 语言.

JS 的 parsing/compilation 过程影响了它的工作效率, 后来出了 Web Assembly 解决这个问题, Web Assembly 的 parsing/compilation 这个步骤在交给 js 引擎执行之前就已经完成, js 引擎拿到的是不再需要编译的 binary-packed 程序.

strict mode 在 2009 年就出现了, 但直到现在还没有成为默认模式. 这是因为 js 是向后兼容的, 默认 strict mode 可能会导致旧代码无法运行在新的浏览器中, 所以 strict mode 需要开发者主动开启. 由于 ES6 模块已经默认使用 strict mode, 所以越来越多的 js 代码向 strict mode 看齐. strict mode 并不是一种限制, 而是一种让程序员写出更好的代码的指引.