读书导读 · CC BY-NC 3.0 · 非商业

《JavaScript 编程精解》全书摘要与逐章导读

CC BY-NC 3.0 · 非商业

非商业许可:按许可,本页不引用原书文字,只链接回官方免费版本的对应小节。免费阅读原书。

《JavaScript 编程精解》讲了什么AI 辅助

与问题相符的词汇,胜过更少的行数

怎样才能让程序始终是你还看得懂的?《Eloquent JavaScript》分层作答。它从比特和类型出发——类型转换在那里悄悄误导人——一路搭起表达式、绑定、控制流和函数,显示出与问题相符的词汇胜过更少的行数。作用域、闭包和纯度随后使抽象和高阶函数成为可能,而复合的对象和数组让真实的信息得以建模。从这里起,思考的单位变成了有边界的部件:接口隐藏内部,模块声明依赖,持久的值可以孤立地推理,而大声的失败加上自动化测试,提供了这门语言所缺的纪律。后半部分把这套词汇花在真实的平台上——把一门微型语言写成解析器加求值器,通过事件而不是轮询来驱动 DOM,围绕可替换的接口和被分派的动作来造一个游戏和一个绘图程序,并经由 HTTP 和异步 I/O 在服务器上运行 JavaScript。通篇反复出现同一个动作:分层是转移困难,而不是消除困难,所以先写正确、不耍小聪明的代码,只有在证明有必要时才加通用性。

一则讲透全书的寓言AI 辅助

用一个故事装下全书最核心的想法,并老实交代故事在哪里失真。

封盒管用,直到雾渗进来

从前,在一座雾城里有一个灯匠行会。老师傅造了一盏灯:一只黄铜柜子,里面每个齿轮都咬着其他所有齿轮,只有他一个人还能把它的内部装在脑子里。他死后,行会没法修它,因为要换灯芯就得懂百叶窗,要懂百叶窗就得懂风箱。于是学徒们重新来过。最年轻的米拉做了一些小封盒,每个只干一件事,只承载一个有名字的想法,外面只露出一个把手,盖子上刻着一份清单,列着它需要的其他盒子。灯盒不在乎它的燃料盒烧的是油还是酒精;只要有合适把手的盒子就行,所以同一根曲柄能驱动许多不同的内芯。她不去刮净重填一个盒子,而是另铸一个新的、留下旧的,这样行会就能预览一个设计,再毫无代价地把它扔掉。她的盒子在把手被转错方向的那一刻还会尖叫——一声刺耳的哨音——因为否则黄铜本身只会耸耸肩,安静地接受胡话,然后发着暗淡的、不对的颜色亮上好多年。可是雾渗了进来。有些盒子漏了:在错误的时刻转动把手,会露出里面一只做到一半的齿轮;一只测量盒会给出过期的读数,除非你在拨动它之前先读。而且行会从未就一种盖子记法的标准达成一致,所以流传着两种互不相容的记法,盒子有时还彼此循环地列在对方的清单里。米拉的回答不是一只完美的盒子。而是一条规则:先造那只朴素而正确的盒子,只封住你已经理解的东西,被误用时让它尖叫,只有当第二个使用者真的出现时才加宽把手。城里的灯依然可修——不是因为雾散了,而是因为它如今必须一次一个把手地爬进来。

故事里的每样东西代表什么

故事里书里
老师傅那只柜子,每个齿轮都咬着其他所有齿轮复杂性:建立在简单规则上的程序会累积出自己的规则,直到程序员迷失;没有边界的代码会成为一大团泥球
每只只干一件事、只承载一个有名字想法的封盒单一概念提取式的抽象,它使接口成为可能
外面的把手接口:一个部件有意暴露的有限操作集合
盖子上刻着的、它所需盒子的清单模块、包与依赖:一个部件既声明它的公开接口,也声明它所依赖的东西
同一根曲柄驱动许多不同的内芯类、原型与多态:按预期接口写的代码,接受任何支持该接口的值
铸一只新盒子,而不是把旧的刮干净持久状态与不可变性:更新产生一个全新的值,所以各操作可以孤立地推理,预览可以重算,撤销也便宜
转错时的刺耳哨音,对比那块只会耸肩、发着不对光的黄铜对抗松散语义的大声失败纪律:类型转换让荒谬的计算悄悄继续,所以 === 、断言、异常和测试补上缺失的纪律
会漏的盒子——转到一半瞥见的半成品齿轮,除非在拨动之前先读,否则读到过期的读数内部终究会泄漏的张力:经由异步间隙泄漏的操作中间状态,DOM 代码里延迟的布局与实时集合,正则使用中的 lastIndex

寓言在哪里失真

这则寓言的扭曲,在于让边界看起来像物理的封印。在书里,边界只是程序员信守的一种约定;JavaScript 里没有任何东西强制执行它,而规定它的这本书同时也展示了它阻止不了的泄漏。寓言还装不下那些给接口提供机制的东西——持有引用的绑定,由程序文本而不是由调用者确定的作用域,每次调用新生的、使闭包能携带记忆的局部绑定,以及由异常来逐层展开的、待返回的调用栈。它也装不下后半部分对这套词汇能够扩展的证明:一个解析器加一个只有寥寥几种情形的求值器就能运行任何程序,而控制结构之所以需要特殊形式,是因为普通的应用先对所有参数求值;事件靠通知而不靠轮询;Promise 在单线程事件循环上代表稍后才有的值。而且书自己的证据比它的寓意更单薄:这些主张建立在短小的代码片段、一个绘图项目和小小的玩具对象、单次计时运行,以及一门只拿一个求和程序演练过的微型语言之上——所以边界的收益是被推理出来的,而不是在大规模上被演示过的,而寓言那个自信的圆满结局,是最没有挣得的部分。

《JavaScript 编程精解》的核心论点

全书捍卫的 10 个论点,按论证需要的顺序排列。

  1. 用与问题相符的词汇来表达解决方案,即使总代码量增加,也更可能是正确的,这就是更高层次的语言和新抽象值得它们的代价的原因。

    Chapter 5. Higher-Order Functions阅读这一节 →

  2. 类型是覆盖在未加区分的比特之上的一层解释,而不是物理性质,所以 JavaScript 松散的语义让荒谬的计算悄悄地继续,程序员必须用 ===、断言和测试补上缺失的纪律。
  3. 程序只靠持有引用的绑定来记忆,而因为局部绑定在每次调用时都是新创建的,每次调用都是隔离的,这正是闭包得以可能的原因。
  4. 原子值表达不了真实的信息,所以复合结构是必要的,而形状良好的数据,甚至能把一条统计公式变成普通的代码。
  5. 程序只有被拆成边界严格的部分、这些部分既暴露有限的接口又声明它们的依赖,才保持可修复;结构性的工作会被忽视,因为它的回报落在未来的维护者身上,这就是程序会腐化成纠缠的原因。
  6. 问题里的每个名词并不都值得有它自己的可变类;计算出新的固定值而不是修改旧的,才使操作可以孤立地推理、预览可以重算、撤销便宜。
  7. 错误和模式应当由明确的理论而不是猜测来处理:把异常限定在失败真正被处理的地方,通过检验假设来调试,并把贪婪匹配当作一种会悄悄匹配过多的回溯搜索。
  8. 编程语言不是魔法:一个产出语法树的解析器,加上一个只有寥寥几种情形的求值器,就能运行任何程序,不过控制结构必须是特殊形式,因为普通的应用先对所有参数求值。
  9. 是浏览器单独让 JavaScript 变得重要,但它们的平台是靠去中心化的、临时凑出的特性长大的,所以它既赋能又设计糟糕——之所以还能驾驭,只因为每一层协议都把下面的凌乱抽象掉了,而页面代码被严格限制。
  10. DOM 笨拙,是因为它被设计成一个与语言无关的接口,所以要用选择器而不是固定的子节点路径去导航它,在修改之前只测量一次布局,并让注册的处理函数通知你,而不是去轮询。

逐章导读

每一章都在本页:论证怎样展开、关键论点、常见误读和核心概念。从目录里选一章。

导言

while 循环如何取代手动跳转?

导言是怎样展开的AI 辅助

开篇:一个被框成缺口的问题。本章以埃伦·乌尔曼(Ellen Ullman)的一段题词开头,警告说计算机不是照着我们的形象造出来的,而是“我们自身极其细小的一部分的投射”——那一部分,交给了逻辑和规则。这段引文为后面的一切定下基调:这将是一本讲怎样迁就一台机器的狭隘的书,而不是歌颂它的威力。正式的论证随后从一个平常的观察开始——计算机像螺丝刀一样常见,却复杂得多——并很快收窄到一个具体的缺口。对常见的任务(电子邮件、计算器),已经有现成的应用;对独特的或没有边界的任务,则没有。编程正是为填这个缺口而被引入,并被直白地定义:为一台“愚蠢、迂腐”的机器构造一组精确的指令,作者称这种活动“在根本上乏味而令人沮丧”,然后才承认它也可能有回报、很快,还是一道好谜题。从这里起,开篇转向语言:编程大多是通过人工构造的语言完成的,而这些语言出人意料地借鉴了人与人交谈的方式。一段简短的历史插叙——20世纪八九十年代的 BASIC 和 DOS 提示符,让位给“更易学,但自由度更小”的可视界面——落在了本书选定的主题上:JavaScript,只要你知道往哪儿看,它还在那里,内置在每一个现代浏览器里。

展开,第一乐章:困难是真的,而且得由你自己去面对。“关于编程”这一节从主题转向读者。作者直白地说,编程很难,并诊断了原因:根本的规则很简单,但建立在它们之上的程序会累积出足够的复杂性,生出自己的规则——“在某种意义上,你是在建造自己的迷宫,而且很容易在里面迷路。”这变成了体己的劝告:读这本书有时会令人沮丧,但挣扎并不意味着你的能力有问题;歇一歇,重读,做做例子。第二段题词,出自勒古恩(Le Guin),给这份劝告提供了节奏(“当行动变得无益时,就去收集信息……”)。这一节随后去寻找程序究竟是什么的定义,承认类比总有不足,暂且落在机器的比较上——许多部件,彼此相连——然后给出本章被引用最多的表述:程序是“一座思想的建筑”,没有成本,没有重量,在我们手下轻易地生长,也同样轻易地变得复杂。于是有了统领全书的论点:“编程的技艺,是构建不让程序员困惑的程序的技艺。”这一乐章以有意转向一个对立的立场收束。有些程序员靠把自己限制在“最佳实践”和一个安全区里来管理复杂性;作者拒绝这种做法,说它既无聊又无效,坚持新问题需要新解法,你应该至少犯一次设计错误才能理解它们,而品味是“靠实践培养出来的,而不是从一份规则清单里学到的。”

展开,第二乐章:同一个程序,爬上一架抽象的梯子。“为什么语言重要”通过一个单一的演示,把本章抽象的主张变得具体,这个演示在五个层级上反复进行。它从最底层开始——在一台假想的机器上,用九行二进制把 1 到 10 加起来——并配上对开关和穿孔纸板的提醒,以及一句苦笑的让步:输入那些比特图案至少曾赐予人“一种自己是强大巫师的深切感觉”。同一个程序随后被改写成关于内存位置的、带编号的英文指令;再用名字(total、count、compare)和标签来写,这时作者停下来把逻辑走了一遍,解释说那个别扭的 compare 变量之所以存在,只因为原始的机器除了判断一个数是不是零,什么也测不了。然后是 JavaScript:一个六行的 while 循环,其中手动的跳转消失了,与零比较的花招也不再需要——“编程语言……可以替我们处理那些无趣的细节。”最后,假定有假想的 range 和 sum 操作,程序坍缩成一行,读起来几乎像英语。寓意被明说了:同一个程序可以被表达得晦涩,也可以被表达得易读,而一门好语言让程序员能在更高的层面上说话——略去细节,提供构件,让你定义自己的构件,并让它们易于组合。这一节是本章的引擎:它把“语言重要”从一句断言,变成了读者亲眼看着发生的事。

转折:从一般的编程,转到这门具体的、不完美的语言。“什么是 JavaScript?”从原理转向偶然与历史。JavaScript 于 1995 年出现在 Netscape Navigator 里,扩散到每一个主流浏览器,并使交互式的 Web 应用成为可能。作者指出,这个名字几乎与 Java 没有关系,选它是出于营销考虑,而不是出于良好的判断——“现在我们被这个名字套住了。”在 Ecma 下的标准化产生了 ECMAScript,它不过是同一门语言的另一个名字。接着是本章最坦率的一段:人们对 JavaScript 说了很难听的话,而其中许多是真的;作者本人起初就鄙视它,因为它几乎什么都接受,却把它解释得与你的本意不同。他诚实地把责任分摊给他自己的缺乏经验和一个真实的设计缺陷——这门语言的宽松本意是帮助初学者,却多半使 bug 更难找。让步随后得到平衡:同样的灵活性允许了僵硬的语言所禁止的技巧,造就一种随意而愉快的风格,而在认真学会它之后,他慢慢喜欢上了它。接下来是一份压缩的版本史(ES3 的漫长统治,在政治上不可能的第4版于 2008 年被放弃,2009 年温和的第5版,2015 年重大的第6版,此后每年的小更新),并附带实用的说明:旧程序仍能工作,较老的浏览器可能滞后,而本书使用 2024 年的版本。最后,范围越过浏览器扩大——MongoDB、CouchDB,尤其是 Node.js——使 JavaScript 不被呈现成只用于 Web 的工具。

收束:实用的说明、一张地图,和一次握手。最后三节是杂务,而且它们有意地从论证降到后勤。“代码,以及怎么处理它”坚持认为阅读和编写代码不可或缺:别对着例子一扫而过,在你真正写出一个能运行的解答之前别以为理解了一道习题,并且在真正的解释器里运行一切,这样才能得到反馈,也才会被诱惑去做实验。它指向在线版——那里的例子可以点击、可以编辑——和带有起始代码与解答的习题页,同时警告说,后面的章节依赖特定的环境,也依赖按章打包成 ZIP 提供的附带文件。“本书概览”随后把版图分成三部分摆出来——十二章讲语言,七章讲浏览器,两章讲 Node.js——外加五个项目章,并对逐章做了概述,其中包括送货机器人、编程语言的实现、平台游戏和像素画程序。“排版约定”解释了等宽字体,展示了一个 factorial 函数,并演示了表示预期输出的 // → 记法——读者已经在 55 的例子里见过两次的同一种记法,现在被正式命名。本章结束于一句简短的“祝你好运!”,它合上了前面那些坦率的警告所开启的圈:困难从未被否认,而读者无论如何被送上前去。

关键论点,以及支撑它们的原文

论点 1

编程本来就乏味而令人沮丧,却值得去做,因为它能让你让一台机器做它本来做不到的事。

作者的推理建立在计算机是什么之上:它们是照字面行事、迂腐的设备,只做直截了当的事,所以指令必须以痛苦的精确来写明。历史把代价说得具体——早期程序员拨一排排开关,或者打孔纸板,所以连简单的程序都要求巧思,复杂的几乎无法设想。与此相对,机器执行那些简单步骤的速度如此之快,以致一个程序几秒钟就能完成手工永远不会去做的事。

Introduction阅读这一节 →

论点 2

固定的最佳实践清单,是对付复杂性的糟糕防御;好的判断靠实践、靠你自己犯设计错误来建立。

在确立了复杂性是核心困难之后,作者转而对付那个显而易见的补救办法。有些程序员把自己限制在一小撮被充分理解的技术和关于程序形式的严格规则里。他论证说这既无聊又无效,因为这个领域年轻、变化快、五花八门,足以容纳非常不同的有效做法,而新问题要求新的解法。他补充说,你应该把那些经典的设计错误至少犯一次,才能理解它们——靶子是僵硬的规则手册,而不是谨慎或自律。

Introduction阅读这一节 →

论点 3

同一个程序,可以写成读不懂的噪音,也可以写成接近英语的东西,所以你用的语言决定了你的代码能有多易懂。

本章让一个任务——把 1 到 10 加起来——沿着一架梯子走下来。在原始二进制里,它是九行看不出意义的比特。用英文指令写出来,它可读,却仍然晦涩,满是内存位置的编号。把这些位置命名为 total、count 和 compare,逻辑就显出来了。在 JavaScript 里,一个 while 循环去掉了手动跳转和与零比较的花招。最后,有了 range 和 sum,整件事就是一行读起来像句子的话——不过本章指出,这两个操作要到后面才有定义。

Introduction阅读这一节 →

另外 1 个论点
论点 4

JavaScript 的极度宽松既是一个真实的缺陷,同时也是它魅力的来源。

作者拿自己当个案。头一回被逼着写 JavaScript 时,他鄙视它:它接受他敲进去的任何东西,却把它解释成与他本意截然不同的东西。他承认其中一部分是他自己缺乏经验,却坚持认为确有一个真实的问题,因为这门语言几乎什么也不提示。把这一点倒过来,他说同样的宽松给僵硬的语言所禁止的技巧留出了空间,并支撑一种随意而愉快的风格——所以在认真学会它之后,他慢慢喜欢上了它。

Introduction阅读这一节 →

它是怎样运作的

本章论证所依靠的 3 个机制。

机制 1

程序的增长催生它自己的复杂性

程序造起来不花成本,敲着敲着就轻易地膨胀,所以它不断增长;每添一个部件都与其他部件相互作用,简单的基础规则堆积成需要自己那套规则的结构。结果是程序员亲手造出的、又会在其中迷路的迷宫,这就是为什么真正的技艺是写出始终可理解的程序。

机制 2

更高层的构造吸收了无趣的细节

原始的机器逼你把每一步都写出来:存一个临时值,做减法,与零比较,跳回某个行号。提供循环结构的语言自己处理跳转和比较,而自制的构件能把整个计算折叠进一次调用。每一层都去掉从来不属于真正问题的记账工作,所以剩下的读起来更贴近意图。

机制 3

宽容隐藏了错误,而不是缓解错误

JavaScript 被设计成几乎接受初学者敲进去的任何东西,而不是拒绝它。因为什么都不拒绝,敲错的或糊涂的代码照样运行,只是意思不同于作者本意。所以系统从不指向问题,定位问题的工作就完全转到了程序员身上,使调试变得更难,而不是更容易。

常见误读

你可能以为

既然复杂性是主要的危险,稳妥的做法就是采用一套严格的最佳实践规则,并待在里面。

其实

作者把品味当作长出来的,而不是颁发的:对一个好程序是什么样子的感觉来自实践,而这实践的一部分,就是犯设计错误并理解它们。由于编程仍然年轻而多样,新问题往往需要没有任何现成规则手册预见到的解法,所以一个自己划定的安全区,限制你多过保护你。

Introduction阅读这一节 →

你可能以为

一门几乎接受你敲进去的任何东西的语言更友好,所以初学者用它更好。

其实

那是这套设计背后的本意,但作者讲的是实践中相反的效果:什么都不拒绝时,错的代码照样运行,悄悄地意味着别的东西,而系统从不告诉你麻烦在哪里。这份宽松真正的好处在别处——在它所允许的不寻常的技巧和随意的风格上——而不在于让新手免于困难。

Introduction阅读这一节 →

导言的核心概念

程序
一段精确指令的文本,它同时也是指挥计算机的力量,以及计算机内存里的数据。
适用处: 与熟悉事物的类比只是部分贴合;与机器的比较只是表面上贴合。
复杂性
简单的规则堆积成连作者自己都被弄糊涂的程序的那种方式。
适用处: 无法靠固定的最佳实践规则消除;品味是靠实践培养出来的。
抽象
让语言或你自己的构件去处理细节,这样你就能在更高的层面上谈论动作。
适用处: 只有当被省略的细节对这个任务来说真的无趣时才有用。
另外 0 个概念

读后感AI 陪读

一篇不愿显得欢迎的导言,把信用花在了诚实上

一篇格外愿意显得不受欢迎的导言:它开篇就讲乏味和挫败,承认作者曾经鄙视他即将要教的这门语言,并拒绝递上读者最想要的最佳实践清单。把它维系在一起的,是一个用两种口吻陈述的论点——复杂性是自己制造的,唯一真正的防御是对人的头脑的可理解。从比特到英语、到 while、到 sum(range()) 的那架梯子,就是这个论证的证明:每一级去掉的不是工作,而是噪音,使注意力能升到上一层。与此相对的,是诚实的摩擦:反对规则的立场很容易被误读为对混乱的许可,而对 JavaScript 宽容的叙述悄悄承认了,这本书所选的语言在结构上就不擅长教学,因为它吞掉错误,而不是指出它们。本章的补偿是动机上的,而不是技术上的——去运行代码,别相信你自己的理解感,歇一歇,而不是对自己下一个裁决。归根结底,这是一个自洽的立场:它把读者当作一个会困惑的人,假定这份困惑并不使人失格,并把自己的信用花在把这话大声说出来上。

在 FeedLitra 里读导言,每段旁边都有这些卡片。

开始读书

论证在哪里吃力

不是反驳,而是细心的读者会想追问的地方。

  1. 书敦促把内部藏在接口后面,却又反复展示它们如何泄漏——原型属性漏进普通对象充当的映射,lastIndex 和码元漏进正则的使用,实时集合和延迟的布局漏进 DOM 代码,以及经由异步间隙泄漏的操作中间状态。

    Chapter 6 › Classes阅读这一节 →

  2. 那些被夸赞为方便的同一批语言特性——类型转换、UTF-16 字符串、贪婪模式、沙箱里被封锁却有用的能力——恰是产生 bug 和需要判断取舍的东西,而书接着教人绕开它们。

    Chapter 1 › Automatic type conversion阅读这一节 →

  3. 模块化被开成对付纠缠的药方,而 JavaScript 自己却带着两套不安共存的模块系统,还允许循环依赖,所以这剂被开出的药方本身就是碎裂的。

    Chapter 10. Modules阅读这一节 →

  4. 不可变性和集中分派被说成是在简化,然而撤销必须限频到每秒一份快照,所有逻辑都集中在单个 update 函数里——所以成本是被封顶、被挪走,而不是被消除。

    Chapter 19 › Undo history阅读这一节 →

另外 1 处张力,以及 8 条证据上的局限
  1. 写自己的语言被捧为解放,可书也承认 Egg 程序比它的 JavaScript 等价物难看得多,使人弄不清什么时候一种领域特定的记法才划算。

    Chapter 12 › The evaluator阅读这一节 →

证据上的局限

  1. 主张大多建立在简短的示例片段、类比和假想的机器上,而不是系统的数据,像成年人两万词的词汇量、或输出写到 1000 这样的数字,只是用来说明规模。

    Introduction阅读这一节 →

  2. 性能与策略的比较来自单次运行——一次混合布局读取与修改的计时,一条被抽样的机器人路线,一个带限定条件的递归对循环的比值——所以它们无法确立可靠的量级。

    Chapter 14 › Layout阅读这一节 →

  3. 模块化和接口的论证是在小小的玩具对象和一个绘图项目上演示的,在那里,零散的状态只会害到一个取色器,所以扩展性是被推理出来的,而不是被展示出来的。

    Chapter 10. Modules阅读这一节 →

  4. Egg 只在一个不到 150 行的小求和程序上被演练过,而可替换显示的说法靠的是实际造出的那两个显示,所以两种设计都没有在演示之外得到检验。

    Chapter 12 › The evaluator阅读这一节 →

  5. 游戏物理承认是临时凑合的:碰撞需要一个真正的引擎,而取消被挡住的运动只在动画步长很小的情况下才有效。

    Chapter 16 › Motion and collision阅读这一节 →

  6. 安全和失败处理被处理得带着明确的保留或只有最浅的深度——HTTPS 被称作不完美,目录穿越只用一次解析再比较的检查来应对,韧性不过是一次延迟重试。

    Chapter 18 › Security and HTTPS阅读这一节 →

  7. 概括是从单个案例外推出来的:字符类的指导来自一个非英文的例子,不可靠的按键事件来自 Android 键盘和输入法软件,位图加载被限制在 100 乘 100 像素且不带 alpha。

    Chapter 9 › International characters阅读这一节 →

  8. 历史性的和作者个人的判断,是作为推理而不是证据提出的——JavaScript 适合 Node,是从缺乏输入输出的惯例推出来的,作者早年对这门语言下的结论,他自己也承认是在不知道自己在干什么的时候做出的。

    Chapter 20 › Background阅读这一节 →

全书概念导图

5 组概念:通俗的叫法、一句话解释,以及原书自己的用语。

1 · 程序由什么构成
精确的指令,用人发明的语言写成
告诉计算机做什么的精确指令;更高层的人工语言省去无趣的细节,并提供可组合的构件,ECMAScript 把 JavaScript 标准化。原书用语: Program and programming language
比特在类型告诉你怎么读之前毫无意义
被当作一份信息来对待的一块比特,它的类型是一种解释性的角色,而不是物理上的差别;JavaScript 悄悄地把值转换成运算符所需的类型,由此产生意外和 NaN。原书用语: Value, type and coercion
产生值的小构件,嵌套在整句式的步骤里
任何产生一个值的片段,可无限嵌套;语句是代码里的一个整句,只在它改变了某个可观察的东西时才有用。原书用语: Expression, statement and side effect
各步骤运行的顺序:直行、分支或重复
语句运行的顺序:直线向前,用 if/else 分支,用循环重复,或由接收未求值表达式的特殊形式来选择。原书用语: Control flow
2 · 函数如何记住东西
名字持有值,这就是代码拥有的全部记忆
一个抓住值的名字,使程序记得它,并且可以重新挂接;全部存活绑定(包括内置的)的集合构成环境。原书用语: Binding and environment
一块打包成值、可以被调用的工作
包在一个值里的一段程序,带着参数被调用,也可能返回一个值,提供内置命令所缺的词汇;高阶函数接收或返回函数,留下由调用者去填的空缺。原书用语: Function
每次调用都得到新的局部变量,所以各次调用保持分离
每个块建立一个新的作用域,由程序文本而不是调用者决定;局部绑定每次调用都是新的,所以返回的函数能保留它们,而栈保存着待返回的上下文。原书用语: Scope, closure and call stack
抽出一个清晰的想法,给它起个名字
把一个清晰的想法命名为它自己的操作,包括没有副作用的纯函数,使人能讨论一个问题而不必带上它附带的步骤。原书用语: Abstraction by single-concept extraction
3 · 把数据塑造成真实的事物
把值归入对象和列表,按引用共享
对象把有名字的属性归在一起,数组容纳有索引的值;数组是对象的一种,带有方法、可变性、身份,以及扁平文本的序列化。原书用语: Composite data and reference identity
一个部件允许外人使用的那几个操作
一个部件有意暴露的有限操作集合——对象类型、模块、角色或显示——使统一的代码能驱动各不相同的实现。原书用语: Interface
共享的行为住在类背后的原型里
对象链接到持有实例共享内容的原型;类把构造函数和方法打包,继承派生出新类,而按预期接口写的代码,接受任何支持该接口的值。原书用语: Class, prototype and polymorphism
造一个新值,而不是改旧的
永不改变的值;更新产生一个全新的值,所以每个操作都可以孤立地推理,预览可以重算,旧的副本依然有效。原书用语: Persistent state and immutability
4 · 让大程序保持可修
简单的规则堆积起来,直到没人跟得上
建立在简单规则上的程序会累积出自己的规则,直到程序员迷失;你还能把自己系统的多少装在脑子里,就是边界所保护的预算。原书用语: Complexity
让错误早早大喊,而不是悄悄溜过
让错误早早浮出水面的做法——严格模式、断言、错误子类、把待返回的调用逐层展开直到被捕获的异常、重新抛出意料之外的异常,以及自动化测试。原书用语: Loud failure discipline
简洁的文本模式,会不断重试直到匹配
一门描述文本形状的晦涩模式语言,由一种重试已记住位置的搜索来匹配,贪婪重复尽可能多地匹配,而 lastIndex 和码元会泄漏出来。原书用语: Regular expression and backtracking
一个声明自己提供什么、需要什么的部件
一个程序部件既声明它的公开接口,也声明它所依赖的东西,可作为有版本的、可安装的包发布;没有这样的边界,代码就成了一大团泥球。原书用语: Module, package and dependency
5 · 从解析器到浏览器
文本变成树,一个微型求值器去运行它
由文本产生的、由嵌套表达式构成的、有分支无环的结构,由求值器来解释;为一个狭窄领域而设的微型记法,能把那个领域的事说得更直接。原书用语: Parser, syntax tree and domain-specific language
现在就开始缓慢的工作,稍后再处理结果
开始缓慢的工作而不让程序停下;事件循环一次一个地运行脚本和回调,Promise 代表稍后才有的值、传播拒绝,并在 async 函数里被 await。原书用语: Asynchronicity and promises
叠起来的协议让每一层都藏住它下面那一层
叠在一起的协议和格式——TCP、带方法和头的 HTTP、URL、HTML 与实体、fetch、URL 编码——各自藏住下面的凌乱,沙箱则限制页面代码能触及的东西。原书用语: Web platform layering
一棵由标签构成的活的树,脚本读它、改它
浏览器里每个标签对应一个对象的那棵树,脚本读取并更新它,其中有实时与静态的集合、延迟的布局计算、CSS 的优先级,以及定位。原书用语: DOM, layout and styling

读后感AI 陪读

没有哪台机器会保护你免受你自己制造的复杂性之害

通读下来,《Eloquent JavaScript》不是一本碰巧写得好的语言手册——它是一本关于管理复杂性的书,只是拿 JavaScript 当它那个充满敌意的试验场。论点在导言里就埋下了,且从未放弃:复杂性是自己制造的,没有哪台机器会保护你免受它之害,唯一持久的防御,是对人类读者的可理解。之后的每一章,都是围绕这个代价由谁来承担、又怎样来偿付所作的变奏。主线沿着三道渐次展开的弧线推进。第一道是语言本身(第1至8章):类型是强加在未加区分的比特之上的一种虚构,结构是值变成句子的那一刻,函数是铸造内置功能从未提供的词汇的核心工具,对象和数组是对原子值描述不了任何真实事物的承认,高阶函数是被诚实标了价的抽象,价码体现在分配和读者必须学习的概念上,封装是一种文化而不是一种特性——继承被有意降到最后,因为它耦合了本章其余部分一直在努力分开的东西——最后是 bug,在那里,正确性被重新界定为一种程序员自己提供的纪律,因为语言几乎什么也不提供。第二道是从业者的一层(第9至12章):正则表达式证明留存下来的工具并不是设计良好的工具,模块是在主张纠缠会让你付两次账(一次在理解上,一次在复用比重写还贵的时候),异步是一架梯子,在那里等待不再是隐式的、而成了由你自己掌管,还有那个小小的解释器,它整个要点都是消胀性的——地板底下没有魔法,只有普通的递归编程。第三道是平台(第13至21章):Web 是沉积而不是设计,DOM 是在没人知道 Web 是干什么用的时候就做出的那些决定的残余,事件是被转移而不是被获得的控制权,然后是四个项目——机器人、平台游戏、像素画编辑器、技能分享网站——它们都从不同角度论证同一个结构性教训:把必须改变的值的集合缩到最小,计算出新状态而不是修改旧状态,让每一次改变都经过一条声明过的通道,让撤销之类的派生功能几乎白送地掉出来。

书里最锐利的判断是那些拒绝。哈弗贝克扣下了读者最想要的最佳实践清单,因为递过去规则,就替代了规则本来要编码的那份判断。他不让面向对象成为条件反射,把机器人那些听上去配得上有类的“东西”消解掉了。他坚持 catch 块必须保持狭窄,因为宽泛的 catch 会把真正的 bug 藏在一个被处理的症状后面。他在正确的轴上给 canvas 与 DOM 比价——谁保留了图形的描述——而不是在天真的速度上。他告诉你长轮询在今天是错误答案,却仍然教它,因为在一个只会拉取的协议里榨出推送,正是协议真正变得看得见的地方。而且他不停地坦白:他曾经鄙视这门语言,提升是一种虚构,箭头函数的存在没有什么深层理由,浏览器技术既强大又荒谬,他自己的像素画编辑器浪费工作,他的技能分享网站会丢掉进行中的输入。这份坦率才是本书真正的教学法——它示范了这样一种姿态:好代码出自一个愿意指出自己作品里错在哪里的人。我会对读者说:不要把它当参考资料来读,也不要跳过那些项目,那正是论点不再是断言、而成了演示的地方。为那些隐喻而读——绑定的触手,塑料壳里的搅拌机,Fu-Tzu 的木棍和黏土——因为它们在语法需要之前很多年,就把直觉装进了你的脑子。预期前三分之一读起来会比它的页数所暗示的更慢,而平台各章会比它们实际上更快、也更不那么要紧。并接受本书的核心交易:它不会让你变快,它会让你变得可读,而在一门如此宽容的语言里,可读性是你真正能抓住的唯一一种正确性。

关于作者:马里金·哈弗贝克(Marijn Haverbeke)

独立程序员:语言工具与编辑器

  • 独立开源程序员,常驻柏林4
  • 2007 年初为 Eloquent JavaScript 网站的控制台写出了这个编辑器的第一版6
  • 2007 年 5 月以 CodeMirror 之名发布了那份代码6
  • CodeMirror,一个面向 Web 的代码编辑器组件3
  • CodeMirror 项目的开发者与维护者6
  1. Marijn Haverbeke(个人网站)
  2. marijnh(Marijn Haverbeke)· GitHub
  3. CodeMirror — 维基百科

网络书评怎么说

各来源的评论者称这是一本清晰、设计精美的入门书,文字明晰,项目章和横向卷轴游戏的例子令人印象深刻;有几位称之为必读。批评者则反对:它的编排把闭包放在了基础之前,例子离真实的 Web 工作很远,术语很重的行文使绝对的初学者难以入手。

综合已发表书评的看法;不展示评分,完整书评请点来源链接。

  1. 书评:Eloquent JavaScript - Human Who Codes
  2. Eloquent JavaScript - Open Textbook Library 书评,作者 Brian Bird
  3. Eloquent JavaScript - deanebarker.net 书库
  4. 书评:Eloquent JavaScript: A Modern Introduction To Programming - Slashdot
  5. 适合初学者的最佳 JavaScript 书 - The freeCodeCamp Forum

来源与授权

CC BY-NC 3.0 · 非商业原书

  • 《Eloquent JavaScript》(JavaScript 编程精解)第四版,作者 Marijn Haverbeke。采用知识共享 署名-非商业性使用 3.0 许可协议(CC BY-NC 3.0);书中代码另可视为按 MIT 许可证授权。可在 eloquentjavascript.net 免费阅读。免费阅读原书 · 许可证
  • 摘要、论点、批注和读后感由 FeedLitra 根据全书原文 AI 辅助生成,引文已对照原文核对。最近更新:2026 年 10 月 22 日。

非商业许可:按许可,本页不引用原书文字,只有 FeedLitra 自己写的说明,每处都只链接回官方免费版本的对应小节。

在 FeedLitra 里一边读《JavaScript 编程精解》,一边看这些卡片。

开始读书