JavaScript 忍者秘籍(第三版 MEAP V10)— 全书拆解
JavaScript 忍者秘籍(第三版 MEAP V10)— 全书拆解
30 秒导读: 这本书回答一个问题:为什么同样写 JavaScript,有人写得像「忍者」,有人写得像实习生? 作者的答案很明确:分水岭不在背了多少 API,而在懂不懂函数——这本书用连续四章(第 5–8 章)把函数讲透, 再让对象、类、集合这些「看起来像主角」的东西全部挂在函数的机制之上。 另一件事先说清:这是 2026 年的 MEAP V10(出版前预览版),只写完第 1–10 章加一个附录; 原书自己反复预告的第 11–18 章在这份文本里不存在,我们的拆解也只覆盖实际存在的部分。
本组文档是我们重写的完整拆解,十三章。读完不必看原书。
1. 先交代清楚:这是谁在什么时候写的
| 作者 | Trevor Burnham、Bear Bibeault、Josip Maras;John Resig(jQuery 作者)是前两版的奠基作者,第三版署名保留 |
| 版本 | MEAP V10——Manning 的「出版前逐章发售」预览版,收录到第 10 章 + 附录 A,原书页内注明 © Manning Publications |
| 时间坐标 | 2026 年前后写作;文中出现的技术现状(Node.js 22 支持直接跑 TypeScript、ES2024 的 Set 运算方法、Promise.withResolvers)都是 2024–2025 年的事 |
| 与旧版的关系 | 第一版 2012 年、第二版 2016 年;第三版整体重写:删掉已死的旧技术(如 arguments 对象整节被 rest 参数取代),换成 TypeScript、现代工具链和新 API |
两件事必须先说,否则会误读这本书:
第一,它没写完。 正文里到处是「第 13 章讲模块」「第 14 章讲构建」「第 17 章讲 workers」, 这些章节在这份 PDF 里不存在。读我们的拆解不会遇到这种悬空引用;但如果你回头翻原书, 看到「Chapter 14」的字样,要知道那是预告,不是内容。
第二,它的立场很强:JavaScript 首先是函数式语言,其次才是带对象的语言。 开篇就警告:「觉得懂 Java 就懂 JavaScript,是个陷阱」——函数是一等对象、对象用原型、代码单线程, 这三点与主流语言的根本差异,是全书所有机制展开的地基。1
2. 全书一条主线(读完这一节,你就懂了这本书)
这本书是一条从头贯到尾的推理链。只读这一节,你也能把这本书讲给别人听。
① 一个 10 天造出来的语言,凭什么统治了计算?
1995 年,Netscape 给浏览器加脚本功能,10 天写出了 JavaScript。 今天它跑在网站、服务器、手机桌面应用、游戏机和汽车里。 作者归因于两个字:演化——浏览器厂商竞争造出了快得多的引擎(V8、JavaScriptCore), 厂商合作把语言标准(ECMAScript)从「十年一改」变成「每年一改」,而 Node.js 把它带出了浏览器。2
② 但它带着一身「原罪」,所以才有后来的工具链
这门语言为了迁就新手,把类型规则做得极其宽松:[] + {} 得到字符串,[] + "" 也得到字符串,
错到运行时都不一定报错。于是整个行业长出了一层外骨骼:
TypeScript 在编译期抓类型错误,ESLint 抓坏模式,Prettier 终结格式争论。
「JavaScript 不是真正的语言」这句旧笑话,靠的不是语言变好,而是工具把护栏补上了。3
③ 想写得像忍者,先承认:这是函数的语言
全书的地基性判断:函数在 JavaScript 里是一等对象——可以存进变量、塞进数组、当参数传来传去、 由别的函数造出来。对象能做的,函数全能做,外加一件对象做不到的事:可以被调用。 从这一定义出发,后面三章全是对它的展开:回调是把函数交给别人回头调用; memoize 是给函数包一层缓存壳;闭包是函数连同它出生时的变量一起被带走。4
④ 但函数有一个隐形的参数,它的值由「怎么调用」决定
JavaScript 函数除了明写的参数,还有一个隐形的 this。
它在 Java 里的意思(「我这个实例」)只是四种可能之一:当普通函数调用是 undefined、
当方法调用是点号前面的对象、当构造器调用是一个新建的空对象、用 call/apply 调用则由调用方直接指定。
「我只是把方法存进变量,行为怎么变了」——无数 bug 出自这里。
解法是 bind(造一个上下文固定的新函数)和箭头函数(根本不认调用方式,只认定义处)。5
⑤ 函数还能记住出生的地方——这就是闭包,也是半本书的隐形主角
函数被创建时,自动打包带走「当时能看见的所有变量」,这个包裹叫闭包。
它的直接推论一个比一个重要:回调能在事件发生时才去读外面的变量;
循环里每次迭代各有自己的 i(所以定时器回调打印 0、1、2 而不是 3、3、3);
模块顶层的变量天然私有。另一面也成立:只要函数还活着,它见过的变量就不能被垃圾回收——
V8 凭经验的启发式判断偶尔因此留着一个 100MB 的对象不放,内存泄漏由此而来。6
⑥ 单线程不是缺陷,是架构——异步是对它的服从
JavaScript 自始至终只有一条线程在跑代码,所以「等」这个动作必须让位:
等文件读入、等网络返回,都不能堵着线程。方案一路升级:
回调(把「之后干什么」交给运行时回头调用)→ promise(把「未来那个结果」变成一个对象,
可以传、可以链、可以组合)→ async/await(把链式写法压回成看起来像同步的代码)。
而支撑这一切的调度纪律只有一条:事件分微任务和宏任务两列队,微任务永远先跑——
这就是为什么 promise 的回调总比 setTimeout(..., 0) 的回调先执行。7
⑦ 对象:给函数披上皮之后,继承是「找不到就往上问」
给函数挂上属性、再配上 new,就得到了对象;对象之间再用「原型」连成链:
读一个属性时,自己身上没有,就问原型,原型没有再问原型的原型。
ES2015 的 class 关键字并没有发明新机制——它只是把这串操作写成 Java 程序员熟悉的语法,
底层仍是构造函数加原型。属性本身也远比「键值对」丰富:可以只读、可以隐藏(不参与枚举)、
可以被 Proxy 整体拦截。8
⑧ 集合是这场演化的摘要
数组其实是对象(越界读给 undefined 而不是报错,就是对象行为的直接后果),
它的整套 map/filter/reduce 方法全是「把函数传进去」的一等公民用法;
Map 补上了对象当字典的两大缺陷(键只能是字符串、分不清自己的还是继承来的),
代价是键按引用比较;不可变性则是最后一个闭环——React 要求状态不可变,
TypeScript 的 as const 在编译期上锁,Immer 用 Proxy 让你「用修改的写法,得到不可变的结果」。9
⑨ 一句话收尾
作者的压缩版是:「把 JavaScript 当函数式语言来理解,是区分忍者和普通人的那把刀—— 对象、类、框架都是在这把刀上磨出来的。」工具链和运行时只是让这把刀好用; 刀本身,是函数。
3. 十三章地图
这张表不用记任何术语 —— 每章后面写的是「读完你能回答什么问题」。
| 章 | 读完你就能回答 |
|---|---|
| 01 JavaScript 无处不在 | 这门语言和 Java/C# 到底哪里不一样?为什么需要 transpiler/polyfill/TypeScript 这些外挂? |
| 02 现代工具三件套 | TypeScript 怎么在跑起来之前抓错?ESLint 规则和「就这条不算」怎么共处?Prettier 怎么终结格式战争? |
| 03 Node.js 与 npm | 单线程的 Node 凭什么当服务器?import "express" 时它去哪个文件夹找文件?^5.1.0 里的 ^ 是什么意思? |
| 04 构建 Web 应用 | 浏览器拿到 HTML 后按什么顺序干活?为什么点一个卡死的按钮,恢复后会连响应三次点击?JSX 把那堆 DOM 操作藏哪了? |
| 05 一等函数(上) | 声明式和表达式定义的函数差在哪?「参数」和「实参」不是一回事?怎么给函数加缓存? |
| 06 this 与四种调用 | 「我只是把方法存进变量,怎么就报错了」是怎么回事?bind 和箭头函数各治什么病? |
| 07 闭包与作用域 | 函数为什么能读到已经「不存在」的变量?let 循环为什么每次迭代有自己的 i?「提升」和「暂时性死区」是什么? |
| 08 Promise 与生成器 | promise 三态怎么流转?为什么 .then 的回调总比 setTimeout 先跑?async 函数凭什么不阻塞线程? |
| 09 对象、原型与类 | 属性怎么做成只读/隐藏?Proxy 能拦什么?class 底下到底是什么?# 开头的字段特殊在哪? |
| 10 数组 | 为什么越界读数组给 undefined 不报错?splice 和 slice 差一个字母差在哪?reduce 到底在累什么? |
| 11 Map 与 Set | 拿对象当字典用会踩什么坑?Map 的键怎么算「同一个」?WeakMap 为什么能防内存泄漏? |
| 12 不可变集合 | 为什么 React 里 push 不触发更新?as const 做了什么?Immer 的「草稿」是怎么骗过你的? |
| 13 环境搭建与替代品 | 怎么用 fnm 管多个 Node 版本?Deno/Bun/pnpm 各凭什么挑战现任者? |
推荐顺序: 01 → 02 → 03 → 04 是「环境层」,05 → 06 → 07 → 08 是「函数四连章」(全书心脏,别跳), 09 → 10 → 11 → 12 是「对象与集合层」,13 是备查。只想拿语言核心,读 05–09 五章就够。
4. 这本书覆盖什么、不覆盖什么
这两张表和上面那张地图一个口径:不用记任何术语。
覆盖(而且讲得比多数材料清楚)—— 读完你能回答:
- 这门语言与主流语言的三个根本差异(函数一等、原型对象、单线程),以及它们如何串起后面所有章节;
- 为什么「写完了才知道错」的 JavaScript 能被 TypeScript/ESLint 改造成「写的时候就报」;
- Node.js 怎么用一条线程扛住服务器:回调、promise、
async/await三代异步方案的动机和机制; this为什么有四种来源,bind与箭头函数各自把哪一种钉死;- 闭包的完整语义,以及它如何同时造就「模块私有」和「内存泄漏」;
- promise 的决议算法(为什么
resolve(另一个 promise)会「跟着它走」)、微任务/宏任务队列的优先级; - 对象属性的隐藏维度(描述符、枚举性、Proxy 拦截)与原型链的回退语义;
- 数组/Map/Set 的行为边界(洞、引用相等键、WeakMap 与垃圾回收)与不可变集合的工程化手段;
- npm 依赖树怎么解析、版本号怎么管、怎么给第三方包打补丁。
不覆盖(别指望在这本里找):
| 缺什么 | 说明 |
|---|---|
| 第 11–18 章 | MEAP V10 没写完。模块/ESM 与 CommonJS 细节、正则、构建打包、tree shaking、workers、事件捕获冒泡、微前端、codemods——全书反复预告,全部缺席 |
| 函数式编程的系统理论 | 组合子、函子、monad 一概未提,只在边栏指了一本专门的书 |
| 正则表达式 | 一个字没讲(原书计划留给后续章) |
| 性能工程 | 只有散落的提示(别阻塞线程、unshift 在大数组上很贵、Proxy 有开销),没有剖析方法 |
| 测试实践 | 只区分单元/端到端并点名框架,正文全用 console.assert 做演示 |
| TypeScript 高级类型 | 模板字面量类型、条件类型等高级特性未提;类型一章只覆盖日常编码层面 |
| 框架内部机制 | React/Express 只作示例,不讲 hook 原理、虚拟 DOM diff 算法细节 |
| 编译原理细节 | 引擎只有「两遍解析 + 惰性解析」的概念级模型,不讲文法和字节码 |
5. 我们的判断
判断(我们的,不是书里的): 这本书在今天的价值排序是—— ① 函数四连章(第 5–8 章,讲透了且不过时)> ② 对象与集合三章(机制准确,但深度止于「会用」)> ③ 工具链两章(步骤级教程,过时最快)> ④ 第 1、4 章的综述(读一遍建立坐标即可)。 引用它时引①,②可引,③里的具体命令和版本号要核当时的日期。 如果错,会错在: 如果判断依据是「它把机制讲透了」——但 MEAP 尚未完结, 后续章(模块、workers)可能与已出章节的口径冲突;那时③④的判断要重排。
判断(我们的,不是书里的): 这本书最有意思的立场不是「怎么写好 JavaScript」, 而是它无意中给出了一份**「语言演化的病理报告」**:this 的四态、var 的函数作用域、隐式转换, 每一项都被标记为「历史事故」,而 strict mode、let/const、class、私有字段、模块, 是逐项的「修复手术史」。研究语言设计的人读它,会比学 JavaScript 的人收获更多。 如果错,会错在: 作者们是执业工程师而非语言学者,「事故/修复」的叙述是他们的工程判断, 不一定是 TC39 标准委员会的设计意图;个别条目(如 class 的定位)在业内至今有争议。
判断(我们的,不是书里的): 读这本书要区分三个声音:三位作者的正文(默认)、 框起来的「DO YOU KNOW?/边栏」(预习问题和延伸推荐)、以及书里大段出现的 ESLint 规则提示 (作者的工具偏好,不是语言标准)。我们的拆解里三者都在当场标清。 如果错,会错在: Manning MEAP 版本在出版前还会修订,边栏内容可能被移除或移动, 以最终版为准。
6. 许诺兑现表
这份拆解里每一处「第 N 章讲」「见后面」,都在这里记一行、逐行核过。
| 许诺在哪 | 许诺了什么 | 应兑现在哪 | 核过了吗 |
|---|---|---|---|
| 本页 §2 ② | TypeScript/ESLint/Prettier 各自补哪块护栏 | 02 §2–§4 | ✓ |
| 本页 §2 ④ | this 的四种来源与两种解法 | 06 §3–§4 | ✓ |
| 本页 §2 ⑤ | 闭包的语义与内存泄漏 | 07 §2、§6 | ✓ |
| 本页 §2 ⑥ | 微任务/宏任务的优先级 | 08 §4 | ✓ |
| 本页 §2 ⑦ | class 只是语法糖的证据 | 09 §4 | ✓ |
| 本页 §2 ⑧ | Map/不可变集合补什么缺陷 | 11、12 | ✓ |
| 本页 §3 地图 01 | 与 Java/C# 的三点不同 | 01 §2 | ✓ |
| 本页 §3 地图 04 | 卡死按钮为何连响应三次 | 04 §3 | ✓ |
| 本页 §3 地图 05 | 参数与实参的错位规则 | 05 §5 | ✓ |
| 本页 §3 地图 06 | 「存进变量就报错」的机制 | 06 §1–§2 | ✓ |
| 本页 §3 地图 07 | 提升与暂时性死区 | 07 §4 | ✓ |
| 本页 §3 地图 08 | resolve(另一个 promise) 为什么跟着走 | 08 §2 | ✓ |
| 本页 §3 地图 09 | # 开头的字段特殊在哪 | 09 §5 | ✓ |
| 本页 §3 地图 10 | 越界读为什么给 undefined | 10 §2 | ✓ |
| 本页 §3 地图 11 | WeakMap 为什么防泄漏 | 11 §3 | ✓ |
| 本页 §3 地图 12 | Immer 的草稿怎么骗人 | 12 §3 | ✓ |
01 §2 框架一节 | 前端/后端/桌面框架怎么选 | 01 §3(地图级),细节超出 MEAP 范围已注明 | ✓ |
03 §2 依赖解析 | node_modules 嵌套与提升 | 03 §3 | ✓ |
04 §4 JSX | 虚拟 DOM 与 reconciliation | 04 §5 | ✓ |
08 §7 生成器 | 什么时候该写生成器 | 08 §7 末 | ✓ |
拆解写于 2026-10-19,依据 Manning MEAP V10(2026,未完结预览版)。原始书籍文件不入库,
本组文档是我们自己的重写;每条引用都可用 node scripts/book-verify.mjs secrets-of-the-javascript-ninja-3e 回核。
Footnotes
-
出处:「Chapter 1: JavaScript is everywhere」第 18–21 段(
text/04-ch01-chapter-1-javascript-is-everywhere.txt:45,搜「it's a trap」)。原书警告:懂 C# 或 Java 就自以为懂 JavaScript「是个陷阱」,并列出函数一等对象、基于原型、单线程三点差异。 ↩ -
出处:「Chapter 1: JavaScript is everywhere」第 12–17 段(
text/04-ch01-chapter-1-javascript-is-everywhere.txt:12,搜「10-day project」)。原书给出 1995 年 Netscape 十天造出 JavaScript 的史实,以及引擎竞争与 ECMAScript 标准化两条演化线。 ↩ -
出处:「Chapter 2: Using modern JavaScript tools」第 9–26 段(
text/05-ch02-chapter-2-using-modern-javascript-tools.txt:9,搜「Back in the early days of JavaScript, there」)。原书复述「它不是真正的语言」这句旧抱怨,并把转变归因于 TypeScript、ESLint、Prettier 三件套。 ↩ -
出处:「Chapter 5: First-class functions for the novice: definitions and arguments」第 23–30 段(
text/08-ch05-chapter-5-first-class-functions-for-the-novice-d.txt:23,搜「first-class objects」)。原书定义函数是一等对象,并预告本章与随后三章全部围绕函数展开。 ↩ -
出处:「Chapter 6: Functions for the journeyman: understanding function invocation」第 14–19 段(
text/09-ch06-chapter-6-functions-for-the-journeyman-understan.txt:14,搜「This chapter introduces the oft-misunderstood this keyword, which」)。原书把 this 称作「隐式函数参数」,并说明它的值随调用方式而变。 ↩ -
出处:「Chapter 7: Functions for the master: closures and scopes」第 27–34 段(
text/10-ch07-chapter-7-functions-for-the-master-closures-and-.txt:97,搜「protective bubble」)。原书用「保护泡」比喻闭包,并说明闭包阻止引擎回收作用域内的变量。 ↩ -
出处:「Chapter 8: Functions for the future: promises and generators」第 440–448 段(
text/11-ch08-chapter-8-functions-for-the-future-promises-and-.txt:440,搜「Promise callbacks live in what the ECMAScript spec」)。原书区分微任务队列与宏任务队列,并给出「微任务严格优先」的规则。 ↩ -
出处:「Chapter 9: Objects, prototypes, and classes」第 601–604 段(
text/12-ch09-chapter-9-objects-prototypes-and-classes.txt:601,搜「unnecessarily convoluted」)。原书明说:post-ES2015 世界里原型那套显得绕,而 class 做的「是同样的事」——它创建的类「只是一个带原型对象的函数」。 ↩ -
出处:「10.1 Arrays」第 27–33 段(
text/13-ch10-01-10-1-arrays.txt:27,搜「each array is also an object」);「10.4 Mutable and immutable collections」第 191–195 段(text/16-ch10-04-10-4-mutable-and-immutable-collections.txt:193,搜「Under the hood」)。数组的对象本性与 Immer 的 Proxy+draft 机制是集合一章的两根支柱。 ↩