跳转至

第 1 章 用 JavaScript — 全书用到的语言特性与工具

这一章讲什么: 原书全部代码用现代 JavaScript 写成。这一章把后面反复出现的语言特性 逐个讲清:箭头函数、类、展开运算符(...)、解构赋值、两种模块格式(CommonJS 与 ECMAScript 模块)、闭包与立即执行函数;后半章是工具箱:编辑器、格式化、静态检查、 类型工具、兼容性查询。 它在全书链条里的位置: 地基章。后面每一章的代码都建立在这些特性上, 尤其是箭头函数 + 解构 + 模块这三样,几乎是所有算法实现的标准写法。

1. 顶层全景:一章一张图

  语言特性(写代码的笔法)
    ├─ 箭头函数:更短的函数写法,省略 return
    ├─ 类:把「原型继承」包成普通的类语法
    ├─ 展开运算符 ...:把一个数组/对象「摊开」成一个个值
    ├─ 解构赋值:一行里同时给多个变量赋值、交换两个变量的值
    ├─ 模块:把一个文件里要公开的函数列一张清单,别的文件按清单取用
    │    ├─ CommonJS(Node.js 的老格式):module.exports + require
    │    └─ ECMAScript 模块(新格式,浏览器通用):export + import
    └─ 闭包 + 立即执行函数:函数记住自己出生时的环境,可用来造「私有变量」

  工具(让代码更好看、更少错)
    ├─ Visual Studio Code:写代码的编辑器
    ├─ Prettier:替你统一代码格式,终结「哪种缩进好」的争吵
    ├─ ESLint:静态检查,把可疑写法(如 == )标出来
    ├─ JSDoc / Flow / TypeScript:文档生成 / 给 JS 加类型标注
    └─ Kangax、Can I Use?:查某个特性在哪个浏览器能用

  图说:左半是「语法」,右半是「工具」。全书只依赖左半;右半是作者推荐的工作习惯。

2. 主线例子:一个数组走完这一章的大部分特性

这一章的特性散,原书自己在各处拿不同的例子演示。我们把它们串成一条线, 从一个具体输入 [3, 1, 4, 1, 5, 9, 2, 6] 出发走完大部分特性 (数组里的数是原书给的21):

第 ① 步:展开运算符把数组「摊开」。

Math.max() 本来要你一个一个传参:Math.max(3, 1, 4, …)。 想直接传一个数组,就在数组前面加 ...:

const myArray = [3, 1, 4, 1, 5, 9, 2, 6];
const arrayMax = Math.max(...myArray);   // 等价于 Math.max(3,1,4,1,5,9,2,6),结果是 9
const newArray = [...myArray];           // 复制一份一模一样的新数组

...myArray 的意思就是「把这个数组的成员挨个摊开摆在这儿」。 所以 Math.max(...myArray) 等于把 8 个数分别递进去,最大的是 9; [...myArray] 等于用同样的成员拼出一个新数组——这是全书复制数组的标准写法22。

第 ② 步:解构赋值一行取多个值、不用临时变量交换。

[first, last] = ["Abraham", "Lincoln"];        // first 得到 "Abraham",last 得到 "Lincoln"
[heap[p], heap[i]] = [heap[i], heap[p]];        // 交换 heap 里第 p 个和第 i 个元素
let [smaller, bigger] = (22 > 9) ? [9, 22] : [22, 9];  // smaller==9,bigger==22

方括号在等号左边,意思是「按位置拆开右边的东西,挨个赋值」。 中间那行是后面第 14 章堆实现里的真实代码:交换两个数组元素, 老写法需要一个临时变量中转,解构一行就够,还少了出错的中转步骤23。

第 ③ 步:箭头函数把这些操作收成函数。

const myMax = (...nums) => {
  let max = nums[0];
  for (let i = 1; i < nums.length; i++) {
    if (max < nums[i]) max = nums[i];
  }
  return max;
};
myMax(...myArray);   // 同样是 9

(...nums) 是「其余参数都收进 nums 这个数组」;函数体只有一行时还可以更短: const arrayMax2 = (arr) => Math.max(...arr);——没有花括号、没有 return, 箭头后面那一行表达式的值就是返回值。全书大量使用这个省写24。

第 ④ 步:模块把函数打包、只公开清单上的名字。

把第 ③ 步的 myMax 存进 my_tools.js,别的文件想用它,有两种写法。 老格式(CommonJS,Node.js 传统):

// my_tools.js 里:
module.exports = { myMax };              // 公开清单:只有列出来的能被外面用
// 别的文件里:
const { myMax } = require("my_tools.js"); // 按清单取,只取这一个

新格式(ECMAScript 模块,浏览器标准):

// my_tools.js 里:
export { myMax };
// 别的文件里:
import { myMax } from "my_tools.js";

两种格式效果一样:文件里没列进公开清单的东西,外面碰不到。 这是原书所有数据结构实现文件的统一组织方式2。

3. 核心原理:逐个特性讲透

3.1 箭头函数:省掉 return,也改变了 this

JavaScript 定义函数的办法很多(具名函数、匿名函数表达式、new Function()), 书里统一用箭头函数,因为它有两个实际好处。

第一个好处:单表达式可以省略 return 和花括号。 第 12 章会反复出现这种函数:

const _getHeight = (tree) => (isEmpty(tree) ? 0 : tree.height);

意思是:树为空就返回 0,否则返回它的 height 字段。等价的长写法要 4 行; 书里给的判断很直白:「更短的代码就是更好」24。 要返回对象时必须给对象套一层括号,如 const newNode = (key) => ({ key, left: null, right: null }), 否则花括号会被当成函数体25。

第二个好处:参数可以设默认值。 递归函数打印二叉树时, 层间缩进的字符串参数可以写成 s = ""——调用时不给第二个参数,它就自动是空串26。

代价要知道: 箭头函数不能当构造函数(不能 new),没有自己的 this, 也没有 arguments 对象1。在算法代码里这三条几乎不碍事—— 这正是全书敢放心用它写所有实现的原因。

3.2 类:把老派的「原型继承」包成普通模样

JavaScript 早期没有类,想实现「一个类型继承另一个类型」要直接操作原型, 代码绕。现在的 class 语法让这件事和普通语言一样直白了:

class Tree {
  _children = [];                     // 字段可以直接写在类体里
  constructor(rootKey) {              // 构造函数: new 时自动执行
    this._key = rootKey;
  }
  isEmpty() { return this._key === undefined; }
  get key() { /* 读 .key 时执行 */ }
  set key(v) { /* 写 .key 时执行 */ }
}

要点:字段不必都塞进构造函数;get/set 把一对函数伪装成一个属性, 读 tree.key 和写 tree.key = v 时各自触发对应的函数; class BinaryTree extends Tree 表示继承。 书里说得很坦白:这本书其实不太用类——后面几乎所有数据结构都用「一个工厂函数返回一个对象」 的轻量写法,类只在第 13 章的多叉树等地方出现27。 补充(不在书里,来自通用知识):this._key 下划线开头只是「约定为私有」, 真正的私有字段(#key)是 ECMAScript 2022 才进标准的,书中未用。

3.3 展开运算符:三个用途,一个「摊」字

展开运算符就是三个点 ...。它做的事永远是「摊开」:

用途 写法 效果
函数调用里摊开实参 Math.max(...myArray) 数组成员挨个变成独立参数
复制数组 [...myArray] 得到一个成员相同的新数组
复制并改对象 {...myObject, year: 1882} 先复制所有字段,再覆盖 year,其余不动

第三行是全书改对象的标准手法:与其逐字段赋值,不如先展开再覆盖, 一行写完、不容易漏28。 早期的替代物 arguments(函数里自带的类数组对象)只有 .length 一个数组属性, 功能弱得多,被 ...nums 参数取代29。

3.4 解构赋值:位置对位置,一次分完

解构的本质是「按位置配对赋值」,方括号或花括号在等号左边出现时就触发。 四种高频用法,每种后面章节都会撞见:

  1. 一次取多个值:[first, last] = ["Abraham", "Lincoln"];
  2. 剩下全给最后一个:[first, last, ...years] = ["Abraham", "Lincoln", 1809, 1865], 后两个数收进 years 数组;
  3. 默认值:[first, last, role = "President"] = ["Abraham", "Lincoln"], role 没有对应值就用默认值;
  4. 交换/轮转:[heap[p], heap[i]] = [heap[i], heap[p]] 交换两个变量, [d, e, f] = [e, f, d] 轮转三个——都不需要临时变量30。

还有一种用途:函数一次返回多个值。让函数返回一个数组, 调用处用解构一次接走:let [smaller, bigger] = order2(22, 9)。 比返回对象再逐字段取更紧凑31。

3.5 模块:一份公开清单,两种格式

模块解决的是一个组织问题:文件多了之后,哪些函数给谁用,要有明文约定。 书里的设计原则是两个词:

  • 高内聚(high cohesion):同一个文件里的东西要真的属于同一主题;
  • 低耦合(low coupling):文件之间互相依赖越少越好,改动一个文件尽量不惊动别的文件32。

每个模块文件末尾列一张「公开清单」,清单外的函数是内部实现细节, 外面看不见、也依赖不上——这就是「黑盒」:只要清单不变,内部随便重构, 使用方无感33。

CommonJS(Node.js 老格式): 用 module.exports = { … } 列清单, 用 require("文件名") 取。取的时候可以解构只拿需要的: const {newRadixTree, add} = require("radix_tree.js")—— 作者明说更喜欢这种写法,因为「我用到了什么」一目了然34。

ECMAScript 模块(新标准,浏览器原生支持): 用 export 标记公开项 (可以在定义处标,也可以集中在文件末尾标),用 import { … } from "文件名" 取; import * as RadixTree from … 则是把整个模块当一个对象取走。 还有一种「默认导出」(export default),一个文件最多一个, 导入时可以随意起名字35。

补充(不在书里,来自通用知识):Node.js 里想用 import/export, 文件后缀得用 .mjs(或在 package.json 标 "type": "module"), .js 默认仍按 CommonJS 处理——书中原注只提了 .mjs 一事36。

3.6 闭包与立即执行函数:不靠语法的「私有」

闭包这个词听着玄,指的是一件很具体的事: 函数记得自己定义时所处的环境,即使那个环境已经执行完了。

书里给的例子是一个「造人」函数,它内部有两个局部变量 first、last, 返回的对象带着几个函数。这些函数诞生在那个环境里,所以事后还能读写 first、last; 而外部直接访问 me.first 得到的是 undefined——这两个变量只有返回的那几个函数碰得到, 效果上就是私有变量37。

立即执行函数表达式(IIFE,读作「iffy」):定义一个匿名函数,当场调用它。 把闭包套进去,就造出了模块出现之前 JavaScript 模拟模块的标准姿势:

const tax = (function (basicTax) {
  let vat = basicTax;               // 私有:外面碰不到
  return { getVat: () => vat, setVat: (v) => { vat = v; }, addVat: (x) => x * (1 + vat / 100) };
})(6);                              // 定义完立刻执行,6 是初始税率

tax.getVat();    // 6
tax.setVat(8);
tax.addVat(200); // 200 × 1.08 = 216

书里特意交代:后面第 5 章做记忆化(memoization,把算过的结果存起来下次直接用)、 预计算数值表时会用到闭包 + IIFE 这个组合38。

3.7 版本、转译与工具箱

语言标准叫 ECMAScript(由 ECMA 组织维护,规范全文公开), Mozilla 的 MDN 网站是查每个特性用法的日常参考39。

转译(transpile):把新语法翻译成等价的旧语法。 浏览器或 Node.js 版本跟不上新标准时,用 Babel 转译后就能跑。 作者全书的代码都在当时的 Node.js 上跑通过40。

工具箱其余几件,一句话一件:

  • Visual Studio Code:免费开源的编辑器,自带调试、终端、Git 集成; 它和微软的 Visual Studio 除了名字像没有关系,它是用 JavaScript 写的、 借 Electron 框架打包成桌面程序41。
  • Fira Code 字体:等宽字体,把 >=、!= 这类两个字符的符号显示成合字, 纯视觉改善42。
  • Prettier:「有主见的」格式化工具——它不问你想怎么排版,它按自己的规则排, 存在的意义是终结团队里关于格式的争吵(引它官网的话:「采用 Prettier 最大的理由是 停止所有关于风格的争论」)。全书代码都用它排版43。
  • ESLint:静态检查器(linter),不运行代码、只读代码,把可疑写法标出来。 书里举了两个典型坑:用 == 而不是 === 会发生「x==y、y==z 却 x!=z」的怪事 (可以拿 x=[]、y=0、z="0" 试);把 if (x == y) 误敲成 if (x = y) 就变成了赋值。ESLint 的 eqeqeq 规则管第一件,no-cond-assign 管第二件44。
  • JSDoc:在函数上方按约定格式写注释(以 /** 开头,带 @param、@return 等标签), 工具扫一遍就能生成一份文档网站。书里不用它,因为正文就是在讲代码45。
  • Flow 与 TypeScript:给 JavaScript 加类型标注的两条路。 Flow 靠注释标注;TypeScript 是 JavaScript 的超集(多出来的主要是类型语法), 写完后转译回普通 JavaScript 运行。书都不用,但建议大项目采用46。

兼容性查询:前端要照顾旧浏览器时,两个网站可查特性支持情况—— Kangax 的兼容表(按 JavaScript 引擎列出每项特性是否可用,绿格子 = 放心用) 和 Can I Use?(按浏览器查,还能看全球有多少比例用户能直接用)。 查不到支持就用 Babel 转译;「polyfill(垫片)」指在旧环境里补上新 API 的实现47。

3.8 类的完整走读:第 13 章那个 Tree 类到底写了什么

原书从第 13 章借来一个真实的类当例子,我们逐行走一遍, 每一行后面写清它做的事——这也是你读后面章节所有类代码的方法3:

class Tree {                          // ❶ 定义一个叫 Tree 的类
  _children = [];                     // ❷ 每个实例自带的字段:孩子列表,初始为空数组

  constructor(rootKey) {              // ❸ 构造函数:new Tree(某键) 时自动执行
    this._key = rootKey;              //    this 指新造出来的这个实例
  }

  isEmpty() {                         // ❹ 普通方法:tree.isEmpty() 调用
    return this._key === undefined;
  }

  get key() {                         // ❺ 访问器:读 tree.key 时执行,不是读字段而是调函数
    this._throwIfEmpty();
    return this._key;
  }

  set key(v) {                        // ❻ 访问器:写 tree.key = v 时执行
    this._key = v;
  }
}

走一遍:const t = new Tree(22) 时,构造器把 22 存进实例的 _key; t._children 是 [];t.isEmpty() 返回 false(因为 _key 是 22 不是 undefined); t.key 看上去像读字段,实际触发 ❺ 的函数——它先检查树为空就抛错,再把 _key 交给你。 访问器的用处是在读写之间插入逻辑:以后想改成「每次改 key 都记日志」, 只要改 set key 一处,所有 t.key = … 的调用处一行不动。

继承一行就够:class BinaryTree extends Tree 表示 BinaryTree 拥有 Tree 的全部方法, 再补自己的。对比老式的原型写法要写一大串 .prototype 赋值——这就是作者说 「类让继承直接了许多」的具体所指。还有一类成员书上没展开:静态(static)属性和方法 属于类本身而不属于实例,比如「这个类一共造了多少个对象」这类计数适合放静态字段4。

3.9 一个真实模块文件的两种写法

第 16 章的字典树模块 radix_tree.js 在原书里出现了两次:同一批函数, 先用 CommonJS 写一遍,再用 ECMAScript 模块写一遍。把两种写法并排看, 差别只剩「公开清单怎么列、怎么取」这一处5:

环节 CommonJS(老,Node.js) ECMAScript 模块(新,浏览器标准)
公开清单 文件末尾 module.exports = { add, find, … } 定义处写 export const …,或末尾 export { … }
私有部分 没进清单的(如 EOW、newNode)外部不可见 没标 export 的同样不可见
取用什么 const {newRadixTree, add} = require("radix_tree.js") import {newRadixTree, add} from "radix_tree.js"
全部取走 const RadixTree = require("radix_tree.js") import * as RadixTree from "radix_tree.js"

这张表值得记住的不是两种语法,而是同一套语义:一份公开清单划定边界, 边界内自由重构,边界外按清单取用。第 3 章讲抽象数据类型时会看到完全同构的思想—— 「对外只承诺行为,对内随便实现」,在代码组织层面就是模块,在数据层面就是 ADT。

为什么作者两种都写?因为生态正处在交替期:Node.js 服务器端存量代码大量是 CommonJS,浏览器和新建项目走 ECMAScript 模块。书里服务器端代码用 require, 并注明 Node.js 用新格式需要 .mjs 后缀6。

3.10 闭包分步走:变量为什么「私有」

闭包一节值得把执行顺序摊开,因为它是「函数记住出生环境」这句话的唯一硬核证据。 拿书里的 createPerson 走一遍7:

第 1 步:调用 createPerson("Federico", "Kereki")。 函数体开始执行,局部变量 first = "Federico"、last = "Kereki" 出现在内存里。 此刻它们只是普通局部变量。

第 2 步:构造并返回一个对象,对象里装着四个函数:getFirst、getLast、 fullName、setName。注意:函数只是被「造出来」装进了对象,都还没有被调用。

第 3 步:函数执行完毕,局部变量本该消失——但没有。 因为返回的那四个函数体内还引用着 first 和 last,JavaScript 规定: 只要还有东西引用它们,它们就不能回收。函数把它出生的那一层环境「带在了身上」, 这就是闭包。

第 4 步:事后调用 me.getFirst(),得到 "Federico";me.setName("John", "Doe") 之后再调 me.fullName(),得到 "John Doe"。 读写走的是同一对变量。

第 5 步:从外部直取 me.first,得到 undefined。 对象上没有这个属性——那对变量不在对象上,只在闭包里, 而闭包的入口只有返回的那几个函数。私有不是声明出来的,是「够不着」天然形成的。

IIFE 只是给这套机制加了一个「定义即执行」的外壳,让上述五步在一次表达式里完成, 于是文件一加载,一个自带私有状态的「模块对象」就存在了。第 5 章记忆化会再用一次: 缓存表就是那个私有变量,返回的函数每次先查表再决定算不算7。

3.11 工具箱串成一条工作流

散件看完,串成作者每天实际使用它们的顺序(这是他「开始新项目最先装的几样」的原文意思):

  1. 写:在 Visual Studio Code 里建项目,配好 Fira Code 字体——从此 != 显示成 ≠;
  2. 存盘瞬间:Prettier 插件(打开 editor.formatOnSave)自动把代码排成统一格式, 团队里没人再讨论缩进;
  3. 边写边查:ESLint 插件实时标红——== 写成 ===、条件里误敲赋值,存盘前就看见; 大项目再叠加 Flow 或 TypeScript 把类型错误也在写的时候拦住;
  4. 交付前:用 ESLint 的 Git 钩子(precommit hook)兜底,检查不过的代码提交不了;
  5. 要兼容旧浏览器:先查 Kangax / Can I Use?,缺支持就 Babel 转译、缺的 API 补 polyfill;
  6. 给别人用:函数上方写 JSDoc 注释,跑一遍工具生成文档站8。

这套流程里没有一步是「算法」——但它决定了算法代码以什么面貌被写出来、被维护下去。 书里所有代码默认处在这条流水线的末端:Prettier 排过版、在当前 Node.js 上跑通。

3.12 函数定义的五种方式:为什么全书只留一种

JavaScript 里定义函数的路子多,书里在开头就列了五种,然后说「工作起来基本一样」, 随后只挑箭头函数用9:

  1. 具名函数声明 function alpha() { … } ——最常见的传统写法;
  2. 匿名函数表达式 const bravo = function () { … } ——函数没有名字,赋给了变量;
  3. 具名函数表达式 const charlie = function something() { … } ——函数有内部名 something,但外部只能通过 charlie 调用;
  4. new Function() 构造 const delta = new Function(…) ——把代码当字符串传进去造函数, 几乎只用于特殊场景(动态生成代码),平时别碰;
  5. 箭头函数 const echo = () => { … } ——本章主角。

五种里真正竞争的是第 1 种和第 5 种。差别不在「能不能用」,而在三个具体行为上, 我们逐个说清「为什么算法代码里它们不重要」:

  • 没有自己的 this:传统函数的 this 随调用方式变来变去(这是 JavaScript 最著名的坑);箭头函数的 this 就是它定义处的 this,永远不变。 而算法代码几乎全是「纯函数」——输入参数、返回值,不碰对象状态—— this 是死是活根本无关。
  • 不能当构造函数:不能 new 一个箭头函数。书里造节点、造树全靠返回对象的 工厂函数(const newNode = (key) => ({ … })),同样不需要 new。
  • 没有 arguments 和 prototype:不定参数改用 (...nums) 收集, 更直观;算法代码不写原型方法。

所以作者的「五种基本一样、我只用箭头函数」背后是一句没明说的判断: 这本书里的代码全是纯函数式的,箭头函数砍掉的三样能力一样也用不上。 这是第 2 章函数式主张在语法层面的伏笔。

3.13 「现代」具体指什么:标准与版本的时间线

书里说的「现代 JavaScript」不是一个模糊赞美,指的是 ECMAScript 2015(俗称 ES6) 及之后加进去的那批特性。时间线捋一遍(补充,不在书里,来自通用知识; 书里的对应说法是它「自 1995 年的最初版本以来不断演化」10):

  • 1995 年:JavaScript 诞生,最初只为在浏览器里做简单的页面交互;
  • 1997–2015 年:标准几乎停摆,社区靠各种补丁和框架度日,跨浏览器兼容是头号噩梦;
  • 2015 年:ECMAScript 2015 一次引入类、模块、箭头函数、Promise、解构、展开 等一揽子特性——本章讲的几乎全部特性都来自这次爆发;
  • 2015 年之后:改为每年一版小更新(ES2016、ES2017……),特性增量进入, 比如 ES2022 的私有字段。

这条时间线解释了书里的两个现象:一是为什么 CommonJS 和 ECMAScript 模块 两种格式并存——模块标准化之前,Node.js 等不及、自己先定了 require; 二是为什么需要 Babel 转译和 Kangax / Can I Use? 这类查询站—— 新特性进标准的速度永远快于旧环境实现它的速度。 作者把话说得很实用:「我在当前版本的 Node.js 里跑了所有代码,没遇到问题」—— 服务器端永远跑最新版,兼容性主要是前端的事11。

3.14 回到主线例子:把整条链走完整

第 2 节那条主线,现在把每一步的「为什么非它不可」补上,这也是对全章的收束:

  1. Math.max(...myArray) 得 9 —— 展开解决「函数只收散值,我手里是数组」 的不匹配。没有这个特性,就得写循环逐个比较,或借用第 ③ 步的 myMax;
  2. [...myArray] 得副本 —— 排序类算法(第 6 章起)常常不能动输入数组, 复制是入场动作;
  3. 解构交换 [heap[p], heap[i]] = [heap[i], heap[p]] —— 堆调整时父子节点 互换位置是最高频的操作,一行写完不留临时变量,临时变量正是这类代码里 最容易写错名字的;
  4. 箭头函数收成 myMax = (...nums) => { … } —— 把前三步的写法封进一个 可复用的函数;...nums 顺便替代了老派的 arguments;
  5. 模块公开 module.exports = { myMax } 或 export { myMax } —— 函数进入 公开清单,测试文件用解构取走它:const { myMax } = require("./my_tools")。

五步串起来就是全书的代码观:数组是默认的数据载体,展开/解构是操作数组的笔法, 箭头函数是封装笔法的方式,模块是封装函数的边界。 后面十七章的所有数据结构实现,都是这五步的不断变奏。

3.15 一张「特性 → 出场章节」对照表

这一章的特性不是讲完就完,它们在后面的章节各有固定岗位。书里 scattered 的引用 收拢成一张表,读后面章节卡住时可以回来查12:

特性 后面在哪里重度使用 干什么用
箭头函数省 return 全书所有章节 一行函数:取高度、取大小、比较器
参数默认值 s = "" 第 12 章打印二叉树 递归时携带缩进前缀
返回对象加括号 第 12 章 newNode() 造树节点
[...arr] 复制 第 6 章起所有排序 不动输入数组,先复制
解构交换 第 14 章堆的上浮下沉 父子节点换位
解构取模块函数 每章的测试代码 只取被测函数
闭包 + IIFE 第 5 章记忆化 缓存表做私有变量
get/set 第 13 章 Tree.key 读键前做空树检查

这张表同时回答了「为什么偏偏是这几个特性」:它们每一条都对应后面某个 反复出现的具体动作。删掉任何一条,后面的代码就会明显变长或变险。

3.16 工具部分的数字与参照

工具一节书里没有给数字,我们补两个有参照物的量感(补充,不在书里,来自通用知识): Visual Studio Code 的更新节奏是每月一个版本——书里的原话是「updated monthly」, 作为对照,大多数桌面软件是一年一到两个大版本,月更意味着修 bug 和加特性的反馈环 短得多;ESLint 诞生于 2013 年,书里特意说了一句「至今势头不减」—— 2013 年是 Node.js 生态起步的年代,同一辈的前端工具大多换了几轮,它还站着, 这本身就是「静态检查已成标配」这条判断的间接证据。

还有一处容易被滑过去的细节:书里讲 == 的坑时给的那组反例 x=[]、y=0、z="0",值得亲手验一次——[] == 0 成立(空数组转数字是 0)、 0 == "0" 也成立(字符串转数字),但 [] == "0" 不成立(空数组转字符串是空串)。 三组判断两两矛盾,传递律整体垮掉。换成 === 之后三组全不成立,矛盾消失。 这不是咬文嚼字:第 6 章排序的比较函数里如果混进隐式转换,排出来的顺序会 「大多数时候对、偶尔错」,最难查13。

3.17 本章小结:一句话记住每一节

节 一句话
箭头函数 单表达式省 return;砍掉的 this/new/arguments 纯函数都用不上
类 原型继承的体面包装;get/set 让读写属性时插入逻辑
展开 ... 一个「摊」字:摊开传参、复制数组、复制改对象
解构 按位置配对:多赋值、默认值、免临时变量交换、多值返回
模块 公开清单划定边界;CommonJS 与 ESM 只是清单的两套写法
闭包/IIFE 函数记住出生环境 → 私有变量;定义即执行 → 旧式模块
工具 Prettier 管格式、ESLint 管疑点、TypeScript 管类型、Babel 管兼容

3.18 JSDoc 注释解剖:一段注释能生成一页文档

书里给了一段第 14 章 add 函数上方的注释,它值得逐行看, 因为「约定格式的注释 → 自动生成文档站」这件事,靠的就是这几个 @ 标签14:

/**
 * Add a new key to a heap.        ← 概述:一句话说清这个函数干什么
 * @author F.Kereki               ← 谁写的
 * @version 1.0                   ← 哪个版本
 * @param {pointer} heap          ← 参数:类型 + 名字 + 一句话说明
 * @param {string} keyToAdd       ← 同上,每个参数一行
 * @return Updated heap           ← 返回什么
 */

规则的门槛只有两条:注释以 /** 开头(比普通注释多一个星),内容里用 @标签。 JSDoc 工具扫完全部文件,把每个函数变成文档站里的一页,标签变成页里的字段。 书里没有用的理由是「正文文字就在解释代码」——拆解文档扮演的正是这个角色。 书里列了但没展开的标签还有:@class、@constructor、@deprecated (标记「别再用这个,将来会删」)、@exports、@property、@throws(会抛什么异常)。 日常项目里 @deprecated 最实用:给旧函数标上它,IDE 会在每个调用处画删除线15。

3.19 编辑器与字体:工具部分为什么也值得一读

工具一节看着与算法无关,但有两个判断值得留下。

编辑器的价值在「集成」二字。 书里把 IDE 和记事本/vi 这类纯文本编辑器对比: 纯文本编辑器什么都能写,但你要自己开终端、自己敲命令、自己在窗口间来回切; IDE 把编辑、调试、终端、Git 集成在一个窗口里,按一下就行。 「省的时间是省在上下文切换上」——写代码最贵的是进入状态,工具的目的就是减少被打断的次数。

字体那节看似闲笔,实则讲了一个普遍现象。 连字(ligature)本来是排版概念: & 是拉丁语 et 的合字,æ 是 a+e,德语 ß 是两个 s——书里的注释特意举了这三个例子。 Fira Code 把同一手法搬到代码上:>= 显示成 ≥、!= 显示成 ≠。 它不改变代码本身一个字节,只改变显示——提醒一件事:你看到的代码不等于磁盘上的代码, 这个认知在第 18 章讲「不可变数据」时会回来:改显示无害,改数据要小心16。

3.20 与第 2 章的衔接:本章埋了什么伏笔

作者在章末预告:下一章深入 JavaScript 的函数式编程侧面,作为全书例子的起点17。 回看本章,伏笔有两处已经可见:

  • 箭头函数一节反复说「更短、更函数式」,但没解释函数式到底好在哪——第 2 章回答;
  • 闭包一节预告「第 5 章记忆化会用到」——数据结构用完之后,第 18 章还会用 「不可变」这个函数式概念收束全书。

3.21 再补三件工具的细节:VSC、兼容性查询、TypeScript 那张图

Visual Studio Code 的具体功能,书里列了一串,逐个说清「集成」到底集成了什么:

  • IntelliSense(智能感知):打字时自动补全函数名、提示参数类型和个数—— 相当于编辑器随时替你翻文档;
  • 集成调试:设断点、单步执行、看变量值,不用离开编辑器;
  • 集成终端:编辑器里开一个命令行窗口,跑测试、起服务不跳出;
  • Git 集成:改了哪些文件、差异是什么,侧边栏直接看18。

这四样的共同点是:它们各自都有独立工具(补全靠记、调试靠浏览器、终端靠另开窗口、 版本管理靠命令行),IDE 的贡献是把四条流水线并成一条。

Kangax 与 Can I Use? 的分工:前者按「JavaScript 引擎」列(Chrome 的 V8、 Firefox 的 SpiderMonkey、Node.js 用的也是 V8……),适合查「这个语法特性在哪个运行环境能用」; 后者按「浏览器 + 版本」列,还给出全球用户覆盖率——比如搜箭头函数, 除了「IE 不支持」还能看到「能直接用它的用户占百分之多少」。 两者都不覆盖 Node.js 服务端,作者的结论因此也干脆:服务端永远用最新版, 这两站主要给前端用19。

TypeScript 示例图讲的是什么:书里那幅图(借自 TypeScript 官网)的场景是—— 代码先给 user 对象的 name 属性赋过值,后来又想访问 user.nameAge, ESLint 当场标错,因为按已知的类型推断,这个属性不存在。 这类错误在纯 JavaScript 里要运行到那一行才炸;有了类型标注,敲完那行就知道。 「把运行时错误提前到写代码时」就是 Flow/TypeScript 的全部卖点20。

4. 作者的判断与证据

哪些是书里明确给的判断,依据是什么:

  • 「更短的代码就是更好」(箭头函数一节)——这是作者的编码审美,不是定理; 支撑是全书一致地贯彻它,所有实现都压到最短。
  • 模块清单式导出带来「黑盒」可维护性——软件工程的通行原则,作者把它和 高内聚/低耦合两条设计原则绑定论述33。
  • 工具推荐是经验之谈:Prettier 终结格式争吵、ESLint 抓 == 和误赋值, 都有具体例子佐证;Flow/TypeScript「大项目考虑加」则是没有任何量化的经验建议46。
  • 作者如实交代了适用范围:类「在这本书里不太用」、JSDoc「这本书不用」、 代码全部在当时的 Node.js 上验证过——这些限定让读者不会误把章节当全貌。

书里没给证据、属于业界共识的部分(我们如实标注):闭包模拟私有变量、 IIFE 模拟模块是 ES2015 之前社区的标准技巧;.mjs 与 package.json 的 "type": "module" 是 Node.js 的现行规则——书中只提了前者(补充,不在书里,来自通用知识)。

5. 边界与局限

  • 这不是 JavaScript 教程。作者假设读者已经会这门语言,第 1 章只拾「后面用得上的」, 基础的变量、循环、对象字面量都没讲;零基础读者需要另配一本入门书。
  • 不讲 TypeScript,尽管书末把它列为大项目工具;所有代码是纯 JavaScript。
  • 工具部分时效性强:版本号、扩展链接、网站界面都会过时;机制(格式化、静态检查、 类型标注)不会过时。
  • 私有字段一笔带过:ECMAScript 2022 的 #field 真私有语法只在注释里提了一句, 书中代码统一用下划线约定。
  • 与算法主线的关系:这一章没有算法内容,它的价值全在「让后面十六章的代码可读」。

6. 可带走的

  1. 箭头函数:单表达式可省 return;不能 new、没有自己的 this——算法代码里只管放心用。
  2. ... 的三个用途:摊开传参、复制数组、复制并改对象——一个「摊」字记住全部。
  3. 解构按位置配对:一行多赋值、默认值、免临时变量交换([a, b] = [b, a])、 函数一次返回多个值。
  4. 模块就是一张公开清单:module.exports/require(Node 老格式)与 export/import(新标准)等价;清单外是私有实现。
  5. 闭包 = 函数 + 它记得的出生环境,效果是私有变量;套进 IIFE 就是旧式模块。
  6. Prettier 管格式、ESLint 管可疑写法——== 换 ===、赋值与比较之别,让工具盯着。
  7. 大项目加类型标注(Flow 或 TypeScript);要兼容旧浏览器,用 Babel 转译 + Kangax / Can I Use? 查支持。
  8. 全书代码口径:当时的最新 Node.js 直接可跑,Prettier 排版。

  9. 五种函数定义只留箭头函数:纯函数代码用不上 this、new、arguments, 被砍掉的能力全是包袱。

  10. 「现代 JavaScript」= ES2015 及以后:每年一版小更新;两种模块格式并存是 标准化之前 Node.js 先动手的遗产。

7. 原文地图

主题 原书章 原文位置
章节定位(只拾要点) Modern JavaScript Features text/10-fm-modern-javascript-features.txt:32(搜「Modern JavaScript Features」)
箭头函数四特性、省 return Modern JavaScript Features text/10-fm-modern-javascript-features.txt:60(搜「new kids on the block」) · text/10-fm-modern-javascript-features.txt:77(搜「omit the return」)
返回对象要加括号 Modern JavaScript Features text/10-fm-modern-javascript-features.txt:93(搜「parentheses」)
参数默认值 Modern JavaScript Features text/10-fm-modern-javascript-features.txt:105(搜「default values」)
类语法、get/set、extends Modern JavaScript Features text/10-fm-modern-javascript-features.txt:122(搜「Classes」)
展开三用途、arguments 之弱 Modern JavaScript Features text/10-fm-modern-javascript-features.txt:172(搜「spread」) · text/10-fm-modern-javascript-features.txt:196(搜「array-like」)
解构四种用法、交换、多值返回 Modern JavaScript Features text/10-fm-modern-javascript-features.txt:216(搜「Destructuring」) · text/10-fm-modern-javascript-features.txt:240(搜「swap」)
模块原则、黑盒 Modern JavaScript Features text/10-fm-modern-javascript-features.txt:269(搜「high cohesion」) · text/10-fm-modern-javascript-features.txt:304(搜「black box」)
CommonJS 写法 Modern JavaScript Features text/10-fm-modern-javascript-features.txt:277(搜「CommonJS」)
ECMAScript 模块、默认导出 Modern JavaScript Features text/10-fm-modern-javascript-features.txt:325(搜「ECMAScript」) · text/10-fm-modern-javascript-features.txt:369(搜「named exports」)
闭包造私有变量 Modern JavaScript Features text/10-fm-modern-javascript-features.txt:390(搜「combination of a function」)
IIFE 模拟模块 Modern JavaScript Features text/10-fm-modern-javascript-features.txt:428(搜「IIFE」)
ECMAScript 标准、MDN、Babel Modern JavaScript Features text/10-fm-modern-javascript-features.txt:471(搜「ECMAScript」) · text/10-fm-modern-javascript-features.txt:477(搜「transpile」)
工具:VSC / Fira Code / Prettier / JSDoc / ESLint / Flow+TS Modern JavaScript Features text/10-fm-modern-javascript-features.txt:487(搜「Development Tools」) · text/10-fm-modern-javascript-features.txt:570(搜「opinionated」) · text/10-fm-modern-javascript-features.txt:624(搜「==」) · text/10-fm-modern-javascript-features.txt:651(搜「Flow and TypeScript」)
兼容性查询与 polyfill Modern JavaScript Features text/10-fm-modern-javascript-features.txt:679(搜「Kangax」)

补充分走读处的出处

主题 原书章 原文位置
Tree 类逐行示例 Modern JavaScript Features text/10-fm-modern-javascript-features.txt:131(搜「class Tree」)
静态属性和方法 Modern JavaScript Features text/10-fm-modern-javascript-features.txt:157(搜「static properties」)
radix_tree 模块两格式 Modern JavaScript Features text/10-fm-modern-javascript-features.txt:283(搜「radix_tree.js」) · text/10-fm-modern-javascript-features.txt:331(搜「modern style」)
createPerson 闭包示例 Modern JavaScript Features text/10-fm-modern-javascript-features.txt:393(搜「createPerson」) · text/10-fm-modern-javascript-features.txt:418(搜「Federico」)
工具清单原文 Modern JavaScript Features text/10-fm-modern-javascript-features.txt:490(搜「arsenal」)

  1. 出处:「Modern JavaScript Features」第 64–73 段(text/10-fm-modern-javascript-features.txt:60,搜「important differences」)。原文逐条列出箭头函数的四点不同:可无 return 返回值、不能作构造函数或生成器、不绑定 this、没有 arguments 对象和 prototype。 ↩

  2. 出处:「Modern JavaScript Features」第 272 段(text/10-fm-modern-javascript-features.txt:272,搜「two formats」)。原文:模块分两种格式——CommonJS(旧,多用于 Node.js)与 ECMAScript 模块(新,浏览器通用)。 ↩

  3. 出处:「Modern JavaScript Features」第 131–150 段(text/10-fm-modern-javascript-features.txt:131,搜「class Tree」)。原文给出带字段 _children、构造器、isEmpty() 方法与 key 的 getter/setter 的完整类定义。 ↩

  4. 出处:「Modern JavaScript Features」第 157 段(text/10-fm-modern-javascript-features.txt:157,搜「static properties」)。原文:静态属性和方法「不属于类实例,而属于类本身」。 ↩

  5. 出处:「Modern JavaScript Features」第 283 段(text/10-fm-modern-javascript-features.txt:283,搜「radix_tree.js」)与第 331 段(text/10-fm-modern-javascript-features.txt:331,搜「modern style」)。原文同一批字典树函数分别以 CommonJS 与 ECMAScript 模块两种格式书写对照。 ↩

  6. 出处:「Modern JavaScript Features」第 358 段(text/10-fm-modern-javascript-features.txt:358,搜「.mjs」)。原书注释:Node.js 中用 ECMAScript 模块需改用 .mjs 扩展名,.js 留给 CommonJS。「Node.js 先于标准定出 require」的史实是补充(不在书里,来自通用知识)。 ↩

  7. 出处:「Modern JavaScript Features」第 393 段(text/10-fm-modern-javascript-features.txt:393,搜「createPerson」)与第 425 段(搜「private values」)。原文:createPerson 返回带四个方法的对象;first、last「在闭包里,无法从外部访问,充当私有值」;记忆化伏笔见第 464 段(搜「memoizing」)。 ↩↩

  8. 出处:「Modern JavaScript Features」第 490 段(text/10-fm-modern-javascript-features.txt:490,搜「arsenal」)。原文:这些工具是「我开始新项目时通常最先安装的几样」;Git 钩子见第 627 段(搜「precommit hooks」)。 ↩

  9. 出处:「Modern JavaScript Features」第 44–56 段(text/10-fm-modern-javascript-features.txt:44,搜「Named functions」)与第 60 段(搜「important differences」)。原文列出五种函数定义方式,随后说明箭头函数与它们的四点差异。 ↩

  10. 出处:「Modern JavaScript Features」第 18 段(text/10-fm-modern-javascript-features.txt:18,搜「1995」)。原文:「JavaScript 自 1995 年的最初版本以来不断演化并增加了大量功能。」ES2015 时间线是补充(不在书里,来自通用知识)。 ↩

  11. 出处:「Modern JavaScript Features」第 477 段(text/10-fm-modern-javascript-features.txt:477,搜「transpile」)与第 676 段(搜「frontend work」)。原文:浏览器或 Node.js 可能未实现最新版本,可用 Babel 转译;「如果你做前端工作,某个函数可能不可用」。 ↩

  12. 出处(各特性在后文的用途,散见本章引用):_getHeight 见第 80 段(text/10-fm-modern-javascript-features.txt:80,搜「_getHeight」);默认值见第 105 段(搜「default values」);newNode 见第 96 段(搜「newNode」);堆交换见第 243 段(搜「heap[p]」);记忆化见第 464 段(搜「memoizing」)。 ↩

  13. 出处:「Modern JavaScript Features」第 624 段(text/10-fm-modern-javascript-features.txt:624,搜「transitive law」)。原文:== 的例子中「x==y 且 y==z,但 x!=z,无论传递律怎么说」;对应规则见第 641 段注释(搜「eqeqeq」)。三行推演中「空数组转数字是 0」等转换规则是补充(不在书里,来自通用知识)。 ↩

  14. 出处:「Modern JavaScript Features」第 589–603 段(text/10-fm-modern-javascript-features.txt:590,搜「Add a new key」)。原文:JSDoc 注释以 /** 开头,含 @author、@version、@param、@return 等标签。 ↩

  15. 出处:「Modern JavaScript Features」第 603 段(text/10-fm-modern-javascript-features.txt:603,搜「@deprecated」)。原文列出其余可用标签:@class、@constructor、@deprecated、@exports、@property、@throws。 ↩

  16. 出处:「Modern JavaScript Features」第 533 段(text/10-fm-modern-javascript-features.txt:533,搜「ligatures」)与第 541 段(搜「encyclop」)。原文:& 是 E 与 t 拼成 et 的连字,æ 见于 encyclopædia,Cæsar,德语 ß 是两个 s;Fira Code 为 JavaScript 提供大量连字。 ↩

  17. 出处:「Modern JavaScript Features」第 716 段(text/10-fm-modern-javascript-features.txt:716,搜「functional programming aspects」)。原文:「下一章我们将更深入地探索 JavaScript 的函数式编程方面,为本书其余部分的示例提供一个起点。」 ↩

  18. 出处:「Modern JavaScript Features」第 501 段(text/10-fm-modern-javascript-features.txt:501,搜「time-saver」)与第 515 段(搜「integrated debugging」)。原文:IDE 省在集成——调试、终端、源代码管理(Git)集成在一个窗口。 ↩

  19. 出处:「Modern JavaScript Features」第 679 段(text/10-fm-modern-javascript-features.txt:679,搜「Kangax」)与第 682 段(搜「Can I Use」)。原文:Kangax 按引擎列表、绿格即可放心用;Can I Use 可按函数搜索并给出各浏览器支持比例;第 701 段(搜「only about browsers」)说明它不覆盖 Node.js。 ↩

  20. 出处:「Modern JavaScript Features」第 651 段(text/10-fm-modern-javascript-features.txt:651,搜「Flow and TypeScript」)与第 663 段(搜「user.name」)。原文:TypeScript 是 JavaScript 的超集、经转译;图中示例是访问类型推断中不存在的 user.name 属性,ESLint 实时标出。 ↩

  21. 出处:「Modern JavaScript Features」第 181 段(text/10-fm-modern-javascript-features.txt:181,搜「myArray」)。原文:const myArray = [3, 1, 4, 1, 5, 9, 2, 6];——第 1 节主线例子里的数组照搬此数。 ↩

  22. 出处:「Modern JavaScript Features」第 175 段(text/10-fm-modern-javascript-features.txt:175,搜「separate values」)与第 184 段(text/10-fm-modern-javascript-features.txt:184,搜「produces 9」)。原文说明 ...myArray 等价于挨个输入 8 个数;第一个用法算出最大值 9,第二个用法「provides a new array with exactly the same elements」。 ↩

  23. 出处:「Modern JavaScript Features」第 243 段(text/10-fm-modern-javascript-features.txt:243,搜「heap[p]」)。原文:[heap[p], heap[i]] = [heap[i], heap[p]]; 并说明它「直接交换两个值,不需要辅助变量」——这是第 14 章堆代码的引用。 ↩

  24. 出处:「Modern JavaScript Features」第 77 段(text/10-fm-modern-javascript-features.txt:77,搜「omit the return」)。原文:能省掉 return 的特性「在本书中被大量使用」;并给出 _getHeight 的一行版与四行版的对照,结论是「更短的代码就是更好」(第 90 段,搜「shorter code is good」)。 ↩↩

  25. 出处:「Modern JavaScript Features」第 93 段(text/10-fm-modern-javascript-features.txt:93,搜「enclose it in parentheses」)。原文:想返回对象必须用括号包起来,并给出第 12 章的 newNode 例子。 ↩

  26. 出处:「Modern JavaScript Features」第 105 段(text/10-fm-modern-javascript-features.txt:105,搜「default values」)。原文示例为第 12 章的递归打印函数,未给 s 时初始化为空串。 ↩

  27. 出处:「Modern JavaScript Features」第 125 段(text/10-fm-modern-javascript-features.txt:125,搜「prototype」)。原文:「虽然我们在本书中不会太多地使用类」,但现代 JavaScript 用类与 extends 让继承直接了许多;get/set 的说明见第 154 段(搜「Getters」),「2022 起有私有属性」的注释见第 165 段(搜「private properties」)。 ↩

  28. 出处:「Modern JavaScript Features」第 191 段(text/10-fm-modern-javascript-features.txt:191,搜「first: Charles」)。原文:{...myObject, first: "Charles", year: 1882} 先复制 myObject 的字段再覆盖 year。 ↩

  29. 出处:「Modern JavaScript Features」第 196 段(text/10-fm-modern-javascript-features.txt:196,搜「array-like」)。原文:arguments 是「类数组」对象,「.length 是它提供的唯一数组属性」。 ↩

  30. 出处:「Modern JavaScript Features」第 229 段(text/10-fm-modern-javascript-features.txt:229,搜「years」)、第 236 段(搜「role」)、第 243 段(搜「rotate」)。原文依次演示「其余全收进最后一个」「默认值」「交换与轮转」。 ↩

  31. 出处:「Modern JavaScript Features」第 258 段(text/10-fm-modern-javascript-features.txt:258,搜「smaller」)。原文:let [smaller, bigger] = order2(22, 9); // smaller==9, bigger==22,并评价返回数组+解构「更紧凑」。 ↩

  32. 出处:「Modern JavaScript Features」第 269 段(text/10-fm-modern-javascript-features.txt:269,搜「high cohesion」)。原文:高内聚指「放在一起的东西确实该在一起」;低耦合指「不同模块尽可能互不依赖」。 ↩

  33. 出处:「Modern JavaScript Features」第 304 段(text/10-fm-modern-javascript-features.txt:304,搜「black box」)。原文:不在 module.exports 清单里的函数外面访问不到;「这与黑盒软件概念高度一致」——模块内部可以随便重构,只要对外功能不变。 ↩↩

  34. 出处:「Modern JavaScript Features」第 311 段(text/10-fm-modern-javascript-features.txt:311,搜「destructuring」)。原文:const {newRadixTree, add} = require("radix_tree.js"); 并用解构只取两个函数;作者明说偏爱这种写法,因为「清楚我在用什么」(第 318 段,搜「prefer」)。 ↩

  35. 出处:「Modern JavaScript Features」第 334 段(text/10-fm-modern-javascript-features.txt:334,搜「export const」)、第 364 段(搜「import」)、第 369 段(搜「named exports」)。原文:export 可随定义标或集中在末尾;import * as RadixTree 导入全部;另可有「单个未命名的默认导出」。 ↩

  36. 出处:「Modern JavaScript Features」第 358 段(text/10-fm-modern-javascript-features.txt:358,搜「.mjs」)。原书注释:Node.js 里用 ECMAScript 模块需把扩展名换成 .mjs,.js 保留给 CommonJS。 ↩

  37. 出处:「Modern JavaScript Features」第 390 段(text/10-fm-modern-javascript-features.txt:390,搜「combination of a function」)。原文:闭包是「一个函数加上它能访问的外层作用域的组合」;me.first 返回 undefined,因为那两个变量「在闭包里」,充当私有值(第 425 段,搜「private values」)。 ↩

  38. 出处:「Modern JavaScript Features」第 428 段(text/10-fm-modern-javascript-features.txt:428,搜「IIFE」)与第 464 段(搜「memoizing」)。原文:IIFE 是「定义后立即执行的函数」;第 5 章讨论记忆化与预计算数组时会用到闭包+IIFE。 ↩

  39. 出处:「Modern JavaScript Features」第 471 段(text/10-fm-modern-javascript-features.txt:471,搜「ECMAScript」)。原文:该语言正式名是 ECMAScript,由 ECMA 维护;MDN 是特性查询的常备资源(第 474 段,搜「Mozilla Developer Network」)。 ↩

  40. 出处:「Modern JavaScript Features」第 477 段(text/10-fm-modern-javascript-features.txt:477,搜「transpile」)。原文:遇到环境不支持可用 Babel 转译;「我在当前版本的 Node.js 中运行了书中所有代码,没有遇到问题」。 ↩

  41. 出处:「Modern JavaScript Features」第 498 段(text/10-fm-modern-javascript-features.txt:498,搜「integrated development environment」)。原文:VSC 开源免费、月更;第 512 段的注释(搜「Electron」)说明它用 JavaScript 开发、经 Electron 打包,与 Visual Studio 无关。 ↩

  42. 出处:「Modern JavaScript Features」第 533 段(text/10-fm-modern-javascript-features.txt:533,搜「ligatures」)。原文:连字(ligatures)把两个以上字符连成一个符号;Fira Code 提供大量连字。 ↩

  43. 出处:「Modern JavaScript Features」第 570 段(text/10-fm-modern-javascript-features.txt:570,搜「opinionated」)。原文:Prettier 是「有主见的」格式化工具;引其官网「采用 Prettier 最重大的理由是停止所有关于风格的持续争论」;全书代码都经它排版。 ↩

  44. 出处:「Modern JavaScript Features」第 624 段(text/10-fm-modern-javascript-features.txt:624,搜「transitive law」)。原文:== 的反例(x=[]、y=0、z="0" 时传递律失效)与 (x=y) 误敲;第 641 段的注释(搜「eqeqeq」)说明对应规则名。 ↩

  45. 出处:「Modern JavaScript Features」第 583 段(text/10-fm-modern-javascript-features.txt:583,搜「aggregating」)。原文:JSDoc 汇总特定格式的注释生成文档;「我们在本书中不用 JSDoc,因为文字部分会解释代码」。 ↩

  46. 出处:「Modern JavaScript Features」第 651 段(text/10-fm-modern-javascript-features.txt:651,搜「superset」)。原文:Flow 用注释标注类型;TypeScript 是 JavaScript 的超集、经转译运行;「对于涉及许多类、方法、函数、类型的大项目,考虑把它们纳入你的工具箱」。 ↩↩

  47. 出处:「Modern JavaScript Features」第 679 段(text/10-fm-modern-javascript-features.txt:679,搜「Kangax」)与第 690 段(搜「polyfill」)。原文:Kangax 按引擎列特性支持,绿格即可放心用;polyfill 是「浏览器原生不支持时复制一份 API」的补垫。 ↩