├── .gitignore ├── 0.md ├── 1.md ├── 10.md ├── 11.md ├── 12.md ├── 13.md ├── 14.md ├── 15.md ├── 16.md ├── 17.md ├── 18.md ├── 19.md ├── 2.md ├── 20.md ├── 21.md ├── 3.md ├── 4.md ├── 5.md ├── 6.md ├── 7.md ├── 8.md ├── 9.md ├── README.md ├── SUMMARY.md ├── cover.jpg ├── diff-en ├── 2ech0-3ech0.diff ├── 2ech1-3ech1.diff ├── 2ech11-3ech12.diff ├── 2ech12-3ech13.diff ├── 2ech13-3ech14.diff ├── 2ech14-3ech15.diff ├── 2ech15-3ech16.diff ├── 2ech16-3ech17.diff ├── 2ech17-3ech18a.diff ├── 2ech18-3ech18b.diff ├── 2ech2-3ech2.diff ├── 2ech20-3ech20.diff ├── 2ech21-3ech21.diff ├── 2ech3-3ech3.diff ├── 2ech4-3ech4.diff ├── 2ech5-3ech5.diff ├── 2ech6-3ech6.diff ├── 2ech8-3ech8.diff └── 2ech9-3ech9.diff ├── img ├── 0-0.jpg ├── 1-0.jpg ├── 10-0.jpg ├── 11-1.svg ├── 11-2.png ├── 12-0.jpg ├── 12-1.svg ├── 13-0.jpg ├── 14-0.jpg ├── 14-1.svg ├── 14-2.svg ├── 14-3.svg ├── 14-4.svg ├── 15-0.jpg ├── 16-0.jpg ├── 16-1.png ├── 16-2.svg ├── 17-0.jpg ├── 17-1.png ├── 17-2.svg ├── 17-3.svg ├── 17-4.png ├── 17-5.png ├── 18-0.jpg ├── 19-0.jpg ├── 19-1.png ├── 19-2.svg ├── 19-3.svg ├── 2-0.jpg ├── 2-1.png ├── 2-2.svg ├── 2-3.svg ├── 2-4.svg ├── 2-5.svg ├── 20-0.jpg ├── 21-0.jpg ├── 21-1.png ├── 3-0.jpg ├── 4-0.jpg ├── 4-1.svg ├── 4-2.jpg ├── 4-3.svg ├── 5-0.jpg ├── 5-1.png ├── 6-0.jpg ├── 6-1.svg ├── 7-0.jpg ├── 7-1.png ├── 8-0.jpg ├── 9-0.jpg ├── 9-1.svg ├── 9-2.svg ├── 9-3.svg └── qr_alipay.png └── styles └── ebook.css /.gitignore: -------------------------------------------------------------------------------- 1 | _book 2 | Thumbs.db 3 | -------------------------------------------------------------------------------- /0.md: -------------------------------------------------------------------------------- 1 | # 零、前言 2 | 3 | > 原文:[Introduction](https://eloquentjavascript.net/00_intro.html) 4 | > 5 | > 译者:[飞龙](https://github.com/wizardforcel) 6 | > 7 | > 协议:[CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/) 8 | > 9 | > 自豪地采用[谷歌翻译](https://translate.google.cn/) 10 | > 11 | > 部分参考了[《JavaScript 编程精解(第 2 版)》](https://book.douban.com/subject/26707144/) 12 | 13 | > We think we are creating the system for our own purposes. We believe we are making it in our own image... But the computer is not really like us. It is a projection of a very slim part of ourselves: that portion devoted to logic, order, rule, and clarity. 14 | 15 | > Ellen Ullman,《Close to the Machine: Technophilia and its Discontents》 16 | 17 | ![](img/0-0.jpg) 18 | 19 | 这是一本关于指导电脑的书。时至今日,计算机就像螺丝刀一样随处可见,但相比于螺丝刀而言,计算机更复杂一些,并且,让他们做你想让他们做的事情,并不总是那么容易。 20 | 21 | 如果你让计算机执行的任务是常见的,易于理解的任务,例如向你显示你的电子邮件,或像计算器一样工作,则可以打开相应的应用并开始工作。 但对于独特的或开放式的任务,应用可能不存在。 22 | 23 | 24 | 这就是编程可能出现的地方。编程是构建一个程序的行为 - 它是一组精确的指令,告诉计算机做什么。 由于计算机是愚蠢的,迂腐的野兽,编程从根本上是乏味和令人沮丧的。 25 | 26 | 幸运的是,如果你可以克服这个事实,并且甚至可以享受愚蠢机器可以处理的严谨思维,那么编程可以是非常有益的。 它可以让你在几秒钟内完成手动操作。 这是一种方法,让你的电脑工具去做它以前做不到的事情。 它也提供了抽象思维的优秀练习。 27 | 28 | 大多数编程都是用编程语言完成的。 编程语言是一种人工构建的语言,用于指导计算机。 有趣的是,我们发现与电脑沟通的最有效的方式,与我们彼此沟通的方式相差太大。 与人类语言一样,计算机语言可以以新的方式组合词语和词组,从而可以表达新的概念。 29 | 30 | 在某种程度上,基于语言的界面,例如 80 年代和 90 年代的 BASIC 和 DOS 提示符,是与计算机交互的主要方法。 这些已经在很大程度上被视觉界面取代,这些视觉界面更容易学习,但提供更少的自由。 计算机语言仍然存在,如果你知道在哪里看到。 每种现代 Web 浏览器都内置了一种这样的语言,即 JavaScript,因此几乎可以在所有设备上使用。 31 | 32 | 本书将试图让你足够了解这门语言,从而完成有用和有趣的东西。 33 | 34 | ## 关于程序设计 35 | 36 | 除了讲解 JavaScript 之外,本书也会介绍一些程序设计的基本原则。程序设计还是比较复杂的。编程的基本规则简单清晰,但在这些基本规则之上构建的程序却容易变得复杂,导致程序产生了自己的规则和复杂性。即便程序是按照你自己的思路去构建的,你也有可能迷失在代码之间。 37 | 38 | 在阅读本书时,你有可能会觉得书中的概念难以理解。如果你刚刚开始学习编程,那么你估计还有不少东西需要掌握呢。如果你想将所学知识融会贯通,那么就需要去多参考和学习一些资料。 39 | 40 | 是否付出必要的努力完全取决于你自己。当你阅读本书的时候发现任何难点,千万不要轻易就对自己的能力下结论。只要能坚持下去,你就是好样的。稍做休息,复习一下所学的知识点,始终确保自己阅读并理解了示例程序和相关的练习。学习是一项艰巨的任务,但你掌握的所有知识都属于你自己,而且今后的学习道路会愈加轻松。 41 | 42 | > 当行动无利可图时,就收集信息;当信息无利可图时,就休息。 43 | 44 | > Ursula K. Le Guin,《The Left Hand of Darkness》 45 | 46 | 一个程序有很多含义:它是开发人员编写的一段文本、计算机执行的一段指令集合、计算机内存当中的数据以及控制内存中数据的操作集合。我们通常很难将程序与我们日常生活中熟悉的事物进行对比。有一种表面上比较恰当的比喻,即将程序视作包含许多组件的机器,为了让机器正常工作,这些组件通过内部通信来实现整个机器的正常运转。 47 | 48 | 计算机是一台物理机器,充当这些非物质机器的载体。计算机本身并不能实现多么复杂的功能,但计算机之所以有用是因为它们的运算速度非常快。而程序的作用就是将这些看似简单的动作组合起来,然后实现复杂的功能。 49 | 50 | 程序是思想的结晶。编写程序不需要什么物质投入,它很轻量级,通过我们的双手创造。 51 | 52 | 但如果不稍加注意,程序的体积和复杂度就会失去控制,甚至代码的编写者也会感到迷惑。在可控的范围内编写程序是编程过程中首要解决的问题。当程序运行时,一切都是那么美好。编程的精粹就在于如何更好地控制复杂度。质量高的程序的复杂度都不会太高。 53 | 54 | 很多开发人员认为,控制程序复杂度的最好方法就是避免使用不熟悉的技术。他们制定了严格的规则(“最佳实践”),并小心翼翼地呆在他们安全区内。 55 | 56 | 这不仅无聊,而且也是无效的。新问题往往需要新的解决方案。编程领域还很年轻,仍然在迅速发展,并且多样到足以为各种不同的方法留出空间。在程序设计中有许多可怕的错误,你应该继续犯错,以便你能理解它们。好的程序看起来是什么样的感觉,是在实践中发展的,而不是从一系列规则中学到的。 57 | 58 | ## 为什么编程语言重要 59 | 60 | 在计算技术发展伊始,并没有编程语言这个概念。程序看起来就像这样: 61 | 62 | ``` 63 | 00110001 00000000 00000000 64 | 00110001 00000001 00000001 65 | 00110011 00000001 00000010 66 | 01010001 00001011 00000010 67 | 00100010 00000010 00001000 68 | 01000011 00000001 00000000 69 | 01000001 00000001 00000001 70 | 00010000 00000010 00000000 71 | 01100010 00000000 00000000 72 | ``` 73 | 74 | 该程序计算数字 1~10 之和,并打印出结果:`1+2+...+10=55`。该程序可以运行在一个简单的机器上。在早期计算机上编程时,我们需要在正确的位置设置大量开关阵列,或在纸带上穿孔并将纸带输入计算机中。你可以想象这个过程是多么冗长乏味且易于出错。即便是编写非常简单的程序,也需要有经验的人耗费很大精力才能完成。编写复杂的程序则更是难上加难。 75 | 76 | 当然了,手动输入这些晦涩难懂的位序列(1 和 0)来编写程序的确能让程序员感到很有成就感,而且能给你的职业带来极大的满足感。 77 | 78 | 在上面的程序中,每行都包含一条指令。我们可以用中文来描述这些指令: 79 | 80 | 1. 将数字 0 存储在内存地址中的位置 0。 81 | 82 | 2. 将数字 1 存储在内存地址的位置 1。 83 | 84 | 3. 将内存地址的位置 1 中的值存储在内存地址的位置 2。 85 | 86 | 4. 将内存地址的位置 2 中的值减去数字 11。 87 | 88 | 5. 如果内存地址的位置 2 中的值是 0,则跳转到指令 9。 89 | 90 | 6. 将内存地址的位置 1 中的值加到内存地址的位置 0。 91 | 92 | 7. 将内存地址的位置 1 中的值加上数字 1。 93 | 94 | 8. 跳转到指令 3。 95 | 96 | 9. 输出内存地址的位置 0 中的值。 97 | 98 | 虽说这已经比一大堆位序列要好读了许多,但仍然不清晰。使用名称而不是数字用于指令和存储位置有所帮助: 99 | 100 | ``` 101 | Set “total” to 0. 102 | Set “count” to 1. 103 | [loop] 104 | Set “compare” to “count”. 105 | Subtract 11 from “compare”. 106 | If “compare” is zero, continue at [end]. 107 | Add “count” to “total”. 108 | Add 1 to “count”. 109 | Continue at [loop]. 110 | [end] 111 | Output “total”. 112 | ``` 113 | 114 | 现在你能看出该程序是如何工作的吗?前两行代码初始化两个内存位置的值:`total`用于保存累加计算结果,而`count`则用于记录当前数字。你可能觉得`compare`的那行代码看起来有些奇怪。程序想根据`count`是否等于 11 来决定是否应该停止运行。因为我们的机器相当原始,所以只能测试一个数字是否为 0,并根据它做出决策。因此程序用名为`compare`的内存位置存放`count–11`的值,并根据该值是否为 0 决定是否跳转。接下来两行将`count`的值累加到结果上,并将`count`加 1,直到`count`等于`11`为止。 115 | 116 | 下面使用 JavaScript 重新编写了上面的程序: 117 | 118 | ```js 119 | let total = 0, count = 1; 120 | while (count <= 10) { 121 | total += count; 122 | count += 1; 123 | } 124 | console.log(total); 125 | // → 55 126 | ``` 127 | 128 | 这个版本的程序得到了一些改进。更为重要的是,我们再也不需要指定程序如何来回跳转了,而是由`while`结构负责完成这个任务。只要我们给予的条件成立,`while`语句就会不停地执行其下方的语句块(包裹在大括号中)。而我们给予的条件是`count<=10`,意思是“`count`小于等于 10”。我们再也不需要创建临时的值并将其与 0 比较,那样的代码十分烦琐。编程语言的一项职责就是,能够帮助我们处理这些烦琐无趣的逻辑。 129 | 130 | 在程序的结尾,也就是`while`语句结束后,我们使用`console.log`操作来输出结果。 131 | 132 | 最后,我们恰好有`range`和`sum`这类方便的操作。下面代码中的`range`函数用于创建数字集合,`sum`函数用于计算数字集合之和: 133 | 134 | ```js 135 | console.log(sum(range(1, 10))); 136 | // → 55 137 | ``` 138 | 139 | 我们可以从这里了解到,同一个程序的长度可长可短,可读性可高可低。第一个版本的程序晦涩难懂,而最后一个版本的程序则接近于人类语言的表达方式:将 1~10 范围内的数字之和记录下来(我们会在后面的章节中详细介绍如何编写`sum`和`range`这样的函数)。 140 | 141 | 优秀的编程语言可以为开发人员提供更高层次的抽象,使用类似于人类语言的方式来与计算机进行交互。它有助于省略细节,提供便捷的积木(比如`while`和`console.log`),允许你定义自己的积木(比如`sum`和`range`函数),并使这些积木易于编写。。 142 | 143 | ## 什么是 JavaScript 144 | 145 | JavaScript 诞生于 1995 年。起初,Netscape Navigator 浏览器将其运用在网页上添加程序。自此以后,各类主流图形网页浏览器均采用了 JavaScript。JavaScript 使得现代网页应用程序成为可能 —— 使用 JavaScript 可以直接与用户交互,从而避免每一个动作都需要重新载入页面。但有许多传统网站也会使用 JavaScript 来提供实时交互以及更加智能的表单功能。 146 | 147 | JavaScript 其实和名为Java的程序设计语言没有任何关系。起了这么一个相似的名字完全是市场考虑使然,这并非是一个明智的决定。当 JavaScript 出现时,Java 语言已在市场上得到大力推广且拥有了极高人气,因此某些人觉得依附于 Java 的成功是个不错的主意。而我们现在已经无法摆脱这个名字了。 148 | 149 | 在 JavaScript 被广泛采用之后,ECMA 国际制订了一份标准文档来描述 JavaScript 的工作行为,以便所有声称支持 JavaScript 的软件都使用同一种语言。标准化完成后,该标准被称为 ECMAScript 标准。实际上,术语 ECMAScript 和 JavaScript 可以交换使用。它们不过是同一种语言的两个名字而已。 150 | 151 | 许多人会说 JavaScript 语言的坏话。这其中有很多这样的言论都是正确的。当被要求第一次使用 JavaScript 编写代码时,我当时就觉得这门语言难以驾驭。JavaScript 接受我输入的任何代码,但是又使用和我的想法完全不同的方式来解释代码。由于我没有任何线索知道我之前做了什么,因此我需要做出更多工作,但这也就存在一个实际问题:我们可以自由使用 JavaScript,而这种自由却几乎没有限度。这种设计其实是希望初学者更容易使用 JavaScript 编写程序。但实际上,系统不会指出我们错在何处,因此从程序中找出问题变得更加棘手。 152 | 153 | 但这种自由性也有其优势,许多技术在更为严格的语言中不可能实现,而在 JavaScript 中则留下了实现的余地,正如你看到的那样(比如第十章),有些优势可以弥补 JavaScript 的一些缺点。在正确地学习 JavaScript 并使用它工作了一段时间后,我真正喜欢上了 JavaScript。 154 | 155 | JavaScript 版本众多。大约在 2000~2010 年间,这正是 JavaScript 飞速发展的时期,浏览器支持最多的是 ECMAScript 3。在此期间,ECMA 着手制定 ECMAScript 4,这是一个雄心勃勃的版本,ECMA 计划在这个版本中加入许多彻底的改进与扩展。但由于 ECMAScript 3 被广泛使用,这种过于激进的修改必然会遭遇重重阻碍,最后 ECMA 不得不于 2008 年放弃了版本 4 的制定。这就产生了不那么雄心勃勃的版本 5,这只是一些没有争议的改进,出现在 2009 年。 然后版本 6 在 2015 年诞生,这是一个重大的更新,其中包括计划用于版本 4 的一些想法。从那以后,每年都会有新的更新。 156 | 157 | 语言不断发展的事实意味着,浏览器必须不断跟上,如果你使用的是较老的浏览器,它可能不支持每个特性。 语言设计师会注意,不要做任何可能破坏现有程序的改变,所以新的浏览器仍然可以运行旧的程序。 在本书中,我使用的是 2017 版的 JavaScript。 158 | 159 | Web 浏览器并不是唯一一个可以运行 JavaScript 的平台。有些数据库,比如 MongoDB 和 CouchDB,也使用 JavaScript 作为脚本语言和查询语言。一些桌面和服务器开发的平台,特别是 Node.js 项目(第二十章介绍),为浏览器以外的 JavaScript 编程提供了一个环境。 160 | 161 | ## 代码及相关工作 162 | 163 | 代码是程序的文本内容。本书多数章节都介绍了大量代码。我相信阅读代码和编写代码是学习编程不可或缺的部分。尝试不要仅仅看一眼示例,而应该认真阅读并理解每个示例。刚开始使用这种方式可能会速度较慢并为代码所困惑,但我坚信你很快就可以熟能生巧。对待习题的方法也应该一样。除非你确实已经编写代码解决了问题,否则不要假设你已经理解了问题。 164 | 165 | 建议读者应尝试在实际的 JavaScript 解释器中执行习题代码。这样一来,你就可以马上获知代码工作情况的反馈,而且我希望读者去做更多的试验,而不仅仅局限于习题的要求。 166 | 167 | 可以在 中查阅本书的在线版本,并运行和实验本书中的代码。也可以在在线版本中点击任何代码示例来编辑、运行并查看其产生的输出。在做习题时,你可以访问 ,该网址会提供每个习题的初始代码,让你专心于解答习题。 168 | 169 | 如果想要在本书提供的沙箱以外执行本书代码,需要稍加注意。许多的示例是独立的,而且可以在任何 JavaScript 环境下运行。但后续章节的代码大多数都是为特定环境(浏览器或者 Node.js)编写的,而且只能在这些特定环境下执行代码。此外,许多章节定义了更大的程序,这些章节中出现的代码片段会互相依赖或是依赖于一些外部文件。本书网站的沙箱提供了 zip 压缩文件的链接,该文件包含了所有运行特定章节代码所需的脚本和数据文件。 170 | 171 | ## 本书概览 172 | 173 | 本书包括三个部分。前十二章讨论 JavaScript 语言本身的一些特性。接下来的 8 章讨论网页浏览器和 JavaScript 在网页编程中的实践。最后两章专门讲解另一个使用 JavaScript 编程的环境 —— Node.js。 174 | 175 | 纵观本书,共有 5 个项目实战章,用于讲解规模较大的示例程序,你可以通过这些章来仔细品味真实的编程过程。根据项目出现次序,我们会陆续构建递送机器人(7)、程序设计语言(12)、平台游戏(16)、像素绘图程序(19)和一个动态网站(21)。 176 | 177 | 本书介绍编程语言时,首先使用4章来介绍 JavaScript 语言的基本结构,包括第二章控制结构(比如在本前言中看到的`while`单词)、第三章函数(编写你自己的积木)和第四章数据结构。此后你就可以编写简单的程序了。接下来,第五章和第六章介绍函数和对象的运用技术,以编写更加抽象的代码并以此来控制复杂度。 178 | 179 | 介绍完第一个项目实战(7)之后,将会继续讲解语言部分,例如第八章错误处理和 bug 修复、第九章正则表达式(处理文本数据的重要工具)、第十章模块化(解决复杂度的问题)以及第十一章异步编程(处理需要时间的事件)。第二个项目实战章节(12)则是对本书第一部分的总结。 180 | 181 | 第二部分(第十三章到第十九章),阐述了浏览器 JavaScript 中的一些工具。你将会学到在屏幕上显示某些元素的方法(第十四章与第十七章),响应用户输入的方法(第十五章)和通过网络通信的方法(第十八章)。这部分又有两个项目实战章节。 182 | 183 | 此后,第二十章阐述 Node.js,而第二十一章使用该工具构建一个简单的网页系统。 184 | 185 | ## 本书版式约定 186 | 187 | 本书中存在大量代码,程序(包括你迄今为止看到的一些示例)代码的字体如下所示: 188 | 189 | ```js 190 | function factorial(n) { 191 | if (n == 0) { 192 | return 1; 193 | } else { 194 | return factorial(n - 1) * n; 195 | } 196 | } 197 | ``` 198 | 199 | 为了展示程序产生的输出,本书常在代码后编写代码期望输出,输出结果前会加上两个反斜杠和一个箭头。 200 | 201 | ```js 202 | console.log(factorial(8)); 203 | // → 40320 204 | ``` 205 | 206 | 祝好运! 207 | -------------------------------------------------------------------------------- /1.md: -------------------------------------------------------------------------------- 1 | ## 一、值,类型和运算符 2 | 3 | > 原文:[Values, Types, and Operators](http://eloquentjavascript.net/01_values.html) 4 | > 5 | > 译者:[飞龙](https://github.com/wizardforcel) 6 | > 7 | > 协议:[CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/) 8 | > 9 | > 自豪地采用[谷歌翻译](https://translate.google.cn/) 10 | > 11 | > 部分参考了[《JavaScript 编程精解(第 2 版)》](https://book.douban.com/subject/26707144/) 12 | 13 | > 在机器的表面之下,程序在运转。 它不费力就可以扩大和缩小。 在和谐的关系中,电子散开并重新聚合。 监视器上的表格只是水面上的涟漪。 本质隐藏在下面。 14 | > 15 | > Master Yuan-Ma,《The Book of Programming》 16 | 17 | ![](img/1-0.jpg) 18 | 19 | 计算机世界里只有数据。 你可以读取数据,修改数据,创建新数据 - 但不能提及不是数据的东西。 所有这些数据都以位的长序列存储,因此基本相似。 20 | 21 | 位是任何类型的二值的东西,通常描述为零和一。 在计算机内部,他们有一些形式,例如高电荷或低电荷,强信号或弱信号,或 CD 表面上的亮斑点或暗斑点。 任何一段离散信息都可以简化为零和一的序列,从而以位表示。 22 | 23 | 例如,我们可以用位来表示数字 13。 它的原理与十进制数字相同,但不是 10 个不同的数字,而只有 2 个,每个数字的权重从右到左增加 2 倍。 以下是组成数字 13 的位,下方显示数字的权重: 24 | 25 | ``` 26 | 0 0 0 0 1 1 0 1 27 | 128 64 32 16 8 4 2 1 28 | ``` 29 | 30 | 因此,这就是二进制数`00001101`,或者`8+4+1`,即 13。 31 | 32 | ## 值 33 | 34 | 想象一下位之海 - 一片它们的海洋。 典型的现代计算机的易失性数据存储器(工作存储器)中,有超过 300 亿位。非易失性存储(硬盘或等价物)往往还有几个数量级。 35 | 36 | 为了能够在不丢失的情况下,处理这些数量的数据,我们必须将它们分成代表信息片段的块。 在 JavaScript 环境中,这些块称为值。 虽然所有值都是由位构成的,但他们起到不同的作用,每个值都有一个决定其作用的类型。 有些值是数字,有些值是文本片段,有些值是函数,等等。 37 | 38 | 要创建一个值,你只需要调用它的名字。 这很方便。 你不必为你的值收集建筑材料或为其付费。 你只需要调用它,然后刷的一下,你就有了它。 当然,它们并不是真正凭空创造的。 每个值都必须存储在某个地方,如果你想同时使用大量的值,则可能会耗尽内存。 幸运的是,只有同时需要它们时,这才是一个问题。 只要你不再使用值,它就会消失,留下它的一部分作为下一代值的建筑材料。 39 | 40 | 本章将会介绍 JavaScript 程序当中的基本元素,包括简单的值类型以及值运算符。 41 | 42 | ## 数字 43 | 44 | 数字(`Number`)类型的值即数字值。在 JavaScript 中写成如下形式: 45 | 46 | ```js 47 | 13 48 | ``` 49 | 50 | 在程序中使用这个值的时候,就会将数字 13 以位序列的方式存放在计算机的内存当中。 51 | 52 | JavaScript使用固定数量的位(64 位)来存储单个数字值。 你可以用 64 位创造很多模式,这意味着可以表示的不同数值是有限的。 对于`N`个十进制数字,可以表示的数值数量是`10^N`。 与之类似,给定 64 个二进制数字,你可以表示`2^64`个不同的数字,大约 18 亿亿(18 后面有 18 个零)。太多了。 53 | 54 | 过去计算机内存很小,人们倾向于使用一组 8 位或 16 位来表示他们的数字。 这么小的数字很容易意外地溢出,最终得到的数字不能放在给定的位数中。 今天,即使是装在口袋里的电脑也有足够的内存,所以你可以自由使用 64 位的块,只有在处理真正的天文数字时才需要担心溢出。 55 | 56 | 不过,并非所有 18 亿亿以下的整数都能放在 JavaScript 数值中。 这些位也存储负数,所以一位用于表示数字的符号。 一个更大的问题是,也必须表示非整数。 为此,一些位用于存储小数点的位置。 可以存储的实际最大整数更多地在 9000 万亿(15 个零)的范围内 - 这仍然相当多。 57 | 58 | 使用小数点来表示分数。 59 | 60 | ```js 61 | 9.81 62 | ``` 63 | 64 | 对于非常大或非常小的数字,你也可以通过输入`e`(表示指数),后面跟着指数来使用科学记数法: 65 | 66 | ```js 67 | 2.998e8 68 | ``` 69 | 70 | 即`2.998 * 10^8 = 299,800,000`。 71 | 72 | 当计算小于前文当中提到的 9000 万亿的整数时,其计算结果会十分精确,不过在计算小数的时候精度却不高。正如(`pi`)无法使用有限个数的十进制数字表示一样,在使用 64 位来存储分数时也同样会丢失一些精度。虽说如此,但这类丢失精度只会在一些特殊情况下才会出现问题。因此我们需要注意在处理分数时,将其视为近似值,而非精确值。 73 | 74 | ### 算术 75 | 76 | 与数字密切相关的就是算术。比如,加法或者乘法之类的算术运算会使用两个数值,并产生一个新的数字。JavaScript 中的算术运算如下所示: 77 | 78 | ```js 79 | 100 + 4 * 11 80 | ``` 81 | 82 | 我们把`+`和`*`符号称为运算符。第一个符号表示加法,第二个符号表示乘法。将一个运算符放在两个值之间,该运算符将会使用其旁边的两个值产生一个新值。 83 | 84 | 但是这个例子的意思是“将 4 和 100 相加,并将结果乘 11”,还是是在加法之前计算乘法? 正如你可能猜到的那样,乘法首先计算。 但是和数学一样,你可以通过将加法包在圆括号中来改变它: 85 | 86 | ```js 87 | (100 + 4) * 11 88 | ``` 89 | 90 | `–`运算符表示减法,`/`运算符则表示除法。 91 | 92 | 在运算符同时出现,并且没有括号的情况下,其运算顺序根据运算符优先级确定。示例中的乘法运算符优先级高于加法。而`/`运算符和`*`运算符优先级相同,`+`运算符和`–`运算符优先级也相同。当多个具有相同优先级的运算符相邻出现时,运算从左向右执行,比如`1–2+1`的运算顺序是`(1–2)+1`。 93 | 94 | 你无需担心这些运算符的优先级规则,不确定的时候只需要添加括号即可。 95 | 96 | 还有一个算术运算符,你可能无法立即认出。 `%`符号用于表示取余操作。 `X % Y`是`Y`除`X`的余数。 例如,`314 % 100`产生`14`,`144 % 12`产生`0`。 余数的优先级与乘法和除法的优先级相同。 你还经常会看到这个运算符被称为模运算符。 97 | 98 | ### 特殊数字 99 | 100 | 在 JavaScript 中有三个特殊的值,它们虽然是数字,但看起来却跟一般的数字不太一样。 101 | 102 | 前两个是`Infinity`和`-Infinity`,它们代表正无穷和负无穷。 “无穷减一”仍然是“无穷”,依此类推。 尽管如此,不要过分信任基于无穷大的计算。 它在数学上不合理,并且很快导致我们的下一个特殊数字:`NaN`。 103 | 104 | `NaN`代表“不是数字”,即使它是数字类型的值。 例如,当你尝试计算`0/0`(零除零),`Infinity - Infinity`或任何其他数字操作,它不会产生有意义的结果时,你将得到此结果。 105 | 106 | ## 字符串 107 | 108 | 下一个基本数据类型是字符串(`String`)。 字符串用于表示文本。 它们是用引号括起来的: 109 | 110 | ```js 111 | `Down on the sea` 112 | "Lie on the ocean" 113 | 'Float on the ocean' 114 | ``` 115 | 116 | 只要字符串开头和结尾的引号匹配,就可以使用单引号,双引号或反引号来标记字符串。 117 | 118 | 几乎所有的东西都可以放在引号之间,并且 JavaScript 会从中提取字符串值。 但少数字符更难。 你可能难以想象,如何在引号之间加引号。 当使用反引号(`` ` ``)引用字符串时,换行符(当你按回车键时获得的字符)可能会被包含,而无需转义。 119 | 120 | 若要将这些字符存入字符串,需要使用下列规则:当反斜杠(`\`)出现在引号之间的文本中时,表示紧跟在其后的字符具有特殊含义,我们将其称之为转义符。当引号紧跟在反斜杠后时,并不意味着字符串结束,而表示这个引号是字符串的一部分。当字符`n`出现在反斜杠后时,JavaScript 将其解释成换行符。以此类推,`\t`表示制表符,我们来看看下面这个字符串: 121 | 122 | ```js 123 | "This is the first line\nAnd this is the second" 124 | ``` 125 | 126 | 该字符串实际表示的文本是: 127 | 128 | ``` 129 | This is the first line 130 | And this is the second 131 | ``` 132 | 133 | 当然,在某些情况下,你希望字符串中的反斜杠只是反斜杠,而不是特殊代码。 如果两个反斜杠写在一起,它们将合并,并且只有一个将留在结果字符串值中。 这就是字符串“`A newline character is written like "\n".`”的表示方式: 134 | 135 | ```js 136 | "A newline character is written like \"\\n\"." 137 | ``` 138 | 139 | 字符串也必须建模为一系列位,以便能够存在于计算机内部。 JavaScript 执行此操作的方式基于 Unicode 标准。 该标准为你几乎需要的每个字符分配一个数字,包括来自希腊语,阿拉伯语,日语,亚美尼亚语,以及其他的字符。 如果我们为每个字符分配一个数字,则可以用一系列数字来描述一个字符串。 140 | 141 | 这就是 JavaScript 所做的。 但是有一个复杂的问题:JavaScript 的表示为每个字符串元素使用 16 位,它可以描述多达 2 的 16 次方个不同的字符。 但是,Unicode 定义的字符多于此 - 大约是此处的两倍。 所以有些字符,比如许多 emoji,在 JavaScript 字符串中占据了两个“字符位置”。 我们将在第 5 章中回来讨论。 142 | 143 | 我们不能将除法,乘法或减法运算符用于字符串,但是`+`运算符却可以。这种情况下,运算符并不表示加法,而是连接操作:将两个字符串连接到一起。以下语句可以产生字符串`"concatenate"`: 144 | 145 | ```js 146 | "con" + "cat" + "e" + "nate" 147 | ``` 148 | 149 | 字符串值有许多相关的函数(方法),可用于对它们执行其他操作。 我们将在第 4 章中回来讨论。 150 | 151 | 用单引号或双引号编写的字符串的行为非常相似 - 唯一的区别是需要在其中转义哪种类型的引号。 反引号字符串,通常称为模板字面值,可以实现更多的技巧。 除了能够跨越行之外,它们还可以嵌入其他值。 152 | 153 | ```js 154 | `half of 100 is ${100 / 2}` 155 | ``` 156 | 157 | 当你在模板字面值中的`$ {}`中写入内容时,将计算其结果,转换为字符串并包含在该位置。 这个例子产生`"half of 100 is 50"`。 158 | 159 | ## 一元运算符 160 | 161 | 并非所有的运算符都是用符号来表示,还有一些运算符是用单词表示的。比如`typeof`运算符,会产生一个字符串的值,内容是给定值的具体类型。 162 | 163 | ```js 164 | console.log(typeof 4.5) 165 | // → number 166 | console.log(typeof "x") 167 | // → string 168 | ``` 169 | 170 | 我们将在示例代码中使用`console.log`,来表示我们希望看到求值结果。更多内容请见下一章。 171 | 172 | 我们所见过的绝大多数运算符都使用两个值进行操作,而`typeof`仅接受一个值进行操作。使用两个值的运算符称为二元运算符,而使用一个值的则称为一元运算符。减号运算符既可用作一元运算符,也可用作二元运算符。 173 | 174 | ```js 175 | console.log(- (10 - 2)) 176 | // → -8 177 | ``` 178 | 179 | ## 布尔值 180 | 181 | 拥有一个值,它能区分两种可能性,通常是有用的,例如“是”和“否”或“开”和“关”。 为此,JavaScript 拥有布尔(`Boolean`)类型,它有两个值:`true`和`false`,它们就写成这些单词。 182 | 183 | ### 比较 184 | 185 | 一种产生布尔值的方法如下所示: 186 | 187 | ```js 188 | console.log(3 > 2) 189 | // → true 190 | console.log(3 < 2) 191 | // → false 192 | ``` 193 | 194 | `>`和`<`符号分别表示“大于”和“小于”。这两个符号是二元运算符,通过该运算符返回的结果是一个布尔值,表示其运算是否为真。 195 | 196 | 我们可以使用相同的方法比较字符串。 197 | 198 | ```js 199 | console.log("Aardvark" < "Zoroaster") 200 | // → true 201 | ``` 202 | 203 | 字符串排序的方式大致是字典序,但不真正是你期望从字典中看到的那样:大写字母总是比小写字母“小”,所以`"Z"<"a"`,非字母字符(`!`,`-`等)也包含在排序中。 比较字符串时,JavaScript 从左向右遍历字符,逐个比较 Unicode 代码。 204 | 205 | 其他类似的运算符则包括`>=`(大于等于),`<=`(小于等于),`==`(等于)和`!=`(不等于)。 206 | 207 | ```js 208 | console.log("Apple" == "Orange") 209 | // → false 210 | ``` 211 | 212 | 在 JavaScript 中,只有一个值不等于其自身,那就是`NaN`(Not a Number,非数值)。 213 | 214 | ```js 215 | console.log(NaN == NaN) 216 | // → false 217 | ``` 218 | 219 | `NaN`用于表示非法运算的结果,正因如此,不同的非法运算结果也不会相等。 220 | 221 | ### 逻辑运算符 222 | 223 | 还有一些运算符可以应用于布尔值上。JavaScript 支持三种逻辑运算符:与(and),或(or)和非(not)。这些运算符可以用于推理布尔值。 224 | 225 | `&&`运算符表示逻辑与,该运算符是二元运算符,只有当赋给它的两个值均为`true`时其结果才是真。 226 | 227 | ```js 228 | console.log(true && false) 229 | // → false 230 | console.log(true && true) 231 | // → true 232 | ``` 233 | 234 | `||`运算符表示逻辑或。当两个值中任意一个为`true`时,结果就为真。 235 | 236 | ```js 237 | console.log(false || true) 238 | // → true 239 | console.log(false || false) 240 | // → false 241 | ``` 242 | 243 | 感叹号(`!`)表示逻辑非,该运算符是一元运算符,用于反转给定的值,比如`!true`的结果是`false`,而`!false`结果是`true`。 244 | 245 | 在混合使用布尔运算符和其他运算符的情况下,总是很难确定什么时候需要使用括号。实际上,只要熟悉了目前为止我们介绍的运算符,这个问题就不难解决了。`||`优先级最低,其次是`&&`,接着是比较运算符(`>`,`==`等),最后是其他运算符。基于这些优先级顺序,我们在一般情况下最好还是尽量少用括号,比如说: 246 | 247 | ```js 248 | 1 + 1 == 2 && 10 * 10 > 50 249 | ``` 250 | 251 | 现在我们来讨论最后一个逻辑运算符,它既不属于一元运算符,也不属于二元运算符,而是三元运算符(同时操作三个值)。该运算符由一个问号和冒号组成,如下所示。 252 | 253 | ```js 254 | console.log(true ? 1 : 2); 255 | // → 1 256 | console.log(false ? 1 : 2); 257 | // → 2 258 | ``` 259 | 260 | 这个被称为条件运算符(或者有时候只是三元运算符,因为它是该语言中唯一的这样的运算符)。 问号左侧的值“挑选”另外两个值中的一个。 当它为真,它选择中间的值,当它为假,则是右边的值。 261 | 262 | ## 空值 263 | 264 | 有两个特殊值,写成`null`和`undefined`,用于表示不存在有意义的值。 它们本身就是值,但它们没有任何信息。 265 | 266 | 在 JavaScript 语言中,有许多操作都会产生无意义的值(我们会在后面的内容中看到实例),这些操作会得到`undefined`的结果仅仅只是因为每个操作都必须产生一个值。 267 | 268 | `undefined`和`null`之间的意义差异是 JavaScript 设计的一个意外,大多数时候它并不重要。 在你实际上不得不关注这些值的情况下,我建议将它们视为几乎可互换的。 269 | 270 | ## 自动类型转换 271 | 272 | 在引言中,我提到 JavaScript 会尽可能接受几乎所有你给他的程序,甚至是那些做些奇怪事情的程序。 以下表达式很好地证明了这一点: 273 | 274 | ```js 275 | console.log(8 * null) 276 | // → 0 277 | console.log("5" - 1) 278 | // → 4 279 | console.log("5" + 1) 280 | // → 51 281 | console.log("five" * 2) 282 | // → NaN 283 | console.log(false == 0) 284 | // → true 285 | ``` 286 | 287 | 当运算符应用于类型“错误”的值时,JavaScript 会悄悄地将该值转换为所需的类型,并使用一组通常不是你想要或期望的规则。 这称为类型转换。 第一个表达式中的`null`变为`0`,第二个表达式中的`"5"`变为`5`(从字符串到数字)。 然而在第三个表达式中,`+`在数字加法之前尝试字符串连接,所以`1`被转换为`"1"`(从数字到字符串)。 288 | 289 | 当某些不能明显映射为数字的东西(如`"five"`或`undefined`)转换为数字时,你会得到值`NaN`。 `NaN`进一步的算术运算会产生`NaN`,所以如果你发现自己在一个意想不到的地方得到了它,需要寻找意外的类型转换。 290 | 291 | 当相同类型的值之间使用`==`符号进行比较时,其运算结果很好预测:除了`NaN`这种情况,只要两个值相同,则返回`true`。但如果类型不同,JavaScript 则会使用一套复杂难懂的规则来确定输出结果。在绝大多数情况下,JavaScript 只是将其中一个值转换成另一个值的类型。但如果运算符两侧存在`null`或`undefined`,那么只有两侧均为`null`或`undefined`时结果才为`true`。 292 | 293 | ```js 294 | console.log(null == undefined); 295 | // → true 296 | console.log(null == 0); 297 | // → false 298 | ``` 299 | 300 | 这种行为通常很有用。 当你想测试一个值是否具有真值而不是`null`或`undefined`时,你可以用`==`(或`!=`)运算符将它与`null`进行比较。 301 | 302 | 但是如果你想测试某些东西是否严格为“false”呢? 字符串和数字转换为布尔值的规则表明,`0`,`NaN`和空字符串(`""`)计为`false`,而其他所有值都计为`true`。 因此,像`'0 == false'`和`"" == false`这样的表达式也是真的。 当你不希望发生自动类型转换时,还有两个额外的运算符:`===`和`!==`。 第一个测试是否严格等于另一个值,第二个测试它是否不严格相等。 所以`"" === false`如预期那样是错误的。 303 | 304 | 我建议使用三字符比较运算符来防止意外类型转换的发生,避免作茧自缚。但如果比较运算符两侧的值类型是相同的,那么使用较短的运算符也没有问题。 305 | 306 | ### 逻辑运算符的短路特性 307 | 308 | 逻辑运算符`&&`和`||`以一种特殊的方式处理不同类型的值。 他们会将其左侧的值转换为布尔型,来决定要做什么,但根据运算符和转换结果,它们将返回原始的左侧值或右侧值。 309 | 310 | 例如,当左侧值可以转换为`true`时,`||`运算符会返回它,否则返回右侧值。 当值为布尔值时,这具有预期的效果,并且对其他类型的值做类似的操作。 311 | 312 | ```js 313 | console.log(null || "user") 314 | // → user 315 | console.log("Agnes" || "user") 316 | // → Agnes 317 | ``` 318 | 319 | 我们可以此功能用作回落到默认值的方式。 如果你的一个值可能是空的,你可以把`||`和备选值放在它之后。 如果初始值可以转换为`false`,那么你将得到备选值。 320 | 321 | `&&`运算符工作方式与其相似但不相同。当左侧的值可以被转换成`false`时,`&&`运算符会返回左侧值,否则返回右侧值。 322 | 323 | 这两个运算符的另一个重要特性是,只在必要时求解其右侧的部分。 在`true || X`的情况下,不管`X`是什么 - 即使它是一个执行某些恶意操作的程序片段,结果都是`true`,并且`X`永远不会求值。 `false && X`也是一样,它是`false`的,并且忽略`X`。 这称为短路求值。 324 | 325 | 条件运算符以类似的方式工作。 在第二个和第三个值中,只有被选中的值才会求值。 326 | 327 | ## 本章小结 328 | 329 | 在本章中,我们介绍了 JavaScript 的四种类型的值:数字,字符串,布尔值和未定义值。 330 | 331 | 通过输入值的名称(`true`,`null`)或值(`13`,`"abc"`)就可以创建它们。你还可以通过运算符来对值进行合并和转换操作。本章已经介绍了算术二元运算符(`+`,`–`,`*`,`/`和`%`),字符串连接符(`+`),比较运算符(`==`,`!=`,`===`,`!==`,`<`,`>`,`<=`和`>=`),逻辑运算符(`&&`和`||`)和一些一元运算符(`–`表示负数,`!`表示逻辑非,`typeof`用于查询值的类型)。 332 | 333 | 这为你提供了足够的信息,将 JavaScript 用作便携式计算器,但并不多。 下一章将开始将这些表达式绑定到基本程序中。 334 | -------------------------------------------------------------------------------- /10.md: -------------------------------------------------------------------------------- 1 | # 十、模块 2 | 3 | > 原文:[Modules](http://eloquentjavascript.net/10_modules.html) 4 | > 5 | > 译者:[飞龙](https://github.com/wizardforcel) 6 | > 7 | > 协议:[CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/) 8 | > 9 | > 自豪地采用[谷歌翻译](https://translate.google.cn/) 10 | 11 | > 编写易于删除,而不是易于扩展的代码。 12 | > 13 | > Tef,《Programming is Terrible》 14 | 15 | ![](img/10-0.jpg) 16 | 17 | 理想的程序拥有清晰的结构。 它的工作方式很容易解释,每个部分都起到明确的作用。 18 | 19 | 典型的真实程序会有机地增长。 新功能随着新需求的出现而增加。 构建和维护结构是额外的工作,只有在下一次有人参与该计划时,才会得到回报。 所以它易于忽视,并让程序的各个部分变得深深地纠缠在一起。 20 | 21 | 这导致了两个实际问题。 首先,这样的系统难以理解。 如果一切都可以接触到一切其它东西,那么很难单独观察任何给定的片段。 你不得不全面理解整个东西。 其次,如果你想在另一个场景中,使用这种程序的任何功能,比起试图从它的上下文中将它分离出来,重写它可能要容易。 22 | 23 | 术语“大泥球”通常用于这种大型,无结构的程序。 一切都粘在一起,当你试图挑选出一段代码时,整个东西就会分崩离析,你的手会变脏。 24 | 25 | ## 模块 26 | 27 | 模块试图避免这些问题。 模块是一个程序片段,规定了它依赖的其他部分,以及它为其他模块提供的功能(它的接口)。 28 | 29 | 模块接口与对象接口有许多共同之处,我们在第 6 章中看到。它们向外部世界提供模块的一部分,并使其余部分保持私有。 通过限制模块彼此交互的方式,系统变得更像积木,其中的组件通过明确定义的连接器进行交互,而不像泥浆一样,一切都混在一起。 30 | 31 | 模块之间的关系称为依赖关系。 当一个模块需要另一个模块的片段时,就说它依赖于这个模块。 当模块中明确规定了这个事实时,它可以用于确定,需要哪些其他模块才能使用给定的模块,并自动加载依赖关系。 32 | 33 | 为了以这种方式分离模块,每个模块需要它自己的私有作用域。 34 | 35 | 将你的 JavaScript 代码放入不同的文件,不能满足这些要求。 这些文件仍然共享相同的全局命名空间。 他们可以有意或无意干扰彼此的绑定。 依赖性结构仍不清楚。 我们将在本章后面看到,我们可以做得更好。 36 | 37 | 合适的模块结构可能难以为程序设计。 在你还在探索这个问题的阶段,尝试不同的事情来看看什么是可行的,你可能不想过多担心它,因为这可能让你分心。 一旦你有一些感觉可靠的东西,现在是后退一步并组织它的好时机。 38 | 39 | ## 包 40 | 41 | 从单独的片段中构建一个程序,并实际上能够独立运行这些片段的一个优点是,你可能能够在不同的程序中应用相同的部分。 42 | 43 | 但如何实现呢? 假设我想在另一个程序中使用第 9 章中的`parseINI`函数。 如果清楚该函数依赖什么(在这种情况下什么都没有),我可以将所有必要的代码复制到我的新项目中并使用它。 但是,如果我在代码中发现错误,我可能会在当时正在使用的任何程序中将其修复,并忘记在其他程序中修复它。 44 | 45 | 一旦你开始复制代码,你很快就会发现,自己在浪费时间和精力来到处复制并使他们保持最新。 46 | 47 | 这就是包的登场时机。包是可分发(复制和安装)的一大块代码。 它可能包含一个或多个模块,并且具有关于它依赖于哪些其他包的信息。 一个包通常还附带说明它做什么的文档,以便那些不编写它的人仍然可以使用它。 48 | 49 | 在包中发现问题或添加新功能时,会将包更新。 现在依赖它的程序(也可能是包)可以升级到新版本。 50 | 51 | 以这种方式工作需要基础设施。 我们需要一个地方来存储和查找包,以及一个便利方式来安装和升级它们。 在 JavaScript 世界中,这个基础结构由 [NPM](https://npmjs.org) 提供。 52 | 53 | NPM 是两个东西:可下载(和上传)包的在线服务,以及可帮助你安装和管理它们的程序(与 Node.js 捆绑在一起)。 54 | 55 | 在撰写本文时,NPM 上有超过 50 万个不同的包。 其中很大一部分是垃圾,我应该提一下,但几乎所有有用的公开包都可以在那里找到。 例如,一个 INI 文件解析器,类似于我们在第 9 章中构建的那个,可以在包名称`ini`下找到。 56 | 57 | 第 20 章将介绍如何使用`npm`命令行程序在局部安装这些包。 58 | 59 | 使优质的包可供下载是非常有价值的。 这意味着我们通常可以避免重新创建一百人之前写过的程序,并在按下几个键时得到一个可靠,充分测试的实现。 60 | 61 | 软件的复制很便宜,所以一旦有人编写它,分发给其他人是一个高效的过程。但首先把它写出来是工作量,回应在代码中发现问题的人,或者想要提出新功能的人,是更大的工作量。 62 | 63 | 默认情况下,你拥有你编写的代码的版权,其他人只有经过你的许可才能使用它。但是因为有些人不错,而且由于发布好的软件可以使你在程序员中出名,所以许多包都会在许可证下发布,明确允许其他人使用它。 64 | 65 | NPM 上的大多数代码都以这种方式授权。某些许可证要求你还要在相同许可证下发布基于那个包构建的代码。其他要求不高,只是要求在分发代码时保留许可证。 JavaScript 社区主要使用后一种许可证。使用其他人的包时,请确保你留意了他们的许可证。 66 | 67 | ## 即兴的模块 68 | 69 | 2015 年之前,JavaScript 语言没有内置的模块系统。 然而,尽管人们已经用 JavaScript 构建了十多年的大型系统,他们需要模块。 70 | 71 | 所以他们在语言之上设计了自己的模块系统。 你可以使用 JavaScript 函数创建局部作用域,并使用对象来表示模块接口。 72 | 73 | 这是一个模块,用于日期名称和数字之间的转换(由`Date`的`getDay`方法返回)。 它的接口由`weekDay.name`和`weekDay.number`组成,它将局部绑定名称隐藏在立即调用的函数表达式的作用域内。 74 | 75 | ```js 76 | const weekDay = function() { 77 | const names = ["Sunday", "Monday", "Tuesday", "Wednesday", 78 | "Thursday", "Friday", "Saturday"]; 79 | return { 80 | name(number) { return names[number]; }, 81 | number(name) { return names.indexOf(name); } 82 | }; 83 | }(); 84 | 85 | console.log(weekDay.name(weekDay.number("Sunday"))); 86 | // → Sunday 87 | ``` 88 | 89 | 这种风格的模块在一定程度上提供了隔离,但它不声明依赖关系。 相反,它只是将其接口放入全局范围,并希望它的依赖关系(如果有的话)也这样做。 很长时间以来,这是 Web 编程中使用的主要方法,但现在它几乎已经过时。 90 | 91 | 如果我们想让依赖关系成为代码的一部分,我们必须控制依赖关系的加载。 实现它需要能够将字符串执行为代码。 JavaScript 可以做到这一点。 92 | 93 | ## 将数据执行为代码 94 | 95 | 有几种方法可以将数据(代码的字符串)作为当前程序的一部分运行。 96 | 97 | 最明显的方法是特殊运算符`eval`,它将在当前作用域内执行一个字符串。 这通常是一个坏主意,因为它破坏了作用域通常拥有的一些属性,比如易于预测给定名称所引用的绑定。 98 | 99 | ```js 100 | const x = 1; 101 | function evalAndReturnX(code) { 102 | eval(code); 103 | return x; 104 | } 105 | 106 | console.log(evalAndReturnX("var x = 2")); 107 | // → 2 108 | console.log(x); 109 | // → 1 110 | ``` 111 | 112 | 将数据解释为代码的不太可怕的方法,是使用`Function`构造器。 它有两个参数:一个包含逗号分隔的参数名称列表的字符串,和一个包含函数体的字符串。 它将代码封装在一个函数值中,以便它获得自己的作用域,并且不会对其他作用域做出奇怪的事情。 113 | 114 | ```py 115 | let plusOne = Function("n", "return n + 1;"); 116 | console.log(plusOne(4)); 117 | // → 5 118 | ``` 119 | 120 | 这正是我们需要的模块系统。 我们可以将模块的代码包装在一个函数中,并将该函数的作用域用作模块作用域。 121 | 122 | ## CommonJS 123 | 124 | 用于连接 JavaScript 模块的最广泛的方法称为 CommonJS 模块。 Node.js 使用它,并且是 NPM 上大多数包使用的系统。 125 | 126 | CommonJS 模块的主要概念是称为`require`的函数。 当你使用依赖项的模块名称调用这个函数时,它会确保该模块已加载并返回其接口。 127 | 128 | 由于加载器将模块代码封装在一个函数中,模块自动得到它们自己的局部作用域。 他们所要做的就是,调用`require`来访问它们的依赖关系,并将它们的接口放在绑定到`exports`的对象中。 129 | 130 | 此示例模块提供了日期格式化功能。 它使用 NPM的两个包,`ordinal`用于将数字转换为字符串,如`"1st"`和`"2nd"`,以及`date-names`用于获取星期和月份的英文名称。 它导出函数`formatDate`,它接受一个`Date`对象和一个模板字符串。 131 | 132 | 模板字符串可包含指明格式的代码,如`YYYY`用于全年,`Do`用于每月的序数日。 你可以给它一个像`"MMMM Do YYYY"`这样的字符串,来获得像`"November 22nd 2017"`这样的输出。 133 | 134 | ```js 135 | const ordinal = require("ordinal"); 136 | const {days, months} = require("date-names"); 137 | 138 | exports.formatDate = function(date, format) { 139 | return format.replace(/YYYY|M(MMM)?|Do?|dddd/g, tag => { 140 | if (tag == "YYYY") return date.getFullYear(); 141 | if (tag == "M") return date.getMonth(); 142 | if (tag == "MMMM") return months[date.getMonth()]; 143 | if (tag == "D") return date.getDate(); 144 | if (tag == "Do") return ordinal(date.getDate()); 145 | if (tag == "dddd") return days[date.getDay()]; 146 | }); 147 | }; 148 | ``` 149 | 150 | `ordinal`的接口是单个函数,而`date-names`导出包含多个东西的对象 - `days`和`months`是名称数组。 为导入的接口创建绑定时,解构是非常方便的。 151 | 152 | 该模块将其接口函数添加到`exports`,以便依赖它的模块可以访问它。 我们可以像这样使用模块: 153 | 154 | ```js 155 | const {formatDate} = require("./format-date"); 156 | 157 | console.log(formatDate(new Date(2017, 9, 13), 158 | "dddd the Do")); 159 | // → Friday the 13th 160 | ``` 161 | 162 | 我们可以用最简单的形式定义`require`,如下所示: 163 | 164 | ```js 165 | require.cache = Object.create(null); 166 | 167 | function require(name) { 168 | if (!(name in require.cache)) { 169 | let code = readFile(name); 170 | let module = {exports: {}}; 171 | require.cache[name] = module; 172 | let wrapper = Function("require, exports, module", code); 173 | wrapper(require, module.exports, module); 174 | } 175 | return require.cache[name].exports; 176 | } 177 | ``` 178 | 179 | 在这段代码中,`readFile`是一个构造函数,它读取一个文件并将其内容作为字符串返回。标准的 JavaScript 没有提供这样的功能,但是不同的 JavaScript 环境(如浏览器和 Node.js)提供了自己的访问文件的方式。这个例子只是假设`readFile`存在。 180 | 181 | 为了避免多次加载相同的模块,`require`需要保存(缓存)已经加载的模块。被调用时,它首先检查所请求的模块是否已加载,如果没有,则加载它。这涉及到读取模块的代码,将其包装在一个函数中,然后调用它。 182 | 183 | 我们之前看到的`ordinal`包的接口不是一个对象,而是一个函数。 CommonJS 模块的特点是,尽管模块系统会为你创建一个空的接口对象(绑定到`exports`),但你可以通过覆盖`module.exports`来替换它。许多模块都这么做,以便导出单个值而不是接口对象。 184 | 185 | 通过将`require`,`exports`和`module`定义为生成的包装函数的参数(并在调用它时传递适当的值),加载器确保这些绑定在模块的作用域中可用。 186 | 187 | 提供给`require`的字符串翻译为实际的文件名或网址的方式,在不同系统有所不同。 当它以`"./"`或`"../"`开头时,它通常被解释为相对于当前模块的文件名。 所以`"./format-date"`就是在同一个目录中,名为`format-date.js`的文件。 188 | 189 | 当名称不是相对的时,Node.js 将按照该名称查找已安装的包。 在本章的示例代码中,我们将把这些名称解释为 NPM 包的引用。 我们将在第 20 章详细介绍如何安装和使用 NPM 模块。 190 | 191 | 现在,我们不用编写自己的 INI 文件解析器,而是使用 NPM 中的某个: 192 | 193 | ```js 194 | const {parse} = require("ini"); 195 | 196 | console.log(parse("x = 10\ny = 20")); 197 | // → {x: "10", y: "20"} 198 | ``` 199 | 200 | ## ECMAScript 模块 201 | 202 | CommonJS 模块很好用,并且与 NPM 一起,使 JavaScript 社区开始大规模共享代码。 203 | 204 | 但他们仍然是个简单粗暴的黑魔法。 例如,表示法有点笨拙 - 添加到`exports`的内容在局部作用域中不可用。 而且因为`require`是一个正常的函数调用,接受任何类型的参数,而不仅仅是字符串字面值,所以在不运行代码就很难确定模块的依赖关系。 205 | 206 | 这就是 2015 年的 JavaScript 标准引入了自己的不同模块系统的原因。 它通常被称为 ES 模块,其中 ES 代表 ECMAScript。 依赖和接口的主要概念保持不变,但细节不同。 首先,表示法现在已整合到该语言中。 你不用调用函数来访问依赖关系,而是使用特殊的`import`关键字。 207 | 208 | ```js 209 | import ordinal from "ordinal"; 210 | import {days, months} from "date-names"; 211 | 212 | export function formatDate(date, format) { /* ... */ } 213 | ``` 214 | 215 | 同样,`export`关键字用于导出东西。 它可以出现在函数,类或绑定定义(`let`,`const`或`var`)的前面。 216 | 217 | ES 模块的接口不是单个值,而是一组命名绑定。 前面的模块将`formatDate`绑定到一个函数。 从另一个模块导入时,导入绑定而不是值,这意味着导出模块可以随时更改绑定的值,导入它的模块将看到其新值。 218 | 219 | 当有一个名为`default`的绑定时,它将被视为模块的主要导出值。 如果你在示例中导入了一个类似于`ordinal`的模块,而没有绑定名称周围的大括号,则会获得其默认绑定。 除了默认绑定之外,这些模块仍然可以以不同名称导出其他绑定。 220 | 221 | 为了创建默认导出,可以在表达式,函数声明或类声明之前编写`export default`。 222 | 223 | ```js 224 | export default ["Winter", "Spring", "Summer", "Autumn"]; 225 | ``` 226 | 227 | 可以使用单词`as`重命名导入的绑定。 228 | 229 | ```js 230 | import {days as dayNames} from "date-names"; 231 | 232 | console.log(dayNames.length); 233 | // → 7 234 | ``` 235 | 236 | 另一个重要的区别是,ES 模块的导入发生在模块的脚本开始运行之前。 这意味着`import`声明可能不会出现在函数或块中,并且依赖项的名称只能是带引号的字符串,而不是任意的表达式。 237 | 238 | 在撰写本文时,JavaScript 社区正在采用这种模块风格。 但这是一个缓慢的过程。 在规定格式之后,花了几年的时间,浏览器和 Node.js 才开始支持它。 虽然他们现在几乎都支持它,但这种支持仍然存在问题,这些模块如何通过 NPM 分发的讨论仍在进行中。 239 | 240 | 许多项目使用 ES 模块编写,然后在发布时自动转换为其他格式。 我们正处于并行使用两个不同模块系统的过渡时期,并且能够读写任何一种之中的代码都很有用。 241 | 242 | ## 构建和打包 243 | 244 | 事实上,从技术上来说,许多 JavaScript 项目都不是用 JavaScript 编写的。有一些扩展被广泛使用,例如第 8 章中提到的类型检查方言。很久以前,在语言的某个计划性扩展添加到实际运行 JavaScript 的平台之前,人们就开始使用它了。 245 | 246 | 为此,他们编译他们的代码,将其从他们选择的 JavaScript 方言翻译成普通的旧式 JavaScript,甚至是过去的 JavaScript 版本,以便旧版浏览器可以运行它。 247 | 248 | 在网页中包含由 200 个不同文件组成的模块化程序,会产生它自己的问题。如果通过网络获取单个文件需要 50 毫秒,则加载整个程序需要 10 秒,或者如果可以同时加载多个文件,则可能需要一半。这浪费了很多时间。因为抓取一个大文件往往比抓取很多小文件要快,所以 Web 程序员已经开始使用工具,将它们发布到 Web 之前,将他们(费力分割成模块)的程序回滚成单个大文件。这些工具被称为打包器。 249 | 250 | 我们可以再深入一点。 除了文件的数量之外,文件的大小也决定了它们可以通过网络传输的速度。 因此,JavaScript 社区发明了压缩器。 通过自动删除注释和空白,重命名绑定以及用占用更少空间的等效代码替换代码段,这些工具使 JavaScript 程序变得更小。 251 | 252 | 因此,你在 NPM 包中找到的代码,或运行在网页上的代码,经历了多个转换阶段 - 从现代 JavaScript 转换为历史 JavaScript,从 ES 模块格式转换为 CommonJS,打包并压缩。 我们不会在本书中详细介绍这些工具,因为它们往往很无聊,并且变化很快。 请注意,你运行的 JavaScript 代码通常不是编写的代码。 253 | 254 | ## 模块设计 255 | 256 | 使程序结构化是编程的一个微妙的方面。 任何有价值的功能都可以用各种方式建模。 257 | 258 | 良好的程序设计是主观的 - 涉及到权衡和品味问题。 了解结构良好的设计的价值的最好方法,是阅读或处理大量程序,并注意哪些是有效的,哪些不是。 不要认为一个痛苦的混乱就是“它本来的方式”。 通过多加思考,你可以改善几乎所有事物的结构。 259 | 260 | 模块设计的一个方面是易用性。 如果你正在设计一些旨在由多人使用,或者甚至是你自己的东西,在三个月之内,当你记不住你所做的细节时,如果你的接口简单且可预测,这会有所帮助。 261 | 262 | 这可能意味着遵循现有的惯例。 `ini`包是一个很好的例子。 此模块模仿标准 JSON 对象,通过提供`parse`和`stringify`(用于编写 INI 文件)函数,就像 JSON 一样,在字符串和普通对象之间进行转换。 所以接口很小且很熟悉,在你使用过一次后,你可能会记得如何使用它。 263 | 264 | 即使没有能模仿的标准函数或广泛使用的包,你也可以通过使用简单的数据结构,并执行单一的重点事项,来保持模块的可预测性。 例如,NPM 上的许多 INI 文件解析模块,提供了直接从硬盘读取文件并解析它的功能。 这使得在浏览器中不可能使用这些模块,因为我们没有文件系统的直接访问权,并且增加了复杂性,通过组合模块与某些文件读取功能,可以更好地解决它。 265 | 266 | 这指向了模块设计的另一个有用的方面 - 一些代码可以轻易与其他代码组合。比起执行带有副作用的复杂操作的更大的模块,计算值的核心模块适用于范围更广的程序。坚持从磁盘读取文件的 INI 文件读取器, 在文件内容来自其他来源的场景中是无用的。 267 | 268 | 与之相关,有状态的对象有时甚至是有用的,但是如果某件事可以用一个函数完成,就用一个函数。 NPM 上的几个 INI​​ 文件读取器提供了一种接口风格,需要你先创建一个对象,然后将该文件加载到对象中,最后使用特定方法来获取结果。这种类型的东西在面向对象的传统中很常见,而且很糟糕。你不能调用单个函数来完成,你必须执行仪式,在各种状态中移动对象。而且由于数据现在封装在一个特定的对象类型中,与它交互的所有代码都必须知道该类型,从而产生不必要的相互依赖关系。 269 | 270 | 通常,定义新的数据结构是不可避免的 - 只有少数非常基本的数据结构由语言标准提供,并且许多类型的数据一定比数组或映射更复杂。 但是当数组足够时,使用数组。 271 | 272 | 一个稍微复杂的数据结构的示例是第 7 章的图。JavaScript 中没有一种明显的表示图的方式。 在那一章中,我们使用了一个对象,其属性保存了字符串数组 - 可以从某个节点到达的其他节点。 273 | 274 | NPM 上有几种不同的寻路包,但他们都没有使用这种图的格式。 它们通常允许图的边带有权重,它是与其相关的成本或距离,这在我们的表示中是不可能的。 275 | 276 | 例如,存在`dijkstrajs`包。 一种著名的寻路方法,与我们的`findRoute`函数非常相似,它被称为迪科斯特拉(Dijkstra)算法,以首先编写它的艾兹格尔·迪科斯特拉(Edsger Dijkstra)命名。 `js`后缀通常会添加到包名称中,以表明它们用 JavaScript 编写。 这个`dijkstrajs`包使用类似于我们的图的格式,但是它不使用数组,而是使用对象,它的属性值是数字 - 边的权重。 277 | 278 | 所以如果我们想要使用这个包,我们必须确保我们的图以它期望的格式存储。 所有边的权重都相同,因为我们的简化模型将每条道路视为具有相同的成本(一个回合)。 279 | 280 | ```js 281 | const {find_path} = require("dijkstrajs"); 282 | 283 | let graph = {}; 284 | for (let node of Object.keys(roadGraph)) { 285 | let edges = graph[node] = {}; 286 | for (let dest of roadGraph[node]) { 287 | edges[dest] = 1; 288 | } 289 | } 290 | 291 | console.log(find_path(graph, "Post Office", "Cabin")); 292 | // → ["Post Office", "Alice's House", "Cabin"] 293 | ``` 294 | 295 | 这可能是组合的障碍 - 当各种包使用不同的数据结构来描述类似的事情时,将它们组合起来很困难。 因此,如果你想要设计可组合性,请查找其他人使用的数据结构,并在可能的情况下遵循他们的示例。 296 | 297 | ## 总结 298 | 299 | 通过将代码分离成具有清晰接口和依赖关系的块,模块是更大的程序结构。 接口是模块中可以从其他模块看到的部分,依赖关系是它使用的其他模块。 300 | 301 | 由于 JavaScript 历史上并没有提供模块系统,因此 CommonJS 系统建立在它之上。 然后在某个时候,它确实有了一个内置系统,它现在与 CommonJS 系统不兼容。 302 | 303 | 包是可以自行分发的一段代码。 NPM 是 JavaScript 包的仓库。 你可以从上面下载各种有用的(和无用的)包。 304 | 305 | ## 练习 306 | 307 | ### 模块化机器人 308 | 309 | 这些是第 7 章的项目所创建的约束: 310 | 311 | ``` 312 | roads 313 | buildGraph 314 | roadGraph 315 | VillageState 316 | runRobot 317 | randomPick 318 | randomRobot 319 | mailRoute 320 | routeRobot 321 | findRoute 322 | goalOrientedRobot 323 | ``` 324 | 325 | 如果你要将该项目编写为模块化程序,你会创建哪些模块? 哪个模块依赖于哪个模块,以及它们的接口是什么样的? 326 | 327 | 哪些片段可能在 NPM 上找到? 你愿意使用 NPM 包还是自己编写? 328 | 329 | ### `roads`模块 330 | 331 | 根据第 7 章中的示例编写 CommonJS 模块,该模块包含道路数组,并将表示它们的图数据结构导出为`roadGraph`。 它应该依赖于一个模块`./graph`,它导出一个函数`buildGraph`,用于构建图。 该函数接受包含两个元素的数组(道路的起点和终点)。 332 | 333 | ```js 334 | // Add dependencies and exports 335 | 336 | const roads = [ 337 | "Alice's House-Bob's House", "Alice's House-Cabin", 338 | "Alice's House-Post Office", "Bob's House-Town Hall", 339 | "Daria's House-Ernie's House", "Daria's House-Town Hall", 340 | "Ernie's House-Grete's House", "Grete's House-Farm", 341 | "Grete's House-Shop", "Marketplace-Farm", 342 | "Marketplace-Post Office", "Marketplace-Shop", 343 | "Marketplace-Town Hall", "Shop-Town Hall" 344 | ]; 345 | ``` 346 | 347 | ### 循环依赖 348 | 349 | 循环依赖是一种情况,其中模块 A 依赖于 B,并且 B 也直接或间接依赖于 A。许多模块系统完全禁止这种情况,因为无论你选择何种顺序来加载此类模块,都无法确保每个模块的依赖关系在它运行之前加载。 350 | 351 | CommonJS 模块允许有限形式的循环依赖。 只要这些模块不会替换它们的默认`exports`对象,并且在完成加载之后才能访问对方的接口,循环依赖就没有问题。 352 | 353 | 本章前面给出的`require`函数支持这种类型的循环依赖。 你能看到它如何处理循环吗? 当一个循环中的某个模块替代其默认`exports`对象时,会出现什么问题? 354 | -------------------------------------------------------------------------------- /12.md: -------------------------------------------------------------------------------- 1 | ## 十二、项目:编程语言 2 | 3 | > 原文:[Project: A Programming Language](https://eloquentjavascript.net/12_language.html) 4 | > 5 | > 译者:[飞龙](https://github.com/wizardforcel) 6 | > 7 | > 协议:[CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/) 8 | > 9 | > 自豪地采用[谷歌翻译](https://translate.google.cn/) 10 | > 11 | > 部分参考了[《JavaScript 编程精解(第 2 版)》](https://book.douban.com/subject/26707144/) 12 | 13 | > 确定编程语言中的表达式含义的求值器只是另一个程序。 14 | > 15 | > Hal Abelson 和 Gerald Sussman,《计算机程序的构造和解释》 16 | 17 | ![](img/12-0.jpg) 18 | 19 | 构建你自己的编程语言不仅简单(只要你的要求不要太高就好),而且对人富有启发。 20 | 21 | 希望通过本章的介绍,你能发现构建自己的编程语言其实并不是什么难事。我经常感到某些人的想法聪明无比,而且十分复杂,以至于我都不能完全理解。不过经过一段时间的阅读和实验,我就发现它们其实也并没有想象中那么复杂。 22 | 23 | 我们将创造一门名为 Egg 的编程语言。这是一门小巧而简单的语言,但是足够强大到能描述你所能想到的任何计算。它允许基于函数的简单抽象。 24 | 25 | ## 解析 26 | 27 | 程序设计语言中最直观的部分就是语法(syntax)或符号。解析器是一种程序,负责读入文本片段(包含程序的文本),并产生一系列与程序结构对应的数据结构。若文本不是一个合法程序,解析器应该指出错误。 28 | 29 | 我们的语言语法简单,而且具有一致性。Egg 中一切都是表达式。表达式可以是绑定名称、数字,或应用(application)。不仅函数调用属于应用,而且`if`和`while`之类的语言构造也属于应用。 30 | 31 | 为了确保解析器的简单性,Egg 中的字符串不支持反斜杠转义符之类的元素。字符串只是简单的字符序列(不包括双引号),并使用双引号包围起来。数值是数字序列。绑定名由任何非空白字符组成,并且在语法中不具有特殊含义。 32 | 33 | 应用的书写方式与 JavaScript 中一样,也是在一个表达式后添加一对括号,括号中可以包含任意数量的参数,参数之间使用逗号分隔。 34 | 35 | ```egg 36 | do(define(x, 10), 37 | if(>(x, 5), 38 | print("large"), 39 | print("small"))) 40 | ``` 41 | 42 | Egg 语言的一致性体现在:JavaScript 中的所有运算符(比如`>`)在 Egg 中都是绑定,但是可以像其他函数一样调用。由于语法中没有语句块的概念,因此我们需要使用`do`结构来表示多个表达式的序列。 43 | 44 | 解析器的数据结构用于描述由表达式对象组成的程序,每个对象都包含一个表示表达式类型的`type`属性,除此以外还有其他描述对象内容的属性。 45 | 46 | 类型为`"value"`的表达式表示字符串和数字。它们的`value`属性包含对应的字符串和数字值。类型为`"word"`的表达式用于标识符(名称)。这类对象以字符串形式将标识符名称保存在`name`属性中。最后,类型为`"apply"`的表达式表示应用。该类型的对象有一个`operator`属性,指向其操作的表达式,还有一个`args`属性,持有参数表达式的数组。 47 | 48 | 上面代码中`> (x, 5)`这部分可以表达成如下形式: 49 | 50 | ```js 51 | { 52 | type: "apply", 53 | operator: {type: "word", name: ">"}, 54 | args: [ 55 | {type: "word", name: "x"}, 56 | {type: "value", value: 5} 57 | ] 58 | } 59 | ``` 60 | 61 | 我们将这样一个数据结构称为表达式树。如果你将对象想象成点,将对象之间的连接想象成点之间的线,这个数据结构将会变成树形。表达式中还会包含其他表达式,被包含的表达式接着又会包含更多表达式,这类似于树的分支重复分裂的方式。 62 | 63 | ![](img/12-1.svg) 64 | 65 | 我们将这个解析器与我们第 9 章中编写的配置文件格式解析器进行对比,第 9 章中的解析器结构很简单:将输入文件划分成行,并逐行处理。而且每一行只有几种简单的语法形式。 66 | 67 | 我们必须使用不同方法来解决这里的问题。Egg 中并没有表达式按行分隔,而且表达式之间还有递归结构。应用表达式包含其他表达式。 68 | 69 | 所幸我们可以使用递归的方式编写一个解析器函数,并优雅地解决该问题,这反映了语言自身就是递归的。 70 | 71 | 我们定义了一个函数`parseExpression`,该函数接受一个字符串,并返回一个对象,包含了字符串起始位置处的表达式与解析表达式后剩余的字符串。当解析子表达式时(比如应用的参数),可以再次调用该函数,返回参数表达式和剩余字符串。剩余的字符串可以包含更多参数,也有可以是一个表示参数列表结束的右括号。 72 | 73 | 这里给出部分解析器代码。 74 | 75 | ```js 76 | function parseExpression(program) { 77 | program = skipSpace(program); 78 | let match, expr; 79 | if (match = /^"([^"]*)"/.exec(program)) { 80 | expr = {type: "value", value: match[1]}; 81 | } else if (match = /^\d+\b/.exec(program)) { 82 | expr = {type: "value", value: Number(match[0])}; 83 | } else if (match = /^[^\s(),#"]+/.exec(program)) { 84 | expr = {type: "word", name: match[0]}; 85 | } else { 86 | throw new SyntaxError("Unexpected syntax: " + program); 87 | } 88 | 89 | return parseApply(expr, program.slice(match[0].length)); 90 | } 91 | 92 | function skipSpace(string) { 93 | let first = string.search(/\S/); 94 | if (first == -1) return ""; 95 | return string.slice(first); 96 | } 97 | ``` 98 | 99 | 由于 Egg 和 JavaScript 一样,允许其元素之间有任意数量的空白,所以我们必须在程序字符串的开始处重复删除空白。 这就是`skipSpace`函数能提供的帮助。 100 | 101 | 跳过开头的所有空格后,`parseExpression`使用三个正则表达式来检测 Egg 支持的三种原子的元素:字符串、数值和单词。解析器根据不同的匹配结果构造不同的数据类型。如果这三种形式都无法与输入匹配,那么输入就是一个非法表达式,解析器就会抛出异常。我们使用`SyntaxError`而不是`Error`作为异常构造器,这是另一种标准错误类型,因为它更具体 - 它也是在尝试运行无效的 JavaScript 程序时,抛出的错误类型。 102 | 103 | 接下来,我们从程序字符串中删去匹配的部分,将剩余的字符串和表达式对象一起传递给`parseApply`函数。该函数检查表达式是否是一个应用,如果是应用则解析带括号的参数列表。 104 | 105 | ```js 106 | function parseApply(expr, program) { 107 | program = skipSpace(program); 108 | if (program[0] != "(") { 109 | return {expr: expr, rest: program}; 110 | } 111 | 112 | program = skipSpace(program.slice(1)); 113 | expr = {type: "apply", operator: expr, args: []}; 114 | while (program[0] != ")") { 115 | let arg = parseExpression(program); 116 | expr.args.push(arg.expr); 117 | program = skipSpace(arg.rest); 118 | if (program[0] == ",") { 119 | program = skipSpace(program.slice(1)); 120 | } else if (program[0] != ")") { 121 | throw new SyntaxError("Expected ',' or ')'"); 122 | } 123 | } 124 | return parseApply(expr, program.slice(1)); 125 | } 126 | ``` 127 | 128 | 如果程序中的下一个字符不是左圆括号,说明当前表达式不是一个应用,parseApply会返回该表达式。 129 | 130 | 否则,该函数跳过左圆括号,为应用表达式创建语法树。接着递归调用`parseExpression`解析每个参数,直到遇到右圆括号为止。此处通过`parseApply`和`parseExpression`互相调用,实现函数间接递归调用。 131 | 132 | 因为我们可以使用一个应用来操作另一个应用表达式(比如`multiplier(2)(1)`),所以`parseApply`解析完一个应用后必须再次调用自身检查是否还有另一对圆括号。 133 | 134 | 这就是我们解析 Egg 所需的全部代码。我们使用`parse`函数来包装`parseExpression`,在解析完表达式之后验证输入是否到达结尾(一个 Egg 程序是一个表达式),遇到输入结尾后会返回整个程序对应的数据结构。 135 | 136 | ```js 137 | function parse(program) { 138 | let {expr, rest} = parseExpression(program); 139 | if (skipSpace(rest).length > 0) { 140 | throw new SyntaxError("Unexpected text after program"); 141 | } 142 | return expr; 143 | } 144 | 145 | console.log(parse("+(a, 10)")); 146 | // → {type: "apply", 147 | // operator: {type: "word", name: "+"}, 148 | // args: [{type: "word", name: "a"}, 149 | // {type: "value", value: 10}]} 150 | ``` 151 | 152 | 程序可以正常工作了!当表达式解析失败时,解析函数不会输出任何有用的信息,也不会存储出错的行号与列号,而这些信息都有助于之后的错误报告。但考虑到我们的目的,这门语言目前已经足够优秀了。 153 | 154 | ## 求值器(evaluator) 155 | 156 | 在有了一个程序的语法树之后,我们该做什么呢?当然是执行程序了!而这就是求值器的功能。我们将语法树和作用域对象传递给求值器,执行器就会求解语法树中的表达式,然后返回整个过程的结果。 157 | 158 | ```js 159 | const specialForms = Object.create(null); 160 | 161 | function evaluate(expr, scope) { 162 | if (expr.type == "value") { 163 | return expr.value; 164 | } else if (expr.type == "word") { 165 | if (expr.name in scope) { 166 | return scope[expr.name]; 167 | } else { 168 | throw new ReferenceError( 169 | `Undefined binding: ${expr.name}`); 170 | } 171 | } else if (expr.type == "apply") { 172 | let {operator, args} = expr; 173 | if (operator.type == "word" && 174 | operator.name in specialForms) { 175 | return specialForms[operator.name](expr.args, scope); 176 | } else { 177 | let op = evaluate(operator, scope); 178 | if (typeof op == "function") { 179 | return op(...args.map(arg => evaluate(arg, scope))); 180 | } else { 181 | throw new TypeError("Applying a non-function."); 182 | } 183 | } 184 | } 185 | } 186 | ``` 187 | 188 | 求值器为每一种表达式类型都提供了相应的处理逻辑。字面值表达式产生自身的值(例如,表达式`100`的求值为数值`100`)。对于绑定而言,我们必须检查程序中是否实际定义了该绑定,如果已经定义,则获取绑定的值。 189 | 190 | 应用则更为复杂。若应用有特殊形式(比如`if`),我们不会求解任何表达式,而是将表达式参数和环境传递给处理这种形式的函数。如果是普通调用,我们求解运算符,验证其是否是函数,并使用求值后的参数调用函数。 191 | 192 | 我们使用一般的 JavaScript 函数来表示 Egg 的函数。在定义特殊格式`fun`时,我们再回过头来看这个问题。 193 | 194 | `evaluate`的递归结构类似于解析器的结构。两者都反映了语言自身的结构。我们也可以将解析器和求值器集成到一起,在解析的同时求解表达式,但将其分离为两个阶段使得程序更易于理解。 195 | 196 | 这就是解释 Egg 所需的全部代码。这段代码非常简单,但如果不定义一些特殊的格式,或向环境中添加一些有用的值,你无法使用该语言完成很多工作。 197 | 198 | ## 特殊形式 199 | 200 | `specialForms`对象用于定义 Egg 中的特殊语法。该对象将单词和求解这种形式的函数关联起来。目前该对象为空,现在让我们添加`if`。 201 | 202 | ```js 203 | specialForms.if = (args, scope) => { 204 | if (args.length != 3) { 205 | throw new SyntaxError("Wrong number of args to if"); 206 | } else if (evaluate(args[0], scope) !== false) { 207 | return evaluate(args[1], scope); 208 | } else { 209 | return evaluate(args[2], scope); 210 | } 211 | }; 212 | ``` 213 | 214 | Egg 的`if`语句需要三个参数。Egg 会求解第一个参数,若结果不是`false`,则求解第二个参数,否则求解第三个参数。相较于 JavaScript 中的`if`语句,Egg 的`if`形式更类似于 JavaScript 中的`?:`运算符。这是一条表达式,而非语句,它会产生一个值,即第二个或第三个参数的结果。 215 | 216 | Egg 和 JavaScript 在处理条件值时也有些差异。Egg 不会将 0 或空字符串作为假,只有当值确实为`false`时,测试结果才为假。 217 | 218 | 我们之所以需要将`if`表达为特殊形式,而非普通函数,是因为函数的所有参数需要在函数调用前求值完毕,而`if`则只应该根据第一个参数的值,确定求解第二个还是第三个参数。`while`的形式也是类似的。 219 | 220 | ```js 221 | specialForms.while = (args, scope) => { 222 | if (args.length != 2) { 223 | throw new SyntaxError("Wrong number of args to while"); 224 | } 225 | while (evaluate(args[0], scope) !== false) { 226 | evaluate(args[1], scope); 227 | } 228 | 229 | // Since undefined does not exist in Egg, we return false, 230 | // for lack of a meaningful result. 231 | return false; 232 | }; 233 | ``` 234 | 235 | 另一个基本的积木是`do`,会自顶向下执行其所有参数。整个`do`表达式的值是最后一个参数的值。 236 | 237 | ```js 238 | specialForms.do = (args, scope) => { 239 | let value = false; 240 | for (let arg of args) { 241 | value = evaluate(arg, scope); 242 | } 243 | return value; 244 | }; 245 | ``` 246 | 247 | 我们还需要创建名为`define`的形式,来创建绑定对绑定赋值。`define`的第一个参数是一个单词,第二个参数是一个会产生值的表达式,并将第二个参数的计算结果赋值给第一个参数。由于`define`也是个表达式,因此必须返回一个值。我们则规定`define`应该将我们赋予绑定的值返回(就像 JavaScript 中的`=`运算符一样)。 248 | 249 | ```js 250 | specialForms.define = (args, scope) => { 251 | if (args.length != 2 || args[0].type != "word") { 252 | throw new SyntaxError("Incorrect use of define"); 253 | } 254 | let value = evaluate(args[1], scope); 255 | scope[args[0].name] = value; 256 | return value; 257 | }; 258 | ``` 259 | 260 | ## 环境 261 | 262 | `evaluate`所接受的作用域是一个对象,它的名称对应绑定名称,它的值对应这些绑定所绑定的值。 我们定义一个对象来表示全局作用域。 263 | 264 | 我们需要先定义布尔绑定才能使用之前定义的`if`语句。由于只有两个布尔值,因此我们不需要为其定义特殊语法。我们简单地将`true`、`false`两个名称与其值绑定即可。 265 | 266 | ```js 267 | const topScope = Object.create(null); 268 | 269 | topScope.true = true; 270 | topScope.false = false; 271 | ``` 272 | 273 | 我们现在可以求解一个简单的表达式来对布尔值求反。 274 | 275 | ```js 276 | let prog = parse(`if(true, false, true)`); 277 | console.log(evaluate(prog, topScope)); 278 | // → false 279 | ``` 280 | 281 | 为了提供基本的算术和比较运算符,我们也添加一些函数值到作用域中。为了确保代码短小,我们在循环中使用` Function`来合成一批运算符,而不是分别定义所有运算符。 282 | 283 | ```js 284 | for (let op of ["+", "-", "*", "/", "==", "<", ">"]) { 285 | topScope[op] = Function("a, b", `return a ${op} b;`); 286 | } 287 | ``` 288 | 289 | 输出也是一个实用的功能,因此我们将`console.log`包装在一个函数中,并称之为`print`。 290 | 291 | ```js 292 | topScope.print = value => { 293 | console.log(value); 294 | return value; 295 | }; 296 | ``` 297 | 298 | 这样一来我们就有足够的基本工具来编写简单的程序了。下面的函数提供了一个便利的方式来编写并运行程序。它创建一个新的环境对象,并解析执行我们赋予它的单个程序。 299 | 300 | ```js 301 | function run(program) { 302 | return evaluate(parse(program), Object.create(topScope)); 303 | } 304 | ``` 305 | 306 | 我们将使用对象原型链来表示嵌套的作用域,以便程序可以在不改变顶级作用域的情况下,向其局部作用域添加绑定。 307 | 308 | ```js 309 | run(` 310 | do(define(total, 0), 311 | define(count, 1), 312 | while(<(count, 11), 313 | do(define(total, +(total, count)), 314 | define(count, +(count, 1)))), 315 | print(total)) 316 | `); 317 | // → 55 318 | ``` 319 | 320 | 我们之前已经多次看到过这个程序,该程序计算数字 1 到 10 的和,只不过这里使用 Egg 语言表达。很明显,相较于实现同样功能的 JavaScript 代码,这个程序并不优雅,但对于一个不足 150 行代码的程序来说已经很不错了。 321 | 322 | ## 函数 323 | 324 | 每个功能强大的编程语言都应该具有函数这个特性。 325 | 326 | 幸运的是我们可以很容易地添加一个`fun`语言构造,`fun`将最后一个参数当作函数体,将之前的所有名称用作函数参数。 327 | 328 | ```js 329 | specialForms.fun = (args, scope) => { 330 | if (!args.length) { 331 | throw new SyntaxError("Functions need a body"); 332 | } 333 | let body = args[args.length - 1]; 334 | let params = args.slice(0, args.length - 1).map(expr => { 335 | if (expr.type != "word") { 336 | throw new SyntaxError("Parameter names must be words"); 337 | } 338 | return expr.name; 339 | }); 340 | 341 | return function() { 342 | if (arguments.length != params.length) { 343 | throw new TypeError("Wrong number of arguments"); 344 | } 345 | let localScope = Object.create(scope); 346 | for (let i = 0; i < arguments.length; i++) { 347 | localScope[params[i]] = arguments[i]; 348 | } 349 | return evaluate(body, localScope); 350 | }; 351 | }; 352 | ``` 353 | 354 | Egg 中的函数可以获得它们自己的局部作用域。 `fun`形式产生的函数创建这个局部作用域,并将参数绑定添加到它。 然后求解此范围内的函数体并返回结果。 355 | 356 | ```js 357 | run(` 358 | do(define(plusOne, fun(a, +(a, 1))), 359 | print(plusOne(10))) 360 | `); 361 | // → 11 362 | 363 | run(` 364 | do(define(pow, fun(base, exp, 365 | if(==(exp, 0), 366 | 1, 367 | *(base, pow(base, -(exp, 1)))))), 368 | print(pow(2, 10))) 369 | `); 370 | // → 1024 371 | ``` 372 | 373 | ## 编译 374 | 375 | 我们构建的是一个解释器。在求值期间,解释器直接作用域由解析器产生的程序的表示。 376 | 377 | 编译是在解析和运行程序之间添加的另一个步骤:通过事先完成尽可能多的工作,将程序转换成一些可以高效求值的东西。例如,在设计良好的编程语言中,使用每个绑定时绑定引用的内存地址都是明确的,而不需要在程序运行时进行动态计算。这样可以省去每次访问绑定时搜索绑定的时间,只需要直接去预先定义好的内存位置获取绑定即可。 378 | 379 | 一般情况下,编译会将程序转换成机器码(计算机处理可以执行的原始格式)。但一些将程序转换成不同表现形式的过程也被认为是编译。 380 | 381 | 我们可以为 Egg 编写一个可供选择的求值策略,首先使用`Function`,调用 JavaScript 编译器编译代码,将 Egg 程序转换成 JavaScript 程序,接着执行编译结果。若能正确实现该功能,可以使得 Egg 运行的非常快,而且实现这种编译器确实非常简单。 382 | 383 | 如果读者对该话题感兴趣,愿意花费一些时间在这上面,建议你尝试实现一个编译器作为练习。 384 | 385 | ## 站在别人的肩膀上 386 | 387 | 我们定义`if`和`while`的时候,你可能注意到他们封装得或多或少和 JavaScript 自身的`if`、`while`有点像。同样的,Egg 中的值也就是 JavaScript 中的值。 388 | 389 | 如果读者比较一下两种 Egg 的实现方式,一种是基于 JavaScrip t之上,另一种是直接使用机器提供的功能构建程序设计语言,会发现第二种方案需要大量工作才能完成,而且非常复杂。不管怎么说,本章的内容就是想让读者对编程语言的运行方式有一个基本的了解。 390 | 391 | 当需要完成一些任务时,相比于自己完成所有工作,借助于别人提供的功能是一种更高效的方式。虽然在本章中我们编写的语言就像玩具一样,十分简单,而且无论在什么情况下这门语言都无法与 JavaScript 相提并论。但在某些应用场景中,编写一门微型语言可以帮助我们更好地完成工作。 392 | 393 | 这些语言不需要像传统的程序设计语言。例如,若 JavaScript 没有正则表达式,你可以为正则表达式编写自己的解析器和求值器。 394 | 395 | 或者想象一下你在构建一个巨大的机械恐龙,需要编程实现恐龙的行为。JavaScript 可能不是实现该功能的最高效方式,你可以选择一种语言作为替代,如下所示: 396 | 397 | ```egg 398 | behavior walk 399 | perform when 400 | destination ahead 401 | actions 402 | move left-foot 403 | move right-foot 404 | 405 | behavior attack 406 | perform when 407 | Godzilla in-view 408 | actions 409 | fire laser-eyes 410 | launch arm-rockets 411 | ``` 412 | 413 | 这通常被称为领域特定语言(Domain-specific Language),一种为表达极为有限的知识领域而量身定制的语言。它可以准确描述其领域中需要表达的事物,而没有多余元素。这种语言比通用语言更具表现力。 414 | 415 | ## 习题 416 | 417 | ### 数组 418 | 419 | 在 Egg 中支持数组需要将以下三个函数添加到顶级作用域:`array(...values)`用于构造一个包含参数值的数组,`length(array)`用于获取数组长度,`element(array, n)`用于获取数组中的第`n`个元素。 420 | 421 | ```js 422 | // Modify these definitions... 423 | 424 | topScope.array = "..."; 425 | 426 | topScope.length = "..."; 427 | 428 | topScope.element = "..."; 429 | 430 | run(` 431 | do(define(sum, fun(array, 432 | do(define(i, 0), 433 | define(sum, 0), 434 | while(<(i, length(array)), 435 | do(define(sum, +(sum, element(array, i))), 436 | define(i, +(i, 1)))), 437 | sum))), 438 | print(sum(array(1, 2, 3)))) 439 | `); 440 | // → 6 441 | ``` 442 | 443 | 444 | ### 闭包 445 | 446 | 我们定义`fun`的方式允许函数引用其周围环境,就像 JavaScript 函数一样,函数体可以使用在定义该函数时可以访问的所有局部绑定。 447 | 448 | 下面的程序展示了该特性:函数f返回一个函数,该函数将其参数和f的参数相加,这意味着为了使用绑定a,该函数需要能够访问f中的局部作用域。 449 | 450 | ```js 451 | run(` 452 | do(define(f, fun(a, fun(b, +(a, b)))), 453 | print(f(4)(5))) 454 | `); 455 | // → 9 456 | ``` 457 | 458 | 回顾一下fun形式的定义,解释一下该机制的工作原理。 459 | 460 | ### 注释 461 | 462 | 如果我们可以在 Egg 中编写注释就太好了。例如,无论何时,只要出现了井号(`#`),我们都将该行剩余部分当成注释,并忽略之,就类似于 JavaScript 中的`//`。 463 | 464 | 解析器并不需要为支持该特性进行大幅修改。我们只需要修改`skipSpace`来像跳过空白符号一样跳过注释即可,此时调用`skipSpace`时不仅会跳过空白符号,还会跳过注释。修改代码,实现这样的功能。 465 | 466 | ```js 467 | // This is the old skipSpace. Modify it... 468 | function skipSpace(string) { 469 | let first = string.search(/\S/); 470 | if (first == -1) return ""; 471 | return string.slice(first); 472 | } 473 | 474 | console.log(parse("# hello\nx")); 475 | // → {type: "word", name: "x"} 476 | 477 | console.log(parse("a # one\n # two\n()")); 478 | // → {type: "apply", 479 | // operator: {type: "word", name: "a"}, 480 | // args: []} 481 | ``` 482 | 483 | 484 | ### 修复作用域 485 | 486 | 目前绑定赋值的唯一方法是`define`。该语言构造可以同时实现定义绑定和将一个新的值赋予已存在的绑定。 487 | 488 | 这种歧义性引发了一个问题。当你尝试为一个非局部绑定赋予新值时,你最后会定义一个局部绑定并替换掉原来的同名绑定。一些语言的工作方式正和这种设计一样,但是我总是认为这是一种笨拙的作用域处理方式。 489 | 490 | 添加一个类似于`define`的特殊形式`set`,该语句会赋予一个绑定新值,若绑定不存在于内部作用域,则更新其外部作用域相应绑定的值。若绑定没有定义,则抛出`ReferenceError`(另一个标准错误类型)。 491 | 492 | 我们目前采取的技术是使用简单的对象来表示作用域对象,处理目前的任务非常方便,此时我们需要更进一步。你可以使用`Object.getPrototypeOf`函数来获取对象原型。同时也要记住,我们的作用域对象并未继承`Object.prototype`,因此若想调用`hasOwnProperty`,需要使用下面这个略显复杂的表达式。 493 | 494 | ```js 495 | Object.prototype.hasOwnProperty.call(scope, name); 496 | ``` 497 | 498 | ```js 499 | specialForms.set = (args, scope) => { 500 | // Your code here. 501 | }; 502 | 503 | run(` 504 | do(define(x, 4), 505 | define(setx, fun(val, set(x, val))), 506 | setx(50), 507 | print(x)) 508 | `); 509 | // → 50 510 | run(`set(quux, true)`); 511 | // → Some kind of ReferenceError 512 | ``` 513 | -------------------------------------------------------------------------------- /13.md: -------------------------------------------------------------------------------- 1 | ## 十三、浏览器中的 JavaScript 2 | 3 | > 原文:[JavaScript and the Browser](https://eloquentjavascript.net/13_browser.html) 4 | > 5 | > 译者:[飞龙](https://github.com/wizardforcel) 6 | > 7 | > 协议:[CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/) 8 | > 9 | > 自豪地采用[谷歌翻译](https://translate.google.cn/) 10 | > 11 | > 部分参考了[《JavaScript 编程精解(第 2 版)》](https://book.douban.com/subject/26707144/) 12 | 13 | > Web 背后的梦想是公共信息空间,其中我们通过共享信息进行交流。 其普遍性至关重要:超文本链接可指向任何东西,无论是个人的,本地的还是全球的,无论是草稿还是高度润色的。 14 | > 15 | > Douglas Crockford,《JavaScript 编程语言》(视频讲座) 16 | 17 | ![](img/13-0.jpg) 18 | 19 | 本书接下来的章节将会介绍 Web 浏览器。可以说,没有浏览器,就没有 JavaScript。就算有,估计也不会有多少人去关心这门编程语言。 20 | 21 | Web 技术自出现伊始,其演变方式和技术上就是以分散的方式发展的。许多浏览器厂商专门为其开发新的功能,有时这些新功能被大众采纳,有时这些功能被其他功能所代替,最终形成了一套标准。 22 | 23 | 这种发展模式是把双刃剑。一方面,不会有一个集中式的组织来管理技术的演进,取而代之的是一个包含多方利益集团的松散协作架构(偶尔会出现对立)。另一方面,互联网这种无计划的发展方式所开发出来的系统,其内部很难实现一致性。事实上,它的一些部分令人疑惑,并且毫无设计。 24 | 25 | ## 网络和 Internet 26 | 27 | 计算机网络出现在 20 世纪 50 年代。如果在两台或多台计算机之间铺设电缆,那么你可以通过这些电缆互相收发数据,并实现一些神奇的功能。 28 | 29 | 如果通过连接同一个建筑中的两台机器就可以实现一些神奇的功能,那么如果可以连接全世界的机器,就可以完成更伟大的工作了。20 世纪 80 年代,人们开发了相关技术来实现这个愿景,我们将其产生的网络称为 Internet。而 Internet 的表现名副其实。 30 | 31 | 计算机可以使用这种网络向其他计算机发送位数据。为了在传输位数据的基础上,实现计算机之间的有效通信,网络两端的机器必须知道这些位所表达的实际含义。对于给定的位序列,其含义完全取决于位序列描述的信息类型与使用的编码机制。 32 | 33 | 网络协议描述了一种网络通信方式。网络协议非常多,其中包括邮件发送、邮件收取和邮件共享,甚至连病毒软件感染控制计算机都有相应的协议。 34 | 35 | 例如,HTTP(超文本传输协议,Hypertext Transfer Protocol)是用于检索命名资源(信息块,如网页或图片)的协议。 它指定发出请求的一方应该以这样的一行开始,命名资源和它正在尝试使用的协议的版本。 36 | 37 | ``` 38 | GET /index.html HTTP/1.1 39 | ``` 40 | 41 | 有很多规则,关于请求者在请求中包含更多信息的方式,以及另一方返回资源并打包其内容的方式。 我们将在第 18 章中更详细地观察 HTTP。 42 | 43 | 大多数协议都建立在其他协议之上。 HTTP 将网络视为一种流式设备,您可以将位放入这些设备,并使其按正确的顺序到达正确的目的地。 我们在第 11 章]中看到,确保这些事情已经是一个相当困难的问题。 44 | 45 | 46 | TCP(传输控制协议,Transmission Control Protocol)就可以帮助我们解决该问题。所有连接到互联网的设备都会使用到这种协议,而多数互联网通信都构建在这种协议之上。 47 | 48 | TCP 连接的工作方式是一台电脑必须等待或者监听,而另一台电脑则开始与之通信。一台机器为了同时监听不同类型的通信信息,会为每个监听器分配一个与之关联的数字(我们称之为端口)。大多数协议都指定了默认使用的端口。例如,当我们向使用 SMTP 协议发送一封邮件时,我们需要通过一台机器来发送邮件,而发送邮件的机器需要监听端口 25。 49 | 50 | 随后另一台机器连接到使用了正确端口号的目标机器上。如果可以连接到目标机器,而且目标机器在监听对应端口,则说明连接创建成功。负责监听的计算机名为服务器,而连接服务器的计算机名为客户端。 51 | 52 | 我们可以将该连接看成双向管道,位可以在其中流动,也就是说两端的机器都可以向连接中写入数据。当成功传输完这些位数据后,双方都可以读取另一端传来的数据。TCP 是一个非常便利的模型。我们可以说TCP就是一种网络的抽象。 53 | 54 | ## Web 55 | 56 | 万维网(World Wide Web,不要将其与 Internet 混淆)是包含一系列协议和格式的集合,允许我们通过浏览器访问网页。词组中的 Web 指的是这些页面可以轻松地链接其他网页,因此最后可以连接成一张巨大的网,用户可以在网络中浏览。 57 | 58 | 你只需将一台计算机连接到 Internet 并使用 HTTP 监听 80 端口,就可以成为 Web 的一部分。其他计算机可以通过网络,并使用 HTTP 协议获取其他计算机上的文件。 59 | 60 | 网络中的每个文件都能通过 URL(统一资源定位符,Universal Resource Locator)访问,如下所示: 61 | 62 | ``` 63 | http://eloquentjavascript.net/13_browser.html 64 | | | | | 65 | protocol server path 66 | ``` 67 | 该地址的第一部分告诉我们 URL 使用的是 HTTP 协议(加密的 HTTP 连接则使用`https://`来表示)。第二部分指的是获取文件的服务器地址。第三部分是我们想要获取的具体文件(或资源)的路径。 68 | 69 | 连接到互联网的机器获得一个 IP 地址,该地址是一个数字,可用于将消息发送到该机器的,类似于`"149.210.142.219"`或`"2001:4860:4860::8888"`。 但是或多或少的随机数字列表很难记住,而且输入起来很笨拙,所以你可以为一个特定的地址或一组地址注册一个域名。 我注册了`eloquentjavascript.net`,来指向我控制的机器的 IP 地址,因此可以使用该域名来提供网页。 70 | 71 | 如果你在浏览器地址栏中输入上面提到的 URL,浏览器会尝试获取并显示该 URL 对应的文档。首先,你的浏览器需要找出域名`eloquentjavascript.net`指向的地址。然后使用 HTTP 协议,连接到该地址处的服务器,并请求`/13_browser.html`这个资源。如果一切顺利,服务器会发回一个文档,然后您的浏览器将显示在屏幕上。 72 | 73 | ## HTML 74 | 75 | HTML,即超文本标记语言(Hypertext Markup Language),是在网页中得到广泛使用的文档格式。HTML 文档不仅包含文本,还包含了标签,用于说明文本结构,描述了诸如链接、段落、标题之类的元素。 76 | 77 | 一个简短的 HTML 文档如下所示: 78 | 79 | ```html 80 | 81 | 82 | 83 | 84 | My home page 85 | 86 | 87 |

My home page

88 |

Hello, I am Marijn and this is my home page.

89 |

I also wrote a book! Read it 90 | here.

91 | 92 | 93 | ``` 94 | 95 | 标签包裹在尖括号之间(`<`和`>`,小于和大于号),提供关于文档结构的信息。其他文本则是纯文本。 96 | 97 | 文档以``开头,告诉浏览器将页面解释为现代 HTML,以别于过去使用的各种方言。 98 | 99 | HTML 文档有头部(head)和主体(body)。头部包含了文档信息,而主体则包含文档自身。在本例中,头部将文档标题声明为`"My home page"`,并使用 UTF-8 编码,它是将 Unicode 文本编码为二进制的方式。文档的主体包含标题(`

`,表示一级标题,`

`到`

`可以产生不同等级的子标题)和两个段落(`

`)。 100 | 101 | 标签有几种形式。一个元素,比如主体、段落或链接以一个起始标签(比如`

`)开始,并以一个闭合标签(比如`

`)结束。一些起始标签,比如一个链接(``),会包含一些额外信息,其形式是`name="value"`这种键值对,我们称之为属性。在本例中,使用属性`href="http://eloquentjavascript.net"`指定链接的目标,其中`href`表示“超文本链接(Hypertext Reference)”。 102 | 103 | 某些类型的标签不会包含任何元素,这种标签不需要闭合。元数据标签``就是一个例子。 104 | 105 | > 译者注:最好还是这样闭合它们:``。 106 | 107 | 尽管 HTML 中尖括号有特殊含义,但为了在文档的文本中包含这些符号,可以引入另外一种形式的特殊标记方法。普通文本中的起始尖括号写成`<`(less than),而闭合尖括号写成`>`(greater than)。在 HTML 中,我们将一个`&`字符后跟着一个单词和分号(`;`)这种写法称为一个实体,浏览器会使用实体编码对应的字符替换它们。 108 | 109 | 与之类似的是 JavaScript 字符串中反斜杠的使用。由于 HTML 中的实体机制赋予了`&`特殊含义,因此我们需要使用`&`来表示一个`&`字符。在属性的值(包在双引号中)中使用`"`可以插入实际的引号字符。 110 | 111 | HTML 的解析过程容错性非常强。当应有的标签丢失时,浏览器会重新构建这些标签。标签的重新构建已经标准化,你可以认为所有现代浏览器的行为都是一致的。 112 | 113 | 下面的文件与之前版本显示效果相同: 114 | 115 | ```html 116 | 117 | 118 | 119 | My home page 120 | 121 |

My home page

122 |

Hello, I am Marijn and this is my home page. 123 |

I also wrote a book! Read it 124 | here. 125 | ``` 126 | 127 | ``、``和``标签可以完全丢弃。浏览器知道``和``属于头部,而`<h1>`属于主体。此外,我再也不用明确关闭某个段落,因为新段落开始或文档结束时,浏览器会隐式关闭段落标签。目标链接两边的引号也可以丢弃。 128 | 129 | 本书的示例通常都会省略`<html>`、`<head>`和`<body>`标签,以保持源代码简短,避免太过杂乱。但我会明确关闭所有标签并在属性两旁包含引号。 130 | 131 | 本书也会经常忽略`doctype`和`charset`声明。这并不是鼓励大家省略它们。当你忘记它们时,浏览器往往会做出荒谬的事情。 您应该认为`doctype`和`charset`元数据隐式出现在示例中,即使它们没有实际显示在文本中。 132 | 133 | ## HTML 和 JavaScript 134 | 135 | 对于本书来说,最重要的一个 HTML 标签是`<script>`。该标签允许我们在文档中包含一段 JavaScript 代码。 136 | 137 | ```html 138 | <h1>Testing alert</h1> 139 | <script>alert("hello!");</script> 140 | ``` 141 | 142 | 当浏览器在读取 HTML 时,一旦遇到`<script>`标签就会执行该代码。这个页面在打开时会弹出一个对话框 - `alert`函数类似`prompt`,因为它弹出一个小窗口,但只显示一条消息而不请求输入。 143 | 144 | 在 HTML 文档中包含大程序是不切实际的。`<script>`标签可以指定一个`src`属性,从一个 URL 获取脚本文件(包含 JavaScript 程序的文本文件)。 145 | 146 | ```html 147 | <h1>Testing alert</h1> 148 | <script src="code/hello.js"></script> 149 | ``` 150 | 151 | 这里包含的文件`code/hello.js`是和上文中相同的一段程序,`alert("hello")`。当一个页面将其他 URL 引用为自身的一部分时(比如图像文件或脚本),网页浏览器将会立即获取这些资源并将其包含在页面中。 152 | 153 | 即使`script`标签引用了一个文本文件,且并未包含任何代码,你也必须使用`</script>`来闭合标签。如果你忘记了这点,浏览器会将剩余的页面会作为脚本的一部分进行解析。 154 | 155 | 你可以在浏览器中加载ES模块(参见第 10 章),向脚本标签提供`type ="module"`属性。 这些模块可以依赖于其他模块,通过将相对于自己的 URL 用作`import`声明中的模块名称。 156 | 157 | 158 | 某些属性也可以包含 JavaScript 程序。下面展示的`<button>`标签(显示一个按钮)有一个`onclick`属性。该属性的值将在点击按钮时运行。 159 | 160 | ```html 161 | <button onclick="alert('Boom!');">DO NOT PRESS</button> 162 | ``` 163 | 164 | 需要注意的是,我们在`onclick`属性的字符串中使用了单引号,这是因为我们在使用了双引号来引用整个属性。我们也可以使用`"`。 165 | 166 | ## 沙箱 167 | 168 | 直接执行从因特网中下载的程序存在潜在危险。你不了解大多数的网页开发者,他们不一定都心怀善意。一旦运行某些不怀好意的人提供的程序,你的电脑可能会感染病毒,这些程序还会窃取数据会并盗走账号。 169 | 170 | 但网络的吸引力就在于你可以浏览网站,而不必要信任所有网站。这就是为什么浏览器严重限制了 JavaScript 程序的能力—— JavaScript 无法查看电脑中的任何文件,也无法修改与其所在页面无关的数据。 171 | 172 | 我们将这种隔离程序运行环境的技术称为沙箱。以该思想编写的程序在沙箱中运行,不会对计算机造成任何伤害。但是你应该想象,这种特殊的沙箱上面有一个厚钢筋笼子,所以在其中运行的程序实际上不会出去。 173 | 174 | 实现沙箱的难点是:一方面我们要给予程序一定的自由使得程序能有实际用处,但又要限制程序,防止其执行危险的行为。许多实用功能(比如与服务器通信或从剪贴板读取内容)也会存在问题,有些侵入者可以利用这些功能来侵入你的计算机。 175 | 176 | 时不时会有一些人想到新方法,突破浏览器的限制,并对你的机器造成伤害,从窃取少量的私人信息到掌握执行浏览器的整个机器。浏览器开发者的对策是修补漏洞,然后一切都恢复正常。直到下一次问题被发现并广为传播之前,某些政府或秘密组织可以私下利用这些漏洞。 177 | 178 | ## 兼容性与浏览器之争 179 | 180 | 在 Web 技术发展的早期,一款名为 Mosaic 的浏览器统治了整个市场。几年之后,这种平衡被 Netscape 公司打破,随后又被微软的 Internet Explorer 排挤出市场。无论什么时候,当一款浏览器统治了整个市场,浏览器供应商就会觉得他们有权利单方面为网络研发新的特性。由于大多数人都使用相同的浏览器,因此网站会开始使用这些独有特性,也就不再考虑其他浏览器的兼容性问题了。 181 | 182 | 这是兼容性的黑暗时代,我们通常称之为浏览器之争。网络开发者总是为缺乏统一的 Web 标准,而需要去考虑两到三种互不兼容的平台而感到烦恼。让事情变得更糟糕的是 2003 年左右使用的浏览器充满了漏洞,当然不同浏览器的漏洞都不一样。网页编写者的生活颇为艰辛。 183 | 184 | Mozilla Firefox,作为 Netscape 浏览器的非盈利性分支,在20世纪初末期开始挑战 Internet Explorer 的霸主地位。因为当时微软并未特别关心与其竞争,导致 Firefox 迅速占领了很大的市场份额。与此同时,Google 发布了它的 Chrome 浏览器,而 Apple 的 Safari 也得到普及,导致现在成为四个主要选手的竞争,而非一家独大。 185 | 186 | 新的参与者对标准有着更认真的态度,和更好的工程实践,为我们减少了不兼容性和错误。 微软看到其市场份额极速下降,在其 Edge 浏览器中采取了这些态度,取代了 Internet Explorer。 如果您今天开始学习网络开发,请认为自己是幸运的。 主流浏览器的最新版本行为非常一致,并且错误相对较少。 187 | 188 | 这并不是说就没有问题了。某些使用网络的人,出于惰性或公司政策,被迫使用旧版本的浏览器。直到这些浏览器完全退出市场之前,为旧版本浏览器编写网站仍需要掌握很多不常见的特性,了解旧浏览器的缺陷和特殊之处。本书不会讨论这些特殊的特性,而着眼于介绍现代且健全的网络程序设计风格。 189 | -------------------------------------------------------------------------------- /2.md: -------------------------------------------------------------------------------- 1 | ## 二、程序结构 2 | 3 | > 原文:[Program Structure](http://eloquentjavascript.net/02_program_structure.html) 4 | > 5 | > 译者:[飞龙](https://github.com/wizardforcel) 6 | > 7 | > 协议:[CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/) 8 | > 9 | > 自豪地采用[谷歌翻译](https://translate.google.cn/) 10 | > 11 | > 部分参考了[《JavaScript 编程精解(第 2 版)》](https://book.douban.com/subject/26707144/) 12 | 13 | > And my heart glows bright red under my filmy, translucent skin and they have to administer 10cc of JavaScript to get me to come back. (I respond well to toxins in the blood.) Man, that stuff will kick the peaches right out your gills! 14 | > 15 | > why,《Why's (Poignant) Guide to Ruby》 16 | 17 | ![](img/2-0.jpg) 18 | 19 | 在本章中,我们开始做一些实际上称为编程的事情。 我们将扩展我们对 JavaScript 语言的掌控,超出我们目前所看到的名词和句子片断,直到我们可以表达有意义的散文。 20 | 21 | ## 表达式和语句 22 | 23 | 在第 1 章中,我们为它们创建了值,并应用了运算符来获得新的值。 像这样创建值是任何 JavaScript 程序的主要内容。 但是,这种东西必须在更大的结构中构建,才能发挥作用。 这就是我们接下来要做的。 24 | 25 | 我们把产生值的操作的代码片段称为表达式。按照字面含义编写的值(比如`22`或`"psychoanalysis"`)都是一个表达式。而括号当中的表达式、使用二元运算符连接的表达式或使用一元运算符的表达式,仍然都是表达式。 26 | 27 | 这展示了一部分基于语言的接口之美。 表达式可以包含其他表达式,其方式非常类似于人类语言的从句嵌套 - 从句可以包含它自己的从句,依此类推。 这允许我们构建描述任意复杂计算的表达式。 28 | 29 | 如果一个表达式对应一个句子片段,则 JavaScript 语句对应于一个完整的句子。 一个程序是一列语句。 30 | 31 | 最简单的一条语句由一个表达式和其后的分号组成。比如这就是一个程序: 32 | 33 | ```js 34 | 1; 35 | !false; 36 | ``` 37 | 38 | 不过,这是一个无用的程序。 表达式可以仅仅满足于产生一个值,然后可以由闭合的代码使用。 一个声明是独立存在的,所以它只有在影响到世界的时候才会成立。 它可以在屏幕上显示某些东西 - 这可以改变世界 - 或者它可以改变机器的内部状态,从而影响后面的语句。 这些变化被称为副作用。 前面例子中的语句仅仅产生值`1`和`true`,然后立即将它们扔掉。 这给世界没有留下什么印象。 当你运行这个程序时,什么都不会发生。 39 | 40 | 在某些情况下,JavaScript 允许您在语句结尾处省略分号。 在其他情况下,它必须在那里,否则下一行将被视为同一语句的一部分。 何时可以安全省略它的规则有点复杂且容易出错。 所以在本书中,每一个需要分号的语句都会有分号。 至少在你更了解省略分号的细节之前,我建议你也这样做。 41 | 42 | ## 绑定 43 | 44 | 程序如何保持内部状态? 它如何记住东西? 我们已经看到如何从旧值中产生新值,但这并没有改变旧值,新值必须立即使用,否则将会再度消失。 为了捕获和保存值,JavaScript 提供了一种称为绑定或变量的东西: 45 | 46 | ```js 47 | let caught = 5 * 5; 48 | ``` 49 | 50 | 这是第二种语句。 关键字(keyword)`let`表示这个句子打算定义一个绑定。 它后面跟着绑定的名称,如果我们想立即给它一个值,使用`=`运算符和一个表达式。 51 | 52 | 前面的语句创建一个名为`caught`的绑定,并用它来捕获乘以`5 * 5`所产生的数字。 53 | 54 | 在定义绑定之后,它的名称可以用作表达式。 这种表达式的值是绑定当前所持有的值。 这是一个例子: 55 | 56 | ```js 57 | let ten = 10; 58 | console.log(ten * ten); 59 | // → 100 60 | ``` 61 | 62 | 当绑定指向某个值时,并不意味着它永远与该值绑定。 可以在现有的绑定上随时使用`=`运算符,将它们与当前值断开连接,并让它们指向一个新值: 63 | 64 | ```js 65 | var mood = "light"; 66 | console.log(mood); 67 | // → light 68 | mood = "dark"; 69 | console.log(mood); 70 | // → dark 71 | ``` 72 | 73 | 你应该将绑定想象为触手,而不是盒子。 他们不包含值; 他们捕获值 - 两个绑定可以引用相同的值。 程序只能访问它还在引用的值。 当你需要记住某些东西时,你需要长出一个触手来捕获它,或者你重新贴上你现有的触手之一。 74 | 75 | 我们来看另一个例子。 为了记住 Luigi 欠你的美元数量,你需要创建一个绑定。 然后当他还你 35 美元时,你赋予这个绑定一个新值: 76 | 77 | ```js 78 | let luigisDebt = 140; 79 | luigisDebt = luigisDebt - 35; 80 | console.log(luigisDebt); 81 | // → 105 82 | ``` 83 | 84 | 当你定义一个绑定而没有给它一个值时,触手没有任何东西可以捕获,所以它只能捕获空气。 如果你请求一个空绑定的值,你会得到`undefined`值。 85 | 86 | 一个`let`语句可以同时定义多个绑定,定义必需用逗号分隔。 87 | 88 | ```js 89 | let one = 1, two = 2; 90 | console.log(one + two); 91 | // → 3 92 | ``` 93 | 94 | `var`和`const`这两个词也可以用来创建绑定,类似于`let`。 95 | 96 | ```js 97 | var name = "Ayda"; 98 | const greeting = "Hello "; 99 | console.log(greeting + name); 100 | // → Hello Ayda 101 | ``` 102 | 103 | 第一个`var`(“variable”的简写)是 JavaScript 2015 之前声明绑定的方式。 我们在下一章中,会讲到它与`let`的确切的不同之处。 现在,请记住它大部分都做同样的事情,但我们很少在本书中使用它,因为它有一些令人困惑的特性。 104 | 105 | `const`这个词代表常量。 它定义了一个不变的绑定,只要它存在,它就指向相同的值。 这对于一些绑定很有用,它们向值提供一个名词,以便之后可以很容易地引用它。 106 | 107 | ## 绑定名称 108 | 109 | 绑定名称可以是任何单词。 数字可以是绑定名称的一部分,例如`catch22`是一个有效的名称,但名称不能以数字开头。 绑定名称可能包含美元符号(`$`)或下划线(`_`),但不包含其他标点符号或特殊字符。 110 | 111 | 具有特殊含义的词,如`let`,是关键字,它们不能用作绑定名称。 在未来的 JavaScript 版本中还有一些“保留供使用”的单词,它们也不能用作绑定名称。 关键字和保留字的完整列表相当长: 112 | 113 | ``` 114 | break case catch class const continue debugger default 115 | delete do else enum export extends false finally for 116 | function if implements import interface in instanceof let 117 | new package private protected public return static super 118 | switch this throw true try typeof var void while with yield 119 | ``` 120 | 121 | 不要担心记住这些东西。 创建绑定时会产生意外的语法错误,请查看您是否尝试定义保留字。 122 | 123 | ## 环境 124 | 125 | 给定时间中存在的绑定及其值的集合称为环境。 当一个程序启动时,这个环境不是空的。 它总是包含作为语言标准一部分的绑定,并且在大多数情况下,它还具有一些绑定,提供与周围系统交互的方式。 例如,在浏览器中,有一些功函数能可以与当前加载的网站交互并读取鼠标和键盘输入。 126 | 127 | ## 函数 128 | 129 | 在默认环境中提供的许多值的类型为函数。 函数是包裹在值中的程序片段。 为了运行包裹的程序,可以将这些值应用于它们。 例如,在浏览器环境中,绑定`prompt`包含一函数,个显示一个小对话框,请求用户输入。 它是这样使用的: 130 | 131 | ```js 132 | prompt("Enter passcode"); 133 | ``` 134 | 135 | ![](img/2-1.png) 136 | 137 | 执行一个函数被称为调用,或应用它(invoke,call,apply)。您可以通过在生成函数值的表达式之后放置括号来调用函数。 通常你会直接使用持有该函数的绑定名称。 括号之间的值被赋予函数内部的程序。 在这个例子中,`prompt`函数使用我们提供的字符串作为文本来显示在对话框中。 赋予函数的值称为参数。 不同的函数可能需要不同的数量或不同类型的参数。 138 | 139 | `prompt`函数在现代 Web 编程中用处不大,主要是因为你无法控制所得对话框的外观,但可以在玩具程序和实验中有所帮助。 140 | 141 | ## `console.log`函数 142 | 143 | 在例子中,我使用`console.log`来输出值。 大多数 JavaScript 系统(包括所有现代 Web 浏览器和 Node.js)都提供了`console.log`函数,将其参数写入一个文本输出设备。 在浏览器中,输出出现在 JavaScript 控制台中。 浏览器界面的这一部分在默认情况下是隐藏的,但大多数浏览器在您按 F12 或在 Mac 上按 Command-Option-I 时打开它。 如果这不起作用,请在菜单中搜索名为“开发人员工具”或类似的项目。 144 | 145 | > 在英文版页面上运行示例(或自己的代码)时,会在示例之后显示`console.log`输出,而不是在浏览器的 JavaScript 控制台中显示。 146 | 147 | ```js 148 | let x = 30; 149 | console.log("the value of x is", x); 150 | // → the value of x is 30 151 | ``` 152 | 153 | 尽管绑定名称不能包含句号字符,但是`console.log`确实拥有。 这是因为`console.log`不是一个简单的绑定。 它实际上是一个表达式,它从`console`绑定所持有的值中检索`log`属性。 我们将在第 4 章中弄清楚这意味着什么。 154 | 155 | ## 返回值 156 | 157 | 显示对话框或将文字写入屏幕是一个副作用。 由于它们产生的副作用,很多函数都很有用。 函数也可能产生值,在这种情况下,他们不需要有副作用就有用。 例如,函数`Math.max`可以接受任意数量的参数并返回最大值。 158 | 159 | ```js 160 | console.log(Math.max(2, 4)); 161 | // → 4 162 | ``` 163 | 164 | 当一个函数产生一个值时,它被称为返回该值。 任何产生值的东西都是 JavaScript 中的表达式,这意味着可以在较大的表达式中使用函数调用。 在这里,`Math.min`的调用(与`Math.max`相反)用作加法表达式的一部分: 165 | 166 | ```js 167 | console.log(Math.min(2, 4) + 100); 168 | // → 102 169 | ``` 170 | 171 | 我们会在下一章当中讲解如何编写自定义函数。 172 | 173 | ## 控制流 174 | 175 | 当你的程序包含多个语句时,这些语句就像是一个故事一样从上到下执行。 这个示例程序有两个语句。 第一个要求用户输入一个数字,第二个在第一个之后执行,显示该数字的平方。 176 | 177 | ```js 178 | let theNumber = Number(prompt("Pick a number")); 179 | console.log("Your number is the square root of " + 180 | theNumber * theNumber); 181 | ``` 182 | 183 | 184 | `Number`函数将一个值转换为一个数字。 我们需要这种转换,因为`prompt`的结果是一个字符串值,我们需要一个数字。 有类似的函数叫做`String`和`Boolean`,它们将值转换为这些类型。 185 | 186 | 以下是直线控制流程的相当简单的示意图: 187 | 188 | ![](img/2-2.svg) 189 | 190 | ## 条件执行 191 | 192 | 并非所有的程序都是直路。 例如,我们可能想创建一条分叉路,在那里该程序根据当前的情况采取适当的分支。 这被称为条件执行。 193 | 194 | ![](img/2-3.svg) 195 | 196 | 在 JavaScript 中,条件执行使用`if`关键字创建。 在简单的情况下,当且仅当某些条件成立时,我们才希望执行一些代码。 例如,仅当输入实际上是一个数字时,我们可能打算显示输入的平方。 197 | 198 | ```js 199 | let theNumber = Number(prompt("Pick a number", "")); 200 | if (!isNaN(theNumber)) 201 | alert("Your number is the square root of " + 202 | theNumber * theNumber); 203 | ``` 204 | 205 | 206 | 修改之后,如果您输入`"parrot"`,则不显示输出。 207 | 208 | `if`关键字根据布尔表达式的值执行或跳过语句。 决定性的表达式写在关键字之后,括号之间,然后是要执行的语句。 209 | 210 | `Number.isNaN`函数是一个标准的 JavaScript 函数,仅当它给出的参数是`NaN`时才返回`true`。 当你给它一个不代表有效数字的字符串时,`Number`函数恰好返回`NaN`。 因此,条件翻译为“如果`theNumber`是一个数字,那么这样做”。 211 | 212 | 在这个例子中,`if`下面的语句被大括号(`{`和`}`)括起来。 它们可用于将任意数量的语句分组到单个语句中,称为代码块。 在这种情况下,你也可以忽略它们,因为它们只包含一个语句,但为了避免必须考虑是否需要,大多数 JavaScript 程 序员在每个这样的被包裹的语句中使用它们。 除了偶尔的一行,我们在本书中大多会遵循这个约定。 213 | 214 | ```js 215 | if (1 + 1 == 2) console.log("It's true"); 216 | // → It's true 217 | ``` 218 | 219 | 您通常不会只执行条件成立时代码,还会处理其他情况的代码。 该替代路径由图中的第二个箭头表示。 可以一起使用`if`和`else`关键字,创建两个单独的替代执行路径。 220 | 221 | ```js 222 | let theNumber = Number(prompt("Pick a number")); 223 | if (!Number.isNaN(theNumber)) { 224 | console.log("Your number is the square root of " + 225 | theNumber * theNumber); 226 | } else { 227 | console.log("Hey. Why didn't you give me a number?"); 228 | } 229 | ``` 230 | 231 | 如果我们需要执行的路径多于两条,可以将多个`if/else`对链接在一起使用。如下所示例子: 232 | 233 | ```js 234 | let num = Number(prompt("Pick a number", "0")); 235 | 236 | if (num < 10) { 237 | console.log("Small"); 238 | } else if (num < 100) { 239 | console.log("Medium"); 240 | } else { 241 | console.log("Large"); 242 | } 243 | ``` 244 | 245 | 该程序首先会检查`num`是否小于 10。如果条件成立,则执行显示`"Small"`的这条路径;如果不成立,则选择`else`分支,`else`分支自身包含了第二个`if`。如果第二个条件即`num`小于 100 成立,且数字的范围在 10 到 100 之间,则执行显示`"Medium"`的这条路径。如果上述条件均不满足,则执行最后一条`else`分支路径。 246 | 247 | 这个程序的模式看起来像这样: 248 | 249 | ![](img/2-4.svg) 250 | 251 | ## `while`和`do`循环 252 | 253 | 现考虑编写一个程序,输出 0 到 12 之间的所有偶数。其中一种编写方式如下所示: 254 | 255 | ```js 256 | console.log(0); 257 | console.log(2); 258 | console.log(4); 259 | console.log(6); 260 | console.log(8); 261 | console.log(10); 262 | console.log(12); 263 | ``` 264 | 265 | 该程序确实可以工作,但编程的目的在于减少工作量,而非增加。如果我们需要小于 1000 的偶数,上面的方式是不可行的。我们现在所需的是重复执行某些代码的方法,我们将这种控制流程称为循环。 266 | 267 | ![](img/2-5.svg) 268 | 269 | 我们可以使用循环控制流来让程序执行回到之前的某个位置,并根据程序状态循环执行代码。如果我们在循环中使用一个绑定计数,那么就可以按照如下方式编写代码: 270 | 271 | ```js 272 | let number = 0; 273 | while (number <= 12) { 274 | console.log(number); 275 | number = number + 2; 276 | } 277 | // → 0 278 | // → 2 279 | // … etcetera 280 | ``` 281 | 282 | 循环语句以关键字`while`开头。在关键字`while`后紧跟一个用括号括起来的表达式,括号后紧跟一条语句,这种形式与`if`语句类似。只要表达式产生的值转换为布尔值后为`true`,该循环会持续进入括号后面的语句。 283 | 284 | 285 | `number`绑定演示了绑定可以跟踪程序进度的方式。 每次循环重复时,`number`的值都比以前的值多 2。 在每次重复开始时,将其与数字 12 进行比较来决定程序的工作是否完成。 286 | 287 | 作为一个实际上有用的例子,现在我们可以编写一个程序来计算并显示`2**10`(2 的 10 次方)的结果。 我们使用两个绑定:一个用于跟踪我们的结果,一个用来计算我们将这个结果乘以 2 的次数。 该循环测试第二个绑定是否已达到 10,如果不是,则更新这两个绑定。 288 | 289 | ```js 290 | let result = 1; 291 | let counter = 0; 292 | while (counter < 10) { 293 | result = result * 2; 294 | counter = counter + 1; 295 | } 296 | console.log(result); 297 | // → 1024 298 | ``` 299 | 300 | 计数器也可以从`1`开始并检查`<= 10`,但是,由于一些在第 4 章中澄清的原因,从 0 开始计数是个好主意。 301 | 302 | `do`循环控制结构类似于`while`循环。两者之间只有一个区别:`do`循环至少执行一遍循环体,只有第一次执行完循环体之后才会开始检测循环条件。`do`循环中将条件检测放在循环体后面,正反映了这一点: 303 | 304 | ```js 305 | let yourName; 306 | do { 307 | yourName = prompt("Who are you?"); 308 | } while (!yourName); 309 | console.log(yourName); 310 | ``` 311 | 312 | 这个程序会强制你输入一个名字。 它会一再询问,直到它得到的东西不是空字符串。 `!`运算符会将值转换为布尔类型再取反,除了`""`之外的所有字符串都转换为`true`。 这意味着循环持续进行,直到您提供了非空名称。 313 | 314 | ## 代码缩进 315 | 316 | 在这些例子中,我一直在语句前添加空格,它们是一些大型语句的一部分。 这些都不是必需的 - 没有它们,计算机也会接受该程序。 实际上,即使是程序中的换行符也是可选的。 如果你喜欢,你可以将程序编写为很长的一行。 317 | 318 | 块内缩进的作用是使代码结构显而易见。 在其他块内开启新的代码块中,可能很难看到块的结束位置,和另一个块开始位置。 通过适当的缩进,程序的视觉形状对应其内部块的形状。 我喜欢为每个开启的块使用两个空格,但风格不同 - 有些人使用四个空格,而有些人使用制表符。 重要的是,每个新块添加相同的空格量。 319 | 320 | ```js 321 | if (false != true) { 322 | console.log("That makes sense."); 323 | if (1 < 2) { 324 | console.log("No surprise there."); 325 | } 326 | } 327 | ``` 328 | 329 | 大多数代码编辑器程序(包括本书中的那个)将通过自动缩进新行来提供帮助。 330 | 331 | ## `for`循环 332 | 333 | 许多循环遵循`while`示例中看到的规律。 首先,创建一个计数器绑定来跟踪循环的进度。 然后出现一个`while`循环,通常用一个测试表达式来检查计数器是否已达到其最终值。 在循环体的末尾,更新计数器来跟踪进度。 334 | 335 | 由于这种规律非常常见,JavaScript 和类似的语言提供了一个稍短而且更全面的形式,`for`循环: 336 | 337 | ```js 338 | for (let number = 0; number <= 12; number = number + 2) 339 | console.log(number); 340 | // → 0 341 | // → 2 342 | // … etcetera 343 | ``` 344 | 345 | 该程序与之前的偶数打印示例完全等价。 唯一的变化是,所有与循环“状态”相关的语句,在`for`之后被组合在一起。 346 | 347 | 关键字`for`后面的括号中必须包含两个分号。第一个分号前面的是循环的初始化部分,通常是定义一个绑定。第二部分则是判断循环是否继续进行的检查表达式。最后一部分则是用于每个循环迭代后更新状态的语句。绝大多数情况下,`for`循环比`while`语句更简短清晰。 348 | 349 | 下面的代码中使用了`for`循环代替`while`循环,来计算`2**10`: 350 | 351 | ```js 352 | var result = 1; 353 | for (var counter = 0; counter < 10; counter = counter + 1) 354 | result = result * 2; 355 | console.log(result); 356 | // → 1024 357 | ``` 358 | 359 | ## 跳出循环 360 | 361 | 除了循环条件为`false`时循环会结束以外,我们还可以使用一个特殊的`break`语句来立即跳出循环。 362 | 363 | 下面的程序展示了`break`语句的用法。该程序的作用是找出第一个大于等于 20 且能被 7 整除的数字。 364 | 365 | ```js 366 | for (let current = 20; ; current++) { 367 | if (current % 7 == 0) 368 | break; 369 | } 370 | } 371 | // → 21 372 | ``` 373 | 374 | 我们可以使用余数运算符(`%`)来判断一个数是否能被另一个数整除。如果可以整除,则余数为 0。 375 | 376 | 本例中的`for`语句省略了检查循环终止条件的表达式。这意味着除非执行了内部的`break`语句,否则循环永远不会结束。 377 | 378 | 如果你要删除这个`break`语句,或者你不小心写了一个总是产生`true`的结束条件,你的程序就会陷入死循环中。 死循环中的程序永远不会完成运行,这通常是一件坏事。 379 | 380 | > 如果您在(英文版)这些页面的其中一个示例中创建了死限循环,则通常会询问您是否要在几秒钟后停止该脚本。 如果失败了,您将不得不关闭您正在处理的选项卡,或者在某些浏览器中关闭整个浏览器,以便恢复。 381 | 382 | `continue`关键字与`break`类似,也会对循环执行过程产生影响。循环体中的`continue`语句可以跳出循环体,并进入下一轮循环迭代。 383 | 384 | ## 更新绑定的简便方法 385 | 386 | 程序经常需要根据绑定的原值进行计算并更新值,特别是在循环过程中,这种情况更加常见。 387 | 388 | ```js 389 | counter = counter + 1; 390 | ``` 391 | 392 | JavaScript 提供了一种简便写法: 393 | 394 | ```js 395 | counter += 1; 396 | ``` 397 | 398 | JavaScript 还为其他运算符提供了类似的简便方法,比如`result*=2`可以将`result`变为原来的两倍,而`counter-=1`可以将`counter`减 1。 399 | 400 | 这样可以稍微简化我们的计数示例代码。 401 | 402 | ```js 403 | for (let number = 0; number <= 12; number += 2) 404 | console.log(number); 405 | ``` 406 | 407 | 对于`counter+=1`和`counter-=1`,还可以进一步简化代码,`counter+=1`可以修改为`counter++`,`counter-=1`可以修改为`counter--`。 408 | 409 | ## `switch`条件分支 410 | 411 | 我们很少会编写如下所示的代码。 412 | 413 | ```js 414 | if (x == "value1") action1(); 415 | else if (x == "value2") action2(); 416 | else if (x == "value3") action3(); 417 | else defaultAction(); 418 | ``` 419 | 420 | 有一种名为`switch`的结构,为了以更直接的方式表达这种“分发”。 不幸的是,JavaScript 为此所使用的语法(它从 C/Java 语言中继承而来)有些笨拙 - `if`语句链看起来可能更好。 这里是一个例子: 421 | 422 | ```js 423 | switch (prompt("What is the weather like?")) { 424 | case "rainy": 425 | console.log("Remember to bring an umbrella."); 426 | break; 427 | case "sunny": 428 | console.log("Dress lightly."); 429 | case "cloudy": 430 | console.log("Go outside."); 431 | break; 432 | default: 433 | console.log("Unknown weather type!"); 434 | break; 435 | } 436 | ``` 437 | 438 | 你可以在`switch`打开的块内放置任意数量的`case`标签。 程序会在向`switch`提供的值的对应标签处开始执行,或者如果没有找到匹配值,则在`default`处开始。 甚至跨越了其他标签,它也会继续执行,直到达到了`break`声明。 在某些情况下,例如在示例中的`"sunny"`的情况下,这可以用来在不同情况下共享一些代码(它建议在晴天和多云天气外出)。 但要小心 - 很容易忘记这样的`break`,这会导致程序执行你不想执行的代码。 439 | 440 | ## 大写 441 | 442 | 绑定名中不能包含空格,但很多时候使用多个单词有助于清晰表达绑定的实际用途。当绑定名中包含多个单词时可以选择多种写法,以下是可以选择的几种绑定名书写方式: 443 | 444 | ```js 445 | fuzzylittleturtle 446 | fuzzy_little_turtle 447 | FuzzyLittleTurtle 448 | fuzzyLittleTurtle 449 | ``` 450 | 451 | 第一种风格可能很难阅读。 我更喜欢下划线的外观,尽管这种风格有点痛苦。 标准的 JavaScript 函数和大多数 JavaScript 程序员都遵循最底下的风格 - 除了第一个词以外,它们都会将每个词的首字母大写。 要习惯这样的小事并不困难,而且混合命名风格的代码可能会让人反感,所以我们遵循这个约定。 452 | 453 | 在极少数情况下,绑定名首字母也会大写,比如`Number`函数。这种方式用来表示该函数是构造函数。我们会在第6章详细讲解构造函数的概念。现在,我们没有必要纠结于表面上的风格不一致性。 454 | 455 | ## 注释 456 | 457 | 通常,原始代码并不能传达你让一个程序传达给读者的所有信息,或者它以神秘的方式传达信息,人们可能不了解它。 在其他时候,你可能只想包含一些相关的想法,作为你程序的一部分。 这是注释的用途。 458 | 459 | 注释是程序中的一段文本,而在程序执行时计算机会完全忽略掉这些文本。JavaScript 中编写注释有两种方法,写单行注释时,使用两个斜杠字符开头,并在后面添加文本注释。 460 | 461 | ```js 462 | let accountBalance = calculateBalance(account); 463 | // It's a green hollow where a river sings 464 | accountBalance.adjust(); 465 | // Madly catching white tatters in the grass. 466 | let report = new Report(); 467 | // Where the sun on the proud mountain rings: 468 | addToReport(accountBalance, report); 469 | // It's a little valley, foaming like light in a glass. 470 | ``` 471 | 472 | `//`注释只能到达行尾。 `/*`和`*/`之间的一段文本将被忽略,不管它是否包含换行符。 这对添加文件或程序块的信息块很有用。 473 | 474 | ```js 475 | /* 476 | I first found this number scrawled on the back of one of 477 | an old notebook. Since then, it has often dropped by, 478 | showing up in phone numbers and the serial numbers of 479 | products that I've bought. It obviously likes me, so I've 480 | decided to keep it. 481 | */ 482 | const myNumber = 11213; 483 | ``` 484 | 485 | ## 本章小结 486 | 487 | 在本章中,我们学习并了解了程序由语句组成,而每条语句又有可能包含了更多语句。在语句中往往包含了表达式,而表达式还可以由更小的表达式组成。 488 | 489 | 程序中的语句按顺序编写,并从上到下执行。你可以使用条件语句(`if`、`else`和`switch`)或循环语句(`while`、`do`和`for`)来改变程序的控制流。 490 | 491 | 绑定可以用来保存任何数据,并用一个绑定名对其引用。而且在记录你的程序执行状态时十分有用。环境是一组定义好的绑定集合。JavaScript 的运行环境中总会包含一系列有用的标准绑定。 492 | 493 | 函数是一种特殊的值,用于封装一段程序。你可以通过`functionName(arg1, arg2)`这种写法来调用函数。函数调用可以是一个表达式,也可以用于生成一个值。 494 | 495 | ## 习题 496 | 497 | 如果你不清楚在哪里可以找到习题的提示,请参考本书的简介部分。 498 | 499 | 每个练习都以问题描述开始。 阅读并尝试解决这个练习。 如果遇到问题,请考虑阅读练习后的提示。 本书不包含练习的完整解决方案,但您可以在 [eloquentjavascript.net/code](https://eloquentjavascript.net/code#2) 上在线查找它们。 如果你想从练习中学到一些东西,我建议仅在你解决了这个练习之后,或者至少在你努力了很长时间而感到头疼之后,再看看这些解决方案。 500 | 501 | ### LoopingaTriangle 502 | 503 | 编写一个循环,调用 7 次`console.log`函数,打印出如下的三角形: 504 | 505 | ``` 506 | # 507 | ## 508 | ### 509 | #### 510 | ##### 511 | ###### 512 | ####### 513 | ``` 514 | 515 | 这里给出一个小技巧,在字符串后加上`.length`可以获取字符串的长度。 516 | 517 | ```js 518 | let abc = "abc"; 519 | console.log(abc.length); 520 | // → 3 521 | ``` 522 | 523 | ### FizzBuzz 524 | 525 | 编写一个程序,使用`console.log`打印出从 1 到 100 的所有数字。不过有两种例外情况:当数字能被 3 整除时,不打印数字,而打印`"Fizz"`。当数字能被 5 整除时(但不能被 3 整除),不打印数字,而打印`"Buzz"`。 526 | 527 | 当以上程序可以正确运行后,请修改你的程序,让程序在遇到能同时被 3 与 5 整除的数字时,打印出`"FizzBuzz"`。 528 | 529 | (这实际上是一个面试问题,据说剔除了很大一部分程序员候选人,所以如果你解决了这个问题,你的劳动力市场价值就会上升。) 530 | 531 | ### 棋盘 532 | 533 | 编写一个程序,创建一个字符串,用于表示`8×8`的网格,并使用换行符分隔行。网格中的每个位置可以是空格或字符`"#"`。这些字符组成了一张棋盘。 534 | 535 | 将字符串传递给`console.log`将会输出以下结果: 536 | 537 | ``` 538 | # # # # 539 | # # # # 540 | # # # # 541 | # # # # 542 | # # # # 543 | # # # # 544 | # # # # 545 | # # # # 546 | ``` 547 | 548 | 当程序可以产生这样的输出后,请定义绑定`size=8`,并修改程序,使程序可以处理任意尺寸(长宽由`size`确定)的棋盘,并输出给定宽度和高度的网格。 549 | -------------------------------------------------------------------------------- /5.md: -------------------------------------------------------------------------------- 1 | ## 五、高阶函数 2 | 3 | > 原文:[Higher-Order Functions](http://eloquentjavascript.net/05_higher_order.html) 4 | > 5 | > 译者:[飞龙](https://github.com/wizardforcel) 6 | > 7 | > 协议:[CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/) 8 | > 9 | > 自豪地采用[谷歌翻译](https://translate.google.cn/) 10 | > 11 | > 部分参考了[《JavaScript 编程精解(第 2 版)》](https://book.douban.com/subject/26707144/) 12 | 13 | > Tzu-li and Tzu-ssu were boasting about the size of their latest programs. ‘Two-hundred thousand lines,’ said Tzu-li, ‘not counting comments!’ Tzu-ssu responded, ‘Pssh, mine is almost a million lines already.’ Master Yuan-Ma said, ‘My best program has five hundred lines.’ Hearing this, Tzu-li and Tzu-ssu were enlightened. 14 | > 15 | > Master Yuan-Ma,《The Book of Programming》 16 | > 17 | > There are two ways of constructing a software design: One way is to make it so simple that there are obviously no deficiencies, and the other way is to make it so complicated that there are no obvious deficiencies. 18 | > 19 | > C.A.R. Hoare,1980 ACM Turing Award Lecture 20 | 21 | ![](img/5-0.jpg) 22 | 23 | 开发大型程序通常需要耗费大量财力和物力,这绝不仅仅是因为构建程序所花费时间的问题。大型程序的复杂程度总是很高,而这些复杂性也会给开发人员带来不少困扰,而程序错误或 bug 往往就是这些时候引入的。大型程序为这些 bug 提供了良好的藏身之所,因此我们更加难以在大型程序中找到它们。 24 | 25 | 让我们简单回顾一下前言当中的两个示例。其中第一个程序包含了 6 行代码并可以直接运行。 26 | 27 | ```js 28 | let total = 0, count = 1; 29 | while (count <= 10) { 30 | total += count; 31 | count += 1; 32 | } 33 | console.log(total); 34 | ``` 35 | 36 | 第二个程序则依赖于外部函数才能执行,且只有一行代码。 37 | 38 | ```js 39 | console.log(sum(range(1, 10))); 40 | ``` 41 | 42 | 哪一个程序更有可能含有 bug 呢? 43 | 44 | 如果算上`sum`和`range`两个函数的代码量,显然第二个程序的代码量更大。不过,我仍然觉得第二个程序包含 bug 的可能性比第一个程序低。 45 | 46 | 之所以这么说的原因是,第二个程序编写的代码很好地表达了我们期望解决的问题。对于计算一组数字之和这个操作来说,我们关注的是计算范围和求和运算,而不是循环和计数。 47 | 48 | `sum`和`range`这两个函数定义的操作当然会包含循环、计数和其他一些操作。但相比于将这些代码直接写到一起,这种表述方式更为简单,同时也易于避免错误。 49 | 50 | ## 抽象 51 | 52 | 在程序设计中,我们把这种编写代码的方式称为抽象。抽象可以隐藏底层的实现细节,从更高(或更加抽象)的层次看待我们要解决的问题。 53 | 54 | 举个例子,比较一下这两份豌豆汤的食谱: 55 | 56 | 按照每人一杯的量将脱水豌豆放入容器中。倒水直至浸没豌豆,然后至少将豌豆浸泡 12 个小时。将豌豆从水中取出沥干,倒入煮锅中,按照每人四杯水的量倒入水。将食材盖满整个锅底,并慢煮 2 个小时。按照每人半个的量加入洋葱,用刀切片,然后放入豌豆中。按照每人一根的量加入芹菜,用刀切片,然后放入豌豆当中。按照每人一根的量放入胡萝卜,用刀切片,然后放入豌豆中。最后一起煮 10 分钟以上即可。 57 | 58 | 第二份食谱: 59 | 60 | 一个人的量:一杯脱水豌豆、半个切好的洋葱、一根芹菜和一根胡萝卜。 61 | 62 | 将豌豆浸泡 12 个小时。按照每人四杯水的量倒入水,然后用文火煨 2 个小时。加入切片的蔬菜,煮 10 分钟以上即可。 63 | 64 | 相比第一份食谱,第二份食谱更简短且更易于理解。但你需要了解一些有关烹调的术语:浸泡、煨、切片,还有蔬菜。 65 | 66 | 在编程的时候,我们不能期望所有功能都是现成的。因此,你可能就会像第一份食谱那样编写你的程序,逐个编写计算机需要执行的代码和步骤,而忽略了这些步骤之上的抽象概念。 67 | 68 | 在编程时,注意你的抽象级别什么时候过低,是一项非常有用的技能。 69 | 70 | ## 重复的抽象 71 | 72 | 我们已经了解的普通函数就是一种很好的构建抽象的工具。但有些时候,光有函数也不一定能够解决我们的问题。 73 | 74 | 程序以给定次数执行某些操作很常见。 你可以为此写一个`for`循环,就像这样: 75 | 76 | ```js 77 | for (let i = 0; i < 10; i++) { 78 | console.log(i); 79 | } 80 | ``` 81 | 82 | 我们是否能够将“做某件事`N`次”抽象为函数? 编写一个调用`console.log` `N`次的函数是很容易的。 83 | 84 | ```js 85 | function repeatLog(n) { 86 | for (let i = 0; i < n; i++) { 87 | console.log(i); 88 | } 89 | } 90 | ``` 91 | 92 | 但如果我们想执行打印数字以外的操作该怎么办呢?我们可以使用函数来定义我们想做的事,而函数也是值,因此我们可以将期望执行的操作封装成函数,然后传递进来。 93 | 94 | ```js 95 | function repeat(n, action) { 96 | for (let i = 0; i < n; i++) { 97 | action(i); 98 | } 99 | } 100 | 101 | repeat(3, console.log); 102 | // → 0 103 | // → 1 104 | // → 2 105 | ``` 106 | 107 | 你不必将预定义的函数传递给`repeat`。 通常情况下,你希望原地创建一个函数值。 108 | 109 | ```js 110 | let labels = []; 111 | repeat(5, i => { 112 | labels.push(`Unit ${i + 1}`); 113 | }); 114 | console.log(labels); 115 | // → ["Unit 1", "Unit 2", "Unit 3", "Unit 4", "Unit 5"] 116 | ``` 117 | 118 | 这个结构有点像`for`循环 - 它首先描述了这种循环,然后提供了一个主体。 但是,主体现在写为一个函数值,它被包裹在`repeat`调用的括号中。 这就是它必须用右小括号和右大括号闭合的原因。 在这个例子中,主体是单个小表达式,你也可以省略大括号并将循环写成单行。 119 | 120 | ## 高阶函数 121 | 122 | 如果一个函数操作其他函数,即将其他函数作为参数或将函数作为返回值,那么我们可以将其称为高阶函数。因为我们已经看到函数就是一个普通的值,那么高阶函数也就不是什么稀奇的概念了。高阶这个术语来源于数学,在数学当中,函数和值的概念有着严格的区分。 123 | 124 | 我们可以使用高阶函数对一系列操作和值进行抽象。高阶函数有多种表现形式。比如你可以使用高阶函数来新建另一些函数。 125 | 126 | ```js 127 | function greaterThan(n) { 128 | return m => m > n; 129 | } 130 | let greaterThan10 = greaterThan(10); 131 | console.log(greaterThan10(11)); 132 | // → true 133 | ``` 134 | 135 | 你也可以使用高阶函数来修改其他的函数。 136 | 137 | ```js 138 | function noisy(f) { 139 | return (...args) => { 140 | console.log("calling with", args); 141 | let result = f(...args); 142 | console.log("called with", args, ", returned", result); 143 | return result; 144 | }; 145 | } 146 | noisy(Math.min)(3, 2, 1); 147 | // → calling with [3, 2, 1] 148 | // → called with [3, 2, 1] , returned 1 149 | ``` 150 | 151 | 你甚至可以使用高阶函数来实现新的控制流。 152 | 153 | ```js 154 | function unless(test, then) { 155 | if (!test) then(); 156 | } 157 | repeat(3, n => { 158 | unless(n % 2 == 1, () => { 159 | console.log(n, "is even"); 160 | }); 161 | }); 162 | // → 0 is even 163 | // → 2 is even 164 | ``` 165 | 166 | 有一个内置的数组方法,`forEach`,它提供了类似`for/of`循环的东西,作为一个高阶函数。 167 | 168 | ```js 169 | ["A", "B"].forEach(l => console.log(l)); 170 | // → A 171 | // → B 172 | ``` 173 | 174 | ## 脚本数据集 175 | 176 | 数据处理是高阶函数表现突出的一个领域。 为了处理数据,我们需要一些真实数据。 本章将使用脚本书写系统的数据集,例如拉丁文,西里尔文或阿拉伯文。 177 | 178 | 请记住第 1 章中的 Unicode,该系统为书面语言中的每个字符分配一个数字。 大多数这些字符都与特定的脚本相关联。 该标准包含 140 个不同的脚本 - 81 个今天仍在使用,59 个是历史性的。 179 | 180 | 虽然我只能流利地阅读拉丁字符,但我很欣赏这样一个事实,即人们使用其他至少 80 种书写系统来编写文本,其中许多我甚至不认识。 例如,以下是泰米尔语手写体的示例。 181 | 182 | ![](img/5-1.png) 183 | 184 | 示例数据集包含 Unicode 中定义的 140 个脚本的一些信息。 本章的[编码沙箱](https://eloquentjavascript.net/code#5)中提供了`SCRIPTS`绑定。 该绑定包含一组对象,其中每个对象都描述了一个脚本。 185 | 186 | ```json 187 | { 188 | name: "Coptic", 189 | ranges: [[994, 1008], [11392, 11508], [11513, 11520]], 190 | direction: "ltr", 191 | year: -200, 192 | living: false, 193 | link: "https://en.wikipedia.org/wiki/Coptic_alphabet" 194 | } 195 | ``` 196 | 197 | 这样的对象会告诉你脚本的名称,分配给它的 Unicode 范围,书写方向,(近似)起始时间,是否仍在使用以及更多信息的链接。 方向可以是从左到右的`"ltr"`,从右到左的`"rtl"`(阿拉伯语和希伯来语文字的写法),或者从上到下的`"ttb"`(蒙古文的写法)。 198 | 199 | `ranges`属性包含 Unicode 字符范围数组,每个数组都有两元素,包含下限和上限。 这些范围内的任何字符码都会分配给脚本。 下限是包括的(代码 994 是一个科普特字符),并且上限排除在外(代码 1008 不是)。 200 | 201 | ## 数组过滤 202 | 203 | 为了找到数据集中仍在使用的脚本,以下函数可能会有所帮助。 它过滤掉数组中未通过测试的元素: 204 | 205 | ```js 206 | function filter(array, test) { 207 | let passed = []; 208 | for (let element of array) { 209 | if (test(element)) { 210 | passed.push(element); 211 | } 212 | } 213 | return passed; 214 | } 215 | 216 | console.log(filter(SCRIPTS, script => script.living)); 217 | // → [{name: "Adlam", …}, …] 218 | ``` 219 | 220 | 该函数使用名为`test`的参数(一个函数值)填充计算中的“间隙” - 决定要收集哪些元素的过程。 221 | 222 | 需要注意的是,`filter`函数并没有从当前数组中删除元素,而是新建了一个数组,并将满足条件的元素存入新建的数组中。这个函数是一个“纯函数”,因为该函数并未修改给定的数组。 223 | 224 | 与`forEach`一样,`filter`函数也是标准的数组方法。本例中定义的函数只是用于展示内部实现原理。今后我们会使用以下方法来过滤数据: 225 | 226 | ```js 227 | console.log(SCRIPTS.filter(s => s.direction == "ttb")); 228 | // → [{name: "Mongolian", …}, …] 229 | ``` 230 | 231 | ## 使用`map`函数转换数组 232 | 233 | 假设我们已经通过某种方式过滤了`SCRIPTS`数组,生成一个用于表示脚本的信息数组。但我们想创建一个包含名称的数组,因为这样更加易于检查。 234 | 235 | `map`方法对数组中的每个元素调用函数,然后利用返回值来构建一个新的数组,实现转换数组的操作。新建数组的长度与输入的数组一致,但其中的内容却通过对每个元素调用的函数“映射”成新的形式。 236 | 237 | ```js 238 | function map(array, transform) { 239 | let mapped = []; 240 | for (let element of array) { 241 | mapped.push(transform(element)); 242 | } 243 | return mapped; 244 | } 245 | 246 | let rtlScripts = SCRIPTS.filter(s => s.direction == "rtl"); 247 | console.log(map(rtlScripts, s => s.name)); 248 | // → ["Adlam", "Arabic", "Imperial Aramaic", …] 249 | ``` 250 | 251 | 与`forEach`和`filter`一样,`map`也是标准的数组方法。 252 | 253 | ## 使用`reduce`汇总数据 254 | 255 | 与数组有关的另一个常见事情是从它们中计算单个值。 我们的递归示例,汇总了一系列数字,就是这样一个例子。 另一个例子是找到字符最多的脚本。 256 | 257 | 表示这种模式的高阶操作称为归约(reduce)(有时也称为折叠(fold))。 它通过反复从数组中获取单个元素,并将其与当前值合并来构建一个值。 在对数字进行求和时,首先从数字零开始,对于每个元素,将其与总和相加。 258 | 259 | `reduce`函数包含三个参数:数组、执行合并操作的函数和初始值。该函数没有`filter`和`map`那样直观,所以仔细看看: 260 | 261 | ```js 262 | function reduce(array, combine, start) { 263 | let current = start; 264 | for (let element of array) { 265 | current = combine(current, element); 266 | } 267 | return current; 268 | } 269 | 270 | console.log(reduce([1, 2, 3, 4], (a, b) => a + b, 0)); 271 | // → 10 272 | ``` 273 | 274 | 数组中有一个标准的`reduce`方法,当然和我们上面看到的那个函数一致,可以简化合并操作。如果你的数组中包含多个元素,在调用`reduce`方法的时候忽略了`start`参数,那么该方法将会使用数组中的第一个元素作为初始值,并从第二个元素开始执行合并操作。 275 | 276 | ```js 277 | console.log([1, 2, 3, 4].reduce((a, b) => a + b)); 278 | // → 10 279 | ``` 280 | 281 | 为了使用`reduce`(两次)来查找字符最多的脚本,我们可以这样写: 282 | 283 | ```js 284 | function characterCount(script) { 285 | return script.ranges.reduce((count, [from, to]) => { 286 | return count + (to - from); 287 | }, 0); 288 | } 289 | 290 | console.log(SCRIPTS.reduce((a, b) => { 291 | return characterCount(a) < characterCount(b) ? b : a; 292 | })); 293 | // → {name: "Han", …} 294 | ``` 295 | 296 | `characterCount`函数通过累加范围的大小,来减少分配给脚本的范围。 请注意归约器函数的参数列表中使用的解构。 `reduce'的第二次调用通过重复比较两个脚本并返回更大的脚本,使用它来查找最大的脚本。 297 | 298 | Unicode 标准分配了超过 89,000 个字符给汉字脚本,它成为数据集中迄今为止最大的书写系统。 汉字是一种(有时)用于中文,日文和韩文的文字。 这些语言共享很多字符,尽管他们倾向于以不同的方式写它们。 (基于美国的)Unicode 联盟决定将它们看做一个单独的书写系统来保存字符码。 这被称为中日韩越统一表意文字(Han unification),并且仍然使一些人非常生气。 299 | 300 | ## 可组合性 301 | 302 | 考虑一下,我们怎样才可以在不使用高阶函数的情况下,编写以上示例(找到最大的脚本)?代码没有那么糟糕。 303 | 304 | ```js 305 | let biggest = null; 306 | for (let script of SCRIPTS) { 307 | if (biggest == null || 308 | characterCount(biggest) < characterCount(script)) { 309 | biggest = script; 310 | } 311 | } 312 | console.log(biggest); 313 | // → {name: "Han", …} 314 | ``` 315 | 316 | 这段代码中多了一些绑定,虽然多了两行代码,但代码逻辑还是很容易让人理解的。 317 | 318 | 当你需要组合操作时,高阶函数的价值就突显出来了。举个例子,我们编写一段代码,找出数据集中男人和女人的平均年龄。 319 | 320 | ```js 321 | function average(array) { 322 | return array.reduce((a, b) => a + b) / array.length; 323 | } 324 | 325 | console.log(Math.round(average( 326 | SCRIPTS.filter(s => s.living).map(s => s.year)))); 327 | // → 1185 328 | console.log(Math.round(average( 329 | SCRIPTS.filter(s => !s.living).map(s => s.year)))); 330 | // → 209 331 | ``` 332 | 333 | 因此,Unicode 中的死亡脚本,平均比活动脚本更老。 这不是一个非常有意义或令人惊讶的统计数据。 但是我希望你会同意,用于计算它的代码不难阅读。 你可以把它看作是一个流水线:我们从所有脚本开始,过滤出活动的(或死亡的)脚本,从这些脚本中抽出时间,对它们进行平均,然后对结果进行四舍五入。 334 | 335 | 你当然也可以把这个计算写成一个大循环。 336 | 337 | ```js 338 | let total = 0, count = 0; 339 | for (let script of SCRIPTS) { 340 | if (script.living) { 341 | total += script.year; 342 | count += 1; 343 | } 344 | } 345 | console.log(Math.round(total / count)); 346 | // → 1185 347 | ``` 348 | 349 | 但很难看到正在计算什么以及如何计算。 而且由于中间结果并不表示为一致的值,因此将“平均值”之类的东西提取到单独的函数中,需要更多的工作。 350 | 351 | 就计算机实际在做什么而言,这两种方法也是完全不同的。 第一个在运行`filter`和`map`的时候会建立新的数组,而第二个只会计算一些数字,从而减少工作量。 你通常可以采用可读的方法,但是如果你正在处理巨大的数组,并且多次执行这些操作,那么抽象风格的加速就是值得的。 352 | 353 | ## 字符串和字符码 354 | 355 | 这个数据集的一种用途是确定一段文本所使用的脚本。 我们来看看执行它的程序。 356 | 357 | 请记住,每个脚本都有一组与其相关的字符码范围。 所以给定一个字符码,我们可以使用这样的函数来找到相应的脚本(如果有的话): 358 | 359 | ```js 360 | function characterScript(code) { 361 | for (let script of SCRIPTS) { 362 | if (script.ranges.some(([from, to]) => { 363 | return code >= from && code < to; 364 | })) { 365 | return script; 366 | } 367 | } 368 | return null; 369 | } 370 | 371 | console.log(characterScript(121)); 372 | // → {name: "Latin", …} 373 | ``` 374 | 375 | `some`方法是另一个高阶函数。 它需要一个测试函数,并告诉你该函数是否对数组中的任何元素返回`true`。 376 | 377 | 但是,我们如何获得字符串中的字符码? 378 | 379 | 在第一章中,我提到 JavaScript 字符串被编码为一个 16 位数字的序列。 这些被称为代码单元。 一个 Unicode 字符代码最初应该能放进这样一个单元(它给你超 65,000 个字符)。 后来人们发现它不够用了,很多人避开了为每个字符使用更多内存的需求。 为了解决这些问题,人们发明了 UTF-16,JavaScript 字符串使用的格式 。它使用单个 16 位代码单元描述了大多数常见字符,但是为其他字符使用一对两个这样的单元。 380 | 381 | 今天 UTF-16 通常被认为是一个糟糕的主意。 它似乎总是故意设计来引起错误。 很容易编写程序,假装代码单元和字符是一个东西。 如果你的语言不使用两个单位的字符,显然能正常工作。 但只要有人试图用一些不太常见的中文字符来使用这样的程序,就会中断。 幸运的是,随着 emoji 符号的出现,每个人都开始使用两个单元的字符,处理这些问题的负担更加分散。 382 | 383 | ```js 384 | // Two emoji characters, horse and shoe 385 | let horseShoe = "\ud83d\udc34\ud83d\udc5f"; 386 | console.log(horseShoe.length); 387 | // → 4 388 | console.log(horseShoe[0]); 389 | // → (Invalid half-character) 390 | console.log(horseShoe.charCodeAt(0)); 391 | // → 55357 (Code of the half-character) 392 | console.log(horseShoe.codePointAt(0)); 393 | // → 128052 (Actual code for horse emoji) 394 | ``` 395 | 396 | JavaScript的`charCodeAt`方法为你提供了一个代码单元,而不是一个完整的字符代码。 稍后添加的`codePointAt`方法确实提供了完整的 Unicode 字符。 所以我们可以使用它从字符串中获取字符。 但传递给`codePointAt`的参数仍然是代码单元序列的索引。 因此,要运行字符串中的所有字符,我们仍然需要处理一个字符占用一个还是两个代码单元的问题。 397 | 398 | 在上一章中,我提到`for/of`循环也可以用在字符串上。 像`codePointAt`一样,这种类型的循环,是在人们敏锐地意识到 UTF-16 的问题的时候引入的。 当你用它来遍历一个字符串时,它会给你真正的字符,而不是代码单元。 399 | 400 | ```js 401 | let roseDragon = "\ud83c\udf45\ud83d\udc09"; 402 | for (let char of roseDragon) { 403 | console.log(char); 404 | // → (emoji rose) 405 | // → (emoji dragon) 406 | ``` 407 | 408 | 如果你有一个字符(它是一个或两个代码单元的字符串),你可以使用`codePointAt(0)`来获得它的代码。 409 | 410 | ## 识别文本 411 | 412 | 我们有了`characterScript`函数和一种正确遍历字符的方法。 下一步将是计算属于每个脚本的字符。 下面的计数抽象会很实用: 413 | 414 | ```js 415 | function countBy(items, groupName) { 416 | let counts = []; 417 | for (let item of items) { 418 | let name = groupName(item); 419 | let known = counts.findIndex(c => c.name == name); 420 | if (known == -1) { 421 | counts.push({name, count: 1}); 422 | } else { 423 | counts[known].count++; 424 | } 425 | } 426 | return counts; 427 | } 428 | 429 | console.log(countBy([1, 2, 3, 4, 5], n => n > 2)); 430 | // → [{name: false, count: 2}, {name: true, count: 3}] 431 | ``` 432 | 433 | `countBy`函数需要一个集合(我们可以用`for/of`来遍历的任何东西)以及一个函数,它计算给定元素的组名。 它返回一个对象数组,每个对象命名一个组,并告诉你该组中找到的元素数量。 434 | 435 | 它使用另一个数组方法`findIndex`。 这个方法有点像`indexOf`,但它不是查找特定的值,而是查找给定函数返回`true`的第一个值。 像`indexOf`一样,当没有找到这样的元素时,它返回 -1。 436 | 437 | 使用`countBy`,我们可以编写一个函数,告诉我们在一段文本中使用了哪些脚本。 438 | 439 | ```js 440 | function textScripts(text) { 441 | let scripts = countBy(text, char => { 442 | let script = characterScript(char.codePointAt(0)); 443 | return script ? script.name : "none"; 444 | }).filter(({name}) => name != "none"); 445 | 446 | let total = scripts.reduce((n, {count}) => n + count, 0); 447 | if (total == 0) return "No scripts found"; 448 | 449 | return scripts.map(({name, count}) => { 450 | return `${Math.round(count * 100 / total)}% ${name}`; 451 | }).join(", "); 452 | } 453 | 454 | console.log(textScripts('英国的狗说"woof", 俄罗斯的狗说"тяв"')); 455 | // → 61% Han, 22% Latin, 17% Cyrillic 456 | ``` 457 | 458 | 该函数首先按名称对字符进行计数,使用`characterScript`为它们分配一个名称,并且对于不属于任何脚本的字符,回退到字符串`"none"`。 `filter`调用从结果数组中删除`"none"`的条目,因为我们对这些字符不感兴趣。 459 | 460 | 为了能够计算百分比,我们首先需要属于脚本的字符总数,我们可以用`reduce`来计算。 如果没有找到这样的字符,该函数将返回一个特定的字符串。 否则,它使用`map`将计数条目转换为可读的字符串,然后使用`join`合并它们。 461 | 462 | ## 本章小结 463 | 464 | 能够将函数值传递给其他函数,是 JavaScript 的一个非常有用的方面。 它允许我们编写函数,用它们中的“间隙”对计算建模。 调用这些函数的代码,可以通过提供函数值来填补间隙。 465 | 466 | 数组提供了许多有用的高阶方法。 你可以使用`forEach`来遍历数组中的元素。 `filter`方法返回一个新数组,只包含通过谓词函数的元素。 通过将函数应用于每个元素的数组转换,使用`map`来完成。 你可以使用`reduce`将数组中的所有元素合并为一个值。 `some`方法测试任何元素是否匹配给定的谓词函数。 `findIndex`找到匹配谓词的第一个元素的位置。 467 | 468 | ## 习题 469 | 470 | ### 展开 471 | 472 | 联合使用`reduce`方法和`concat`方法,将一个数组的数组“展开”成一个单个数组,包含原始数组的所有元素。 473 | 474 | ```js 475 | let arrays = [[1, 2, 3], [4, 5], [6]]; 476 | // Your code here. 477 | // → [1, 2, 3, 4, 5, 6] 478 | ``` 479 | 480 | ### 你自己的循环 481 | 482 | 编写一个高阶函数`loop`,提供类似`for`循环语句的东西。 它接受一个值,一个测试函数,一个更新函数和一个主体函数。 每次迭代中,它首先在当前循环值上运行测试函数,并在返回`false`时停止。 然后它调用主体函数,向其提供当前值。 最后,它调用`update`函数来创建一个新的值,并从头开始。 483 | 484 | 定义函数时,可以使用常规循环来执行实际循环。 485 | 486 | ```js 487 | // Your code here. 488 | 489 | loop(3, n => n > 0, n => n - 1, console.log); 490 | // → 3 491 | // → 2 492 | // → 1 493 | ``` 494 | 495 | ### `every` 496 | 497 | 类似于`some`方法,数组也有`every`方法。 当给定函数对数组中的每个元素返回`true`时,此函数返回`true`。 在某种程度上,`some`是作用于数组的`||`运算符的一个版本,`every`就像`&&`运算符。 498 | 499 | 将`every`实现为一个函数,接受一个数组和一个谓词函数作为参数。编写两个版本,一个使用循环,另一个使用`some`方法。 500 | 501 | ```js 502 | function every(array, test) { 503 | // Your code here. 504 | } 505 | 506 | console.log(every([1, 3, 5], n => n < 10)); 507 | // → true 508 | console.log(every([2, 4, 16], n => n < 10)); 509 | // → false 510 | console.log(every([], n => n < 10)); 511 | // → true 512 | ``` 513 | -------------------------------------------------------------------------------- /7.md: -------------------------------------------------------------------------------- 1 | # 七、项目:机器人 2 | 3 | > 原文:[Project: A Robot](http://eloquentjavascript.net/07_robot.html) 4 | > 5 | > 译者:[飞龙](https://github.com/wizardforcel) 6 | > 7 | > 协议:[CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/) 8 | > 9 | > 自豪地采用[谷歌翻译](https://translate.google.cn/) 10 | 11 | > [...] 置疑计算机能不能思考 [...] 就相当于置疑潜艇能不能游泳。 12 | > 13 | > 艾兹格尔·迪科斯特拉,《计算机科学的威胁》 14 | 15 | ![](img/7-0.jpg) 16 | 17 | 在“项目”章节中,我会在短时间内停止向你讲述新理论,相反我们会一起完成一个项目。 学习编程理论是必要的,但阅读和理解实际的计划同样重要。 18 | 19 | 我们在本章中的项目是构建一个自动机,一个在虚拟世界中执行任务的小程序。 我们的自动机将是一个接送包裹的邮件递送机器人。 20 | 21 | ## Meadowfield 22 | 23 | Meadowfield 村不是很大。 它由 11 个地点和 14 条道路组成。 它可以用`roads`数组来描述: 24 | 25 | ```js 26 | const roads = [ 27 | "Alice's House-Bob's House", "Alice's House-Cabin", 28 | "Alice's House-Post Office", "Bob's House-Town Hall", 29 | "Daria's House-Ernie's House", "Daria's House-Town Hall", 30 | "Ernie's House-Grete's House", "Grete's House-Farm", 31 | "Grete's House-Shop", "Marketplace-Farm", 32 | "Marketplace-Post Office", "Marketplace-Shop", 33 | "Marketplace-Town Hall", "Shop-Town Hall" 34 | ]; 35 | ``` 36 | 37 | ![](img/7-1.png) 38 | 39 | 村里的道路网络形成了一个图。 图是节点(村里的地点)与他们之间的边(道路)的集合。 这张图将成为我们的机器人在其中移动的世界。 40 | 41 | 字符串数组并不易于处理。 我们感兴趣的是,我们可以从特定地点到达的目的地。 让我们将道路列表转换为一个数据结构,对于每个地点,都会告诉我们从那里可以到达哪些地点。 42 | 43 | ```js 44 | function buildGraph(edges) { 45 | let graph = Object.create(null); 46 | function addEdge(from, to) { 47 | if (graph[from] == null) { 48 | graph[from] = [to]; 49 | } else { 50 | graph[from].push(to); 51 | } 52 | } 53 | for (let [from, to] of edges.map(r => r.split("-"))) { 54 | addEdge(from, to); 55 | addEdge(to, from); 56 | } 57 | return graph; 58 | } 59 | 60 | const roadGraph = buildGraph(roads); 61 | ``` 62 | 63 | 给定边的数组,`buildGraph`创建一个映射对象,该对象为每个节点存储连通节点的数组。 64 | 65 | 它使用`split`方法,将形式为`"Start-End"`的道路字符串,转换为两元素数组,包含起点和终点作为单个字符串。 66 | 67 | ## 任务 68 | 69 | 我们的机器人将在村庄周围移动。 在各个地方都有包裹,每个都寄往其他地方。 机器人在收到包裹时拾取包裹,并在抵达目的地时将其送达。 70 | 71 | 自动机必须在每个点决定下一步要去哪里。 所有包裹递送完成后,它就完成了任务。 72 | 73 | 为了能够模拟这个过程,我们必须定义一个可以描述它的虚拟世界。 这个模型告诉我们机器人在哪里以及包裹在哪里。 当机器人决定移到某处时,我们需要更新模型以反映新情况。 74 | 75 | 如果你正在考虑面向对象编程,你的第一个冲动可能是开始为世界中的各种元素定义对象。 一个机器人,一个包裹,也许还有一个地点。 然后,它们可以持有描述其当前状态的属性,例如某个位置的一堆包裹,我们可以在更新世界时改变这些属性。 76 | 77 | 这是错的。 78 | 79 | 至少,通常是这样。 一个东西听起来像一个对象,并不意味着它应该是你的程序中的一个对象。 为应用程序中的每个概念反射式编写类,往往会留下一系列互连对象,每个对象都有自己的内部的变化的状态。 这样的程序通常很难理解,因此很容易崩溃。 80 | 81 | 相反,让我们将村庄的状态压缩成定义它的值的最小集合。 机器人的当前位置和未送达的包裹集合,其中每个都拥有当前位置和目标地址。这样就够了。 82 | 83 | 当我们到达新地点时,让我们这样做,在机器人移动时不会改变这种状态,而是在移动之后为当前情况计算一个新状态。 84 | 85 | ```js 86 | class VillageState { 87 | constructor(place, parcels) { 88 | this.place = place; 89 | this.parcels = parcels; 90 | } 91 | 92 | move(destination) { 93 | if (!roadGraph[this.place].includes(destination)) { 94 | return this; 95 | } else { 96 | let parcels = this.parcels.map(p => { 97 | if (p.place != this.place) return p; 98 | return {place: destination, address: p.address}; 99 | }).filter(p => p.place != p.address); 100 | return new VillageState(destination, parcels); 101 | } 102 | } 103 | } 104 | ``` 105 | 106 | `move`方法是动作发生的地方。 它首先检查是否有当前位置到目的地的道路,如果没有,则返回旧状态,因为这不是有效的移动。 107 | 108 | 然后它创建一个新的状态,将目的地作为机器人的新地点。 但它也需要创建一套新的包裹 - 机器人携带的包裹(位于机器人当前位置)需要移动到新位置。 而要寄往新地点的包裹需要送达 - 也就是说,需要将它们从未送达的包裹中移除。 `'map'`的调用处理移动,并且`'filter'`的调用处理递送。 109 | 110 | 包裹对象在移动时不会更改,但会被重新创建。 `move`方法为我们提供新的村庄状态,但完全保留了原有的村庄状态。 111 | 112 | ```js 113 | let first = new VillageState( 114 | "Post Office", 115 | [{place: "Post Office", address: "Alice's House"}] 116 | ); 117 | let next = first.move("Alice's House"); 118 | 119 | console.log(next.place); 120 | // → Alice's House 121 | console.log(next.parcels); 122 | // → [] 123 | console.log(first.place); 124 | // → Post Office 125 | ``` 126 | 127 | `move`会使包裹被送达,并在下一个状态中反映出来。 但最初的状态仍然描述机器人在邮局并且包裹未送达的情况。 128 | 129 | ## 持久性数据 130 | 131 | 不会改变的数据结构称为不变的(immutable)或持久性的(persistent)。 他们的表现很像字符串和数字,因为他们就是他们自己,并保持这种状态,而不是在不同的时间包含不同的东西。 132 | 133 | 在 JavaScript 中,几乎所有的东西都可以改变,所以使用应该持久性的值需要一些限制。 有一个叫做`Object.freeze`的函数,它可以改变一个对象,使其忽略它的属性的写入。 如果你想要小心,你可以使用它来确保你的对象没有改变。 `freeze`确实需要计算机做一些额外的工作,忽略更新可能会让一些人迷惑,让他们做错事。 所以我通常更喜欢告诉人们,不应该弄乱给定的对象,并希望他们记住它。 134 | 135 | ```js 136 | let object = Object.freeze({value: 5}); 137 | object.value = 10; 138 | console.log(object.value); 139 | // → 5 140 | ``` 141 | 142 | 当语言显然期待我这样做时,为什么我不想改变对象? 143 | 144 | 因为它帮助我理解我的程序。 这又是关于复杂性管理。 当我的系统中的对象是固定的,稳定的东西时,我可以孤立地考虑操作它们 - 从给定的起始状态移动到爱丽丝的房子,始终会产生相同的新状态。 当对象随着时间而改变时,这就给这种推理增加了全新的复杂性。 145 | 146 | 对于小型系统,例如我们在本章中构建的东西,我们可以处理那些额外的复杂性。 但是我们可以建立什么样的系统,最重要的限制是我们能够理解多少。 任何让你的代码更容易理解的东西,都可以构建一个更加庞大的系统。 147 | 148 | 不幸的是,尽管理解构建在持久性数据结构上的系统比较容易,但设计一个,特别是当你的编程语言没有帮助时,可能会更难一些。 我们将在本书中寻找使用持久性数据结构的时机,但我们也将使用可变数据结构。 149 | 150 | ## 模拟 151 | 152 | 递送机器人观察世界并决定它想要移动的方向。 因此,我们可以说机器人是一个函数,接受`VillageState`对象并返回附近地点的名称。 153 | 154 | 因为我们希望机器人能够记住东西,以便他们可以制定和执行计划,我们也会传递他们的记忆,并让他们返回一个新的记忆。 因此,机器人返回的东西是一个对象,包含它想要移动的方向,以及下次调用时将返回给它的记忆值。 155 | 156 | ```js 157 | function runRobot(state, robot, memory) { 158 | for (let turn = 0;; turn++) { 159 | if (state.parcels.length == 0) { 160 | console.log(`Done in ${turn} turns`); 161 | break; 162 | } 163 | let action = robot(state, memory); 164 | state = state.move(action.direction); 165 | memory = action.memory; 166 | console.log(`Moved to ${action.direction}`); 167 | } 168 | } 169 | ``` 170 | 171 | 考虑一下机器人必须做些什么来“解决”一个给定的状态。 它必须通过访问拥有包裹的每个位置来拾取所有包裹,并通过访问包裹寄往的每个位置来递送,但只能在拾取包裹之后。 172 | 173 | 什么是可能有效的最愚蠢的策略? 机器人可以在每回合中,向随机方向行走。 这意味着很有可能它最终会碰到所有的包裹,然后也会在某个时候到达包裹应该送达的地方。 174 | 175 | 以下是可能的样子: 176 | 177 | ```js 178 | function randomPick(array) { 179 | let choice = Math.floor(Math.random() * array.length); 180 | return array[choice]; 181 | } 182 | 183 | function randomRobot(state) { 184 | return {direction: randomPick(roadGraph[state.place])}; 185 | } 186 | ``` 187 | 188 | 请记住,`Math.random()`返回 0 和 1 之间的数字,但总是小于 1。 将这样一个数乘以数组长度,然后将`Math.floor`应用于它,向我们提供数组的随机索引。 189 | 190 | 由于这个机器人不需要记住任何东西,所以它忽略了它的第二个参数(记住,可以使用额外的参数调用 JavaScript 函数而不会产生不良影响)并省略返回对象中的`memory`属性。 191 | 192 | 为了使这个复杂的机器人工作,我们首先需要一种方法来创建一些包裹的新状态。 静态方法(通过直接向构造函数添加一个属性来编写)是放置该功能的好地方。 193 | 194 | ```js 195 | VillageState.random = function(parcelCount = 5) { 196 | let parcels = []; 197 | for (let i = 0; i < parcelCount; i++) { 198 | let address = randomPick(Object.keys(roadGraph)); 199 | let place; 200 | do { 201 | place = randomPick(Object.keys(roadGraph)); 202 | } while (place == address); 203 | parcels.push({place, address}); 204 | } 205 | return new VillageState("Post Office", parcels); 206 | }; 207 | ``` 208 | 209 | 我们不想要发往寄出地的任何包裹。 出于这个原因,当`do`循环获取与地址相同的地方时,它会继续选择新的地方。 210 | 211 | 让我们建立一个虚拟世界。 212 | 213 | ```js 214 | runRobot(VillageState.random(), randomRobot); 215 | // → Moved to Marketplace 216 | // → Moved to Town Hall 217 | // → … 218 | // → Done in 63 turns 219 | ``` 220 | 221 | 机器人需要花费很多时间来交付包裹,因为它没有很好规划。 我们很快就会解决。 222 | 223 | 为了更好地理解模拟,你可以使用本章编程环境中提供的`runRobotAnimation`函数。 这将运行模拟,但不是输出文本,而是向你展示机器人在村庄地图上移动。 224 | 225 | ```js 226 | runRobotAnimation(VillageState.random(), randomRobot); 227 | ``` 228 | 229 | `runRobotAnimation`的实现方式现在仍然是一个谜,但是在阅读本书的后面的章节,讨论 Web 浏览器中的 JavaScript 集成之后,你将能够猜到它的工作原理。 230 | 231 | ## 邮车的路线 232 | 233 | 我们应该能够比随机机器人做得更好。 一个简单的改进就是从现实世界的邮件传递方式中获得提示。 如果我们发现一条经过村庄所有地点的路线,机器人可以通行该路线两次,此时它保证能够完成。 这是一条这样的路线(从邮局开始)。 234 | 235 | ```js 236 | const mailRoute = [ 237 | "Alice's House", "Cabin", "Alice's House", "Bob's House", 238 | "Town Hall", "Daria's House", "Ernie's House", 239 | "Grete's House", "Shop", "Grete's House", "Farm", 240 | "Marketplace", "Post Office" 241 | ]; 242 | ``` 243 | 244 | 为了实现路线跟踪机器人,我们需要利用机器人的记忆。 机器人将其路线的其余部分保存在其记忆中,并且每回合丢弃第一个元素。 245 | 246 | ```js 247 | function routeRobot(state, memory) { 248 | if (memory.length == 0) { 249 | memory = mailRoute; 250 | } 251 | return {direction: memory[0], memory: memory.slice(1)}; 252 | } 253 | ``` 254 | 255 | 这个机器人已经快了很多。 它最多需要 26 个回合(13 步的路线的两倍),但通常要少一些。 256 | 257 | ```js 258 | runRobotAnimation(VillageState.random(), routeRobot, []); 259 | ``` 260 | 261 | ## 寻路 262 | 263 | 不过,我不会盲目遵循固定的智能寻路行为。 如果机器人为需要完成的实际工作调整行为,它可以更高效地工作。 264 | 265 | 为此,它必须能够有针对性地朝着给定的包裹移动,或者朝着包裹必须送达的地点。 尽管如此,即使目标距离我们不止一步,也需要某种寻路函数。 266 | 267 | 在图上寻找路线的问题是一个典型的搜索问题。 我们可以判断一个给定的解决方案(路线)是否是一个有效的解决方案,但我们不能像 2 + 2 这样,直接计算解决方案。 相反,我们必须不断创建潜在的解决方案,直到找到有效的解决方案。 268 | 269 | 图上的可能路线是无限的。 但是当搜索`A`到`B`的路线时,我们只关注从`A`起始的路线。 我们也不关心两次访问同一地点的路线 - 这绝对不是最有效的路线。 这样可以减少查找者必须考虑的路线数量。 270 | 271 | 事实上,我们最感兴趣的是最短路线。 所以我们要确保,查看较长路线之前,我们要查看较短的路线。 一个好的方法是,从起点使路线“生长”,探索尚未到达的每个可到达的地方,直到路线到达目标。 这样,我们只探索潜在的有趣路线,并找到到目标的最短路线(或最短路线之一,如果有多条路线)。 272 | 273 | 这是一个实现它的函数: 274 | 275 | ```js 276 | function findRoute(graph, from, to) { 277 | let work = [{at: from, route: []}]; 278 | for (let i = 0; i < work.length; i++) { 279 | let {at, route} = work[i]; 280 | for (let place of graph[at]) { 281 | if (place == to) return route.concat(place); 282 | if (!work.some(w => w.at == place)) { 283 | work.push({at: place, route: route.concat(place)}); 284 | } 285 | } 286 | } 287 | } 288 | ``` 289 | 290 | 探索必须按照正确的顺序完成 - 首先到达的地方必须首先探索。 我们不能到达一个地方就立即探索,因为那样意味着,从那里到达的地方也会被立即探索,以此类推,尽管可能还有其他更短的路径尚未被探索。 291 | 292 | 因此,该函数保留一个工作列表。 这是一系列应该探索的地方,以及让我们到那里的路线。 它最开始只有起始位置和空路线。 293 | 294 | 然后,通过获取列表中的下一个项目并进行探索,来执行搜索,这意味着,会查看从该地点起始的所有道路。 如果其中之一是目标,则可以返回完成的路线。 否则,如果我们以前没有看过这个地方,就会在列表中添加一个新项目。 如果我们之前看过它,因为我们首先查看了短路线,我们发现,到达那个地方的路线较长,或者与现有路线一样长,我们不需要探索它。 295 | 296 | 你可以在视觉上将它想象成一个已知路线的网,从起始位置爬出来,在各个方向上均匀生长(但不会缠绕回去)。 只要第一条线到达目标位置,其它线就会退回起点,为我们提供路线。 297 | 298 | 我们的代码无法处理工作列表中没有更多工作项的情况,因为我们知道我们的图是连通的,这意味着可以从其他所有位置访问每个位置。 我们始终能够找到两点之间的路线,并且搜索不会失败。 299 | 300 | ```js 301 | function goalOrientedRobot({place, parcels}, route) { 302 | if (route.length == 0) { 303 | let parcel = parcels[0]; 304 | if (parcel.place != place) { 305 | route = findRoute(roadGraph, place, parcel.place); 306 | } else { 307 | route = findRoute(roadGraph, place, parcel.address); 308 | } 309 | } 310 | return {direction: route[0], memory: route.slice(1)}; 311 | } 312 | ``` 313 | 314 | 这个机器人使用它的记忆值作为移动方向的列表,就像寻路机器人一样。 无论什么时候这个列表是空的,它都必须弄清下一步该做什么。 它会取出集合中第一个未送达的包裹,如果该包裹还没有被拾取,则会绘制一条朝向它的路线。 如果包裹已经被拾取,它仍然需要送达,所以机器人会创建一个朝向递送地址的路线。 315 | 316 | 让我们看看如何实现。 317 | 318 | ```js 319 | runRobotAnimation(VillageState.random(), 320 | goalOrientedRobot, []); 321 | ``` 322 | 323 | 这个机器人通常在大约 16 个回合中,完成了送达 5 个包裹的任务。 略好于`routeRobot`,但仍然绝对不是最优的。 324 | 325 | ## 练习 326 | 327 | ### 测量机器人 328 | 329 | 很难通过让机器人解决一些场景来客观比较他们。 也许一个机器人碰巧得到了更简单的任务,或者它擅长的那种任务,而另一个没有。 330 | 331 | 编写一个`compareRobots`,接受两个机器人(和它们的起始记忆)。 它应该生成 100 个任务,并让每个机器人解决每个这些任务。 完成后,它应输出每个机器人每个任务的平均步数。 332 | 333 | 为了公平起见,请确保你将每个任务分配给两个机器人,而不是为每个机器人生成不同的任务。 334 | 335 | ```js 336 | function compareRobots(robot1, memory1, robot2, memory2) { 337 | // Your code here 338 | } 339 | 340 | compareRobots(routeRobot, [], goalOrientedRobot, []); 341 | ``` 342 | 343 | ### 机器人的效率 344 | 345 | 你能写一个机器人,比`goalOrientedRobot`更快完成递送任务吗? 如果你观察机器人的行为,它会做什么明显愚蠢的事情?如何改进它们? 346 | 347 | 如果你解决了上一个练习,你可能打算使用`compareRobots`函数来验证你是否改进了机器人。 348 | 349 | ```js 350 | // Your code here 351 | 352 | runRobotAnimation(VillageState.random(), yourRobot, memory); 353 | ``` 354 | 355 | ### 持久性分组 356 | 357 | 标准 JavaScript 环境中提供的大多数数据结构不太适合持久使用。 数组有`slice`和`concat`方法,可以让我们轻松创建新的数组而不会损坏旧数组。 但是`Set`没有添加或删除项目并创建新集合的方法。 358 | 359 | 编写一个新的类`PGroup`,类似于第六章中的`Group`类,它存储一组值。 像`Group`一样,它具有`add`,`delete`和`has`方法。 360 | 361 | 然而,它的`add`方法应该返回一个新的`PGroup`实例,并添加给定的成员,并保持旧的不变。 与之类似,`delete`创建一个没有给定成员的新实例。 362 | 363 | 该类应该适用于任何类型的值,而不仅仅是字符串。 当与大量值一起使用时,它不一定非常高效。 364 | 365 | 构造函数不应该是类接口的一部分(尽管你绝对会打算在内部使用它)。 相反,有一个空的实例`PGroup.empty`,可用作起始值。 366 | 367 | 为什么只需要一个`PGroup.empty`值,而不是每次都创建一个新的空分组? 368 | 369 | ```js 370 | class PGroup { 371 | // Your code here 372 | } 373 | 374 | let a = PGroup.empty.add("a"); 375 | let ab = a.add("b"); 376 | let b = ab.delete("a"); 377 | 378 | console.log(b.has("b")); 379 | // → true 380 | console.log(a.has("b")); 381 | // → false 382 | console.log(b.has("a")); 383 | // → false 384 | ``` 385 | -------------------------------------------------------------------------------- /8.md: -------------------------------------------------------------------------------- 1 | ## 八、Bug 和错误 2 | 3 | > 原文:[Bugs and Errors](http://eloquentjavascript.net/08_error.html) 4 | > 5 | > 译者:[飞龙](https://github.com/wizardforcel) 6 | > 7 | > 协议:[CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/) 8 | > 9 | > 自豪地采用[谷歌翻译](https://translate.google.cn/) 10 | > 11 | > 部分参考了[《JavaScript 编程精解(第 2 版)》](https://book.douban.com/subject/26707144/) 12 | 13 | > 调试的难度是开始编写代码的两倍。 因此,如果你尽可能巧妙地编写代码,那么根据定义,你的智慧不足以进行调试。 14 | > 15 | > Brian Kernighan 和 P.J. Plauger,《The Elements of Programming Style》 16 | 17 | ![](img/8-0.jpg) 18 | 19 | 计算机程序中的缺陷通常称为 bug。 它让程序员觉得很好,将它们想象成小事,只是碰巧进入我们的作品。 实际上,当然,我们自己把它们放在了那里。 20 | 21 | 如果一个程序是思想的结晶,你可以粗略地将错误分为因为思想混乱引起的错误,以及思想转换为代码时引入的错误。 前者通常比后者更难诊断和修复。 22 | 23 | ## 语言 24 | 25 | 计算机能够自动地向我们指出许多错误,如果它足够了解我们正在尝试做什么。 但是这里 JavaScript 的宽松是一个障碍。 它的绑定和属性概念很模糊,在实际运行程序之前很少会发现拼写错误。 即使这样,它也允许你做一些不会报错的无意义的事情,比如计算`true *'monkey'`。 26 | 27 | JavaScript 有一些报错的事情。 编写不符合语言语法的程序会立即使计算机报错。 其他的东西,比如调用不是函数的东西,或者在未定义的值上查找属性,会导致在程序尝试执行操作时报告错误。 28 | 29 | 不过,JavaScript 在处理无意义的计算时,会仅仅返回`NaN`(表示不是数字)或`undefined`这样的结果。程序会认为其执行的代码毫无问题并顺利运行下去,要等到随后的运行过程中才会出现问题,而此时已经有许多函数使用了这个无意义的值。程序执行中也可能不会遇到任何错误,只会产生错误的程序输出。找出这类错误的源头是非常困难的。 30 | 31 | 我们将查找程序中的错误或者 bug 的过程称为调试(debug)。 32 | 33 | ## 严格模式 34 | 35 | 当启用了严格模式(strict mode)后,JavaScript 就会在执行代码时变得更为严格。我们只需在文件或函数体顶部放置字符串`"use strict"`就可以启用严格模式了。下面是示例代码: 36 | 37 | ```js 38 | function canYouSpotTheProblem() { 39 | "use strict"; 40 | for (counter = 0; counter < 10; counter++) { 41 | console.log("Happy happy"); 42 | } 43 | } 44 | 45 | canYouSpotTheProblem(); 46 | // → ReferenceError: counter is not defined 47 | ``` 48 | 49 | 通常,当你忘记在绑定前面放置`let`时,就像在示例中的`counter`一样,JavaScript 静静地创建一个全局绑定并使用它。 在严格模式下,它会报告错误。 这非常有帮助。 但是,应该指出的是,当绑定已经作为全局绑定存在时,这是行不通的。 在这种情况下,循环仍然会悄悄地覆盖绑定的值。 50 | 51 | 严格模式中的另一个变化是,在未被作为方法而调用的函数中,`this`绑定持有值`undefined`。 当在严格模式之外进行这样的调用时,`this`引用全局作用域对象,该对象的属性是全局绑定。 因此,如果你在严格模式下不小心错误地调用方法或构造器,JavaScript 会在尝试从`this`读取某些内容时产生错误,而不是愉快地写入全局作用域。 52 | 53 | 例如,考虑下面的代码,该代码不带`new`关键字调用构造器,以便其`this`不会引用新构造的对象: 54 | 55 | ```js 56 | function Person(name) { this.name = name; } 57 | let ferdinand = Person("Ferdinand"); // oops 58 | console.log(name); 59 | // → Ferdinand 60 | ``` 61 | 62 | 虽然我们错误调用了`Person`,代码也可以执行成功,但会返回一个未定义值,并创建名为`name`的全局绑定。而在严格模式中,结果就不同了。 63 | 64 | ```js 65 | "use strict"; 66 | function Person(name) { this.name = name; } 67 | let ferdinand = Person("Ferdinand"); 68 | // → TypeError: Cannot set property 'name' of undefined 69 | ``` 70 | 71 | JavaScript 会立即告知我们代码中包含错误。这种特性十分有用。 72 | 73 | 幸运的是,使用`class`符号创建的构造器,如果在不使用`new`来调用,则始终会报错,即使在非严格模式下也不会产生问题。 74 | 75 | 严格模式做了更多的事情。 它不允许使用同一名称给函数赋多个参数,并且完全删除某些有问题的语言特性(例如`with`语句,这是错误的,本书不会进一步讨论)。 76 | 77 | 简而言之,在程序顶部放置`"use strict"`很少会有问题,并且可能会帮助你发现问题。 78 | 79 | ## 类型 80 | 81 | 有些语言甚至在运行程序之前想要知道,所有绑定和表达式的类型。 当类型以不一致的方式使用时,他们会马上告诉你。 JavaScript 只在实际运行程序时考虑类型,即使经常尝试将值隐式转换为它预期的类型,所以它没有多大帮助。 82 | 83 | 尽管如此,类型为讨论程序提供了一个有用的框架。 许多错误来自于值的类型的困惑,它们进入或来自一个函数。 如果你把这些信息写下来,你不太可能会感到困惑。 84 | 85 | 你可以在上一章的`goalOrientedRobot`函数上面,添加一个像这样的注释来描述它的类型。 86 | 87 | ```js 88 | // (WorldState, Array) → {direction: string, memory: Array} 89 | function goalOrientedRobot(state, memory) { 90 | // ... 91 | } 92 | ``` 93 | 94 | 有许多不同的约定,用于标注 JavaScript 程序的类型。 95 | 96 | 关于类型的一点是,他们需要引入自己的复杂性,以便能够描述足够有用的代码。 你认为从数组中返回一个随机元素的`randomPick`函数的类型是什么? 你需要引入一个绑定类型`T`,它可以代表任何类型,这样你就可以给予`randomPick`一个像`([T])->T`的类型(从`T`到`T`的数组的函数)。 97 | 98 | 当程序的类型已知时,计算机可以为你检查它们,在程序运行之前指出错误。 有几种 JavaScript 语言为语言添加类型并检查它们。 最流行的称为 [TypeScript](https://www.typescriptlang.org/)。 如果你有兴趣为你的程序添加更多的严谨性,我建议你尝试一下。 99 | 100 | 在本书中,我们将继续使用原始的,危险的,非类型化的 JavaScript 代码。 101 | 102 | ## 测试 103 | 104 | 如果语言不会帮助我们发现错误,我们将不得不努力找到它们:通过运行程序并查看它是否正确执行。 105 | 106 | 一次又一次地手动操作,是一个非常糟糕的主意。 这不仅令人讨厌,而且也往往是无效的,因为每次改变时都需要花费太多时间来详尽地测试所有内容。 107 | 108 | 计算机擅长重复性任务,测试是理想的重复性任务。 自动化测试是编写测试另一个程序的程序的过程。 编写测试比手工测试有更多的工作,但是一旦你完成了它,你就会获得一种超能力:它只需要几秒钟就可以验证,你的程序在你编写为其测试的所有情况下都能正常运行。 当你破坏某些东西时,你会立即注意到,而不是在稍后的时间里随机地碰到它。 109 | 110 | 测试通常采用小标签程序的形式来验证代码的某些方面。 例如,一组(标准的,可能已经由其他人测试过)`toUpperCase`方法的测试可能如下: 111 | 112 | ```js 113 | function test(label, body) { 114 | if (!body()) console.log(`Failed: ${label}`); 115 | } 116 | 117 | test("convert Latin text to uppercase", () => { 118 | return "hello".toUpperCase() == "HELLO"; 119 | }); 120 | test("convert Greek text to uppercase", () => { 121 | return "Χαίρετε".toUpperCase() == "ΧΑΊΡΕΤΕ"; 122 | }); 123 | test("don't convert case-less characters", () => { 124 | return "مرحبا".toUpperCase() == "مرحبا"; 125 | }); 126 | ``` 127 | 128 | 像这样写测试往往会产生很多重复,笨拙的代码。 幸运的是,有些软件通过提供适合于表达测试的语言(以函数和方法的形式),并在测试失败时输出丰富的信息来帮助你构建和运行测试集合(测试套件,test suite)。 这些通常被称为测试运行器(test runner)。 129 | 130 | 一些代码比其他代码更容易测试。 通常,代码与外部交互的对象越多,建立用于测试它的上下文就越困难。 上一章中显示的编程风格,使用自包含的持久值而不是更改对象,通常很容易测试。 131 | 132 | ## 调试 133 | 134 | 当程序的运行结果不符合预期或在运行过程中产生错误时,你就会注意到程序出现问题了,下一步就是要推断问题出在什么地方。 135 | 136 | 有时错误很明显。错误消息会指出错误出现在程序的哪一行,只要稍加阅读错误描述及出错的那行代码,你一般就知道如何修正错误了。 137 | 138 | 但不总是这样。 有时触发问题的行,只是第一个地方,它以无效方式使用其他地方产生的奇怪的值。 如果你在前几章中已经解决了练习,你可能已经遇到过这种情况。 139 | 140 | 下面的示例代码尝试将一个整数转换成给定进制表示的字符串(十进制、二进制等),其原理是:不断循环取出最后一位数字,并将其除以基数(将最后一位数从数字中除去)。但该程序目前的输出表明程序中是存在bug的。 141 | 142 | ```js 143 | function numberToString(n, base = 10) { 144 | let result = "", sign = ""; 145 | if (n < 0) { 146 | sign = "-"; 147 | n = -n; 148 | } 149 | do { 150 | result = String(n % base) + result; 151 | n /= base; 152 | } while (n > 0); 153 | return sign + result; 154 | } 155 | console.log(numberToString(13, 10)); 156 | // → 1.5e-3231.3e-3221.3e-3211.3e-3201.3e-3191.3e-3181.3… 157 | ``` 158 | 159 | 你可能已经发现程序运行结果不对了,不过先暂时装作不知道。我们知道程序运行出了问题,试图找出其原因。 160 | 161 | 这是一个地方,你必须抵制随机更改代码来查看它是否变得更好的冲动。 相反,要思考。 分析正在发生的事情,并提出为什么可能发生的理论。 然后,再做一些观察来检验这个理论 - 或者,如果你还没有理论,可以进一步观察来帮助你想出一个理论。 162 | 163 | 有目的地在程序中使用`console.log`来查看程序当前的运行状态,是一种不错的获取额外信息的方法。在本例中,我们希望`n`的值依次变为 13,1,然后是 0。让我们先在循环起始处输出`n`的值。 164 | 165 | ``` 166 | 13 167 | 1.3 168 | 0.13 169 | 0.013 170 | … 171 | 1.5e-323 172 | ``` 173 | 174 | 没错。13 除以 10 并不会产生整数。我们不应该使用`n/=base`,而应该使用`n=Math.floor(n/base)`,使数字“右移”,这才是我们实际想要的结果。 175 | 176 | 使用`console.log`来查看程序行为的替代方法,是使用浏览器的调试器(debugger)功能。 浏览器可以在代码的特定行上设置断点(breakpoint)。 当程序执行到带有断点的行时,它会暂停,并且你可以检查该点的绑定值。 我不会详细讨论,因为调试器在不同浏览器上有所不同,但请查看浏览器的开发人员工具或在 Web 上搜索来获取更多信息。 177 | 178 | 设置断点的另一种方法,是在程序中包含一个`debugger`语句(仅由该关键字组成)。 如果你的浏览器的开发人员工具是激活的,则只要程序达到这个语句,程序就会暂停。 179 | 180 | ## 错误传播 181 | 182 | 不幸的是,程序员不可能避免所有问题。 如果你的程序以任何方式与外部世界进行通信,则可能会导致输入格式错误,工作负荷过重或网络故障。 183 | 184 | 如果你只为自己编程,那么你就可以忽略这些问题直到它们发生。 但是如果你创建了一些将被其他人使用的东西,你通常希望程序比只是崩溃做得更好。 有时候,正确的做法是不择手段地继续运行。 在其他情况下,最好向用户报告出了什么问题然后放弃。 但无论在哪种情况下,该程序都必须积极采取措施来回应问题。 185 | 186 | 假设你有一个函数`promptInteger`,要求用户输入一个整数并返回它。 如果用户输入`"orange"`,它应该返回什么? 187 | 188 | 一种办法是返回一个特殊值,通常会使用`null`,`undefined`或 -1。 189 | 190 | ```js 191 | function promptNumber(question) { 192 | let result = Number(prompt(question, "")); 193 | if (Number.isNaN(result)) return null; 194 | else return result; 195 | } 196 | 197 | console.log(promptNumber("How many trees do you see?")); 198 | ``` 199 | 200 | 现在,调用`promptNumber`的任何代码都必须检查是否实际读取了数字,否则必须以某种方式恢复 - 也许再次询问或填充默认值。 或者它可能会再次向它的调用者返回一个特殊值,表示它未能完成所要求的操作。 201 | 202 | 在很多情况下,当错误很常见并且调用者应该明确地考虑它们时,返回特殊值是表示错误的好方法。 但它确实有其不利之处。 首先,如果函数已经可能返回每一种可能的值呢? 在这样的函数中,你必须做一些事情,比如将结果包装在一个对象中,以便能够区分成功与失败。 203 | 204 | ```js 205 | function lastElement(array) { 206 | if (array.length == 0) { 207 | return {failed: true}; 208 | } else { 209 | return {element: array[array.length - 1]}; 210 | } 211 | } 212 | ``` 213 | 214 | 返回特殊值的第二个问题是它可能产生非常笨拙的代码。 如果一段代码调用`promptNumber` 10 次,则必须检查是否返回`null` 10 次。 如果它对`null`的回应是简单地返回`null`本身,函数的调用者将不得不去检查它,以此类推。 215 | 216 | ## 异常 217 | 218 | 当函数无法正常工作时,我们只希望停止当前任务,并立即跳转到负责处理问题的位置。这就是异常处理的功能。 219 | 220 | 异常是一种当代码执行中遇到问题时,可以触发(或抛出)异常的机制,异常只是一个普通的值。触发异常类似于从函数中强制返回:异常不只跳出到当前函数中,还会跳出函数调用方,直到当前执行流初次调用函数的位置。这种方式被称为“堆栈展开(Unwinding the Stack)”。你可能还记得我们在第3章中介绍的函数调用栈,异常会减小堆栈的尺寸,并丢弃所有在缩减程序栈尺寸过程中遇到的函数调用上下文。 221 | 222 | 如果异常总是会将堆栈尺寸缩减到栈底,那么异常也就毫无用处了。它只不过是换了一种方式来彻底破坏你的程序罢了。异常真正强大的地方在于你可以在堆栈上设置一个“障碍物”,当异常缩减堆栈到达这个位置时会被捕获。一旦发现异常,你可以使用它来解决问题,然后继续运行该程序。 223 | 224 | ```js 225 | function promptDirection(question) { 226 | let result = prompt(question, ""); 227 | if (result.toLowerCase() == "left") return "L"; 228 | if (result.toLowerCase() == "right") return "R"; 229 | throw new Error("Invalid direction: " + result); 230 | } 231 | 232 | function look() { 233 | if (promptDirection("Which way?") == "L") { 234 | return "a house"; 235 | } else { 236 | return "two angry bears"; 237 | } 238 | } 239 | 240 | try { 241 | console.log("You see", look()); 242 | } catch (error) { 243 | console.log("Something went wrong: " + error); 244 | } 245 | ``` 246 | 247 | `throw`关键字用于引发异常。 异常的捕获通过将一段代码包装在一个`try`块中,后跟关键字`catch`来完成。 当`try`块中的代码引发异常时,将求值`catch`块,并将括号中的名称绑定到异常值。 在`catch`块结束之后,或者`try`块结束并且没有问题时,程序在整个`try / catch`语句的下面继续执行。 248 | 249 | 在本例中,我们使用`Error`构造器来创建异常值。这是一个标准的 JavaScript 构造器,用于创建一个对象,包含`message`属性。在多数 JavaScript 环境中,构造器实例也会收集异常创建时的调用栈信息,即堆栈跟踪信息(Stack Trace)。该信息存储在`stack`属性中,对于调用问题有很大的帮助,我们可以从堆栈跟踪信息中得知问题发生的精确位置,即问题具体出现在哪个函数中,以及执行失败为止调用的其他函数链。 250 | 251 | 需要注意的是现在`look`函数可以完全忽略`promptDirection`出错的可能性。这就是使用异常的优势:只有在错误触发且必须处理的位置才需要错误处理代码。其间的函数可以忽略异常处理。 252 | 253 | 嗯,我们要讲解的理论知识差不多就这些了。 254 | 255 | ## 异常后清理 256 | 257 | 异常的效果是另一种控制流。 每个可能导致异常的操作(几乎每个函数调用和属性访问)都可能导致控制流突然离开你的代码。 258 | 259 | 这意味着当代码有几个副作用时,即使它的“常规”控制流看起来像它们总是会发生,但异常可能会阻止其中一些发生。 260 | 261 | 这是一些非常糟糕的银行代码。 262 | 263 | ```js 264 | const accounts = { 265 | a: 100, 266 | b: 0, 267 | c: 20 268 | }; 269 | 270 | function getAccount() { 271 | let accountName = prompt("Enter an account name"); 272 | if (!accounts.hasOwnProperty(accountName)) { 273 | throw new Error(`No such account: ${accountName}`); 274 | } 275 | return accountName; 276 | } 277 | 278 | function transfer(from, amount) { 279 | if (accounts[from] < amount) return; 280 | accounts[from] -= amount; 281 | accounts[getAccount()] += amount; 282 | } 283 | ``` 284 | 285 | `transfer`函数将一笔钱从一个给定的账户转移到另一个账户,在此过程中询问另一个账户的名称。 如果给定一个无效的帐户名称,`getAccount`将引发异常。 286 | 287 | 但是`transfer`首先从帐户中删除资金,之后调用`getAccount`,之后将其添加到另一个帐户。 如果它在那个时候由异常中断,它就会让钱消失。 288 | 289 | 这段代码本来可以更智能一些,例如在开始转移资金之前调用`getAccount`。 但这样的问题往往以更微妙的方式出现。 即使是那些看起来不像是会抛出异常的函数,在特殊情况下,或者当他们包含程序员的错误时,也可能会这样。 290 | 291 | 解决这个问题的一个方法是使用更少的副作用。 同样,计算新值而不是改变现有数据的编程风格有所帮助。 如果一段代码在创建新值时停止运行,没有人会看到这个完成一半的值,并且没有问题。 292 | 293 | 但这并不总是实际的。 所以`try`语句具有另一个特性。 他们可能会跟着一个`finally`块,而不是`catch`块,也不是在它后面。 `finally`块会说“不管发生什么事,在尝试运行`try`块中的代码后,一定会运行这个代码。” 294 | 295 | ```js 296 | function transfer(from, amount) { 297 | if (accounts[from] < amount) return; 298 | let progress = 0; 299 | try { 300 | accounts[from] -= amount; 301 | progress = 1; 302 | accounts[getAccount()] += amount; 303 | progress = 2; 304 | } finally { 305 | if (progress == 1) { 306 | accounts[from] += amount; 307 | } 308 | } 309 | } 310 | ``` 311 | 312 | 这个版本的函数跟踪其进度,如果它在离开时注意到,它中止在创建不一致的程序状态的位置,则修复它造成的损害。 313 | 314 | 请注意,即使`finally`代码在异常退出`try`块时运行,它也不会影响异常。`finally`块运行后,堆栈继续展开。 315 | 316 | 即使异常出现在意外的地方,编写可靠运行的程序也非常困难。 很多人根本就不关心,而且由于异常通常针对异常情况而保留,因此问题可能很少发生,甚至从未被发现。 这是一件好事还是一件糟糕的事情,取决于软件执行失败时会造成多大的损害。 317 | 318 | ## 选择性捕获 319 | 320 | 当程序出现异常且异常未被捕获时,异常就会直接回退到栈顶,并由 JavaScript 环境来处理。其处理方式会根据环境的不同而不同。在浏览器中,错误描述通常会写入 JavaScript 控制台中(可以使用浏览器工具或开发者菜单来访问控制台)。我们将在第 20 章中讨论的,无浏览器的 JavaScript 环境 Node.js 对数据损坏更加谨慎。 当发生未处理的异常时,它会中止整个过程。 321 | 322 | 对于程序员的错误,让错误通行通常是最好的。 未处理的异常是表示糟糕的程序的合理方式,而在现代浏览器上,JavaScript 控制台为你提供了一些信息,有关在发生问题时堆栈上调用了哪些函数的。 323 | 324 | 对于在日常使用中发生的预期问题,因未处理的异常而崩溃是一种糟糕的策略。 325 | 326 | 语言的非法使用方式,比如引用一个不存在的绑定,在null中查询属性,或调用的对象不是函数最终都会引发异常。你可以像自己的异常一样捕获这些异常。 327 | 328 | 进入`catch`语句块时,我们只知道`try`体中引发了异常,但不知道引发了哪一类或哪一个异常。 329 | 330 | JavaScript(很明显的疏漏)并未对选择性捕获异常提供良好的支持,要不捕获所有异常,要不什么都不捕获。这让你很容易假设,你得到的异常就是你在写`catch`时所考虑的异常。 331 | 332 | 但它也可能不是。 可能会违反其他假设,或者你可能引入了导致异常的 bug。 这是一个例子,它尝试持续调用`promptDirection`,直到它得到一个有效的答案: 333 | 334 | ```js 335 | for (;;) { 336 | try { 337 | let dir = promtDirection("Where?"); // ← typo! 338 | console.log("You chose ", dir); 339 | break; 340 | } catch (e) { 341 | console.log("Not a valid direction. Try again."); 342 | } 343 | } 344 | ``` 345 | 346 | 我们可以使用`for (;;)`循环体来创建一个无限循环,其自身永远不会停止运行。我们在用户给出有效的方向之后会跳出循环。但我们拼写错了`promptDirection`,因此会引发一个“未定义值”错误。由于`catch`块完全忽略了异常值,假定其知道问题所在,错将绑定错误信息当成错误输入。这样不仅会引发无限循环,而且会掩盖掉真正的错误消息——绑定名拼写错误。 347 | 348 | 一般而言,只有将抛出的异常重定位到其他地方进行处理时,我们才会捕获所有异常。比如说通过网络传输通知其他系统当前应用程序的崩溃信息。即便如此,我们也要注意编写的代码是否会将错误信息掩盖起来。 349 | 350 | 因此,我们转而会去捕获那些特殊类型的异常。我们可以在`catch`代码块中判断捕获到的异常是否就是我们期望处理的异常,如果不是则将其重新抛出。那么我们该如何辨别抛出异常的类型呢? 351 | 352 | 我们可以将它的`message`属性与我们所期望的错误信息进行比较。 但是,这是一种不稳定的编写代码的方式 - 我们将使用供人类使用的信息来做出程序化决策。 只要有人更改(或翻译)该消息,代码就会停止工作。 353 | 354 | 我们不如定义一个新的错误类型,并使用`instanceof`来识别异常。 355 | 356 | ```js 357 | class InputError extends Error {} 358 | 359 | function promptDirection(question) { 360 | let result = prompt(question); 361 | if (result.toLowerCase() == "left") return "L"; 362 | if (result.toLowerCase() == "right") return "R"; 363 | throw new InputError("Invalid direction: " + result); 364 | } 365 | ``` 366 | 367 | 新的错误类扩展了`Error`。 它没有定义它自己的构造器,这意味着它继承了`Error`构造器,它需要一个字符串消息作为参数。 事实上,它根本没有定义任何东西 - 这个类是空的。 `InputError`对象的行为与`Error`对象相似,只是它们的类不同,我们可以通过类来识别它们。 368 | 369 | 现在循环可以更仔细地捕捉它们。 370 | 371 | ```js 372 | for (;;) { 373 | try { 374 | let dir = promptDirection("Where?"); 375 | console.log("You chose ", dir); 376 | break; 377 | } catch (e) { 378 | if (e instanceof InputError) { 379 | console.log("Not a valid direction. Try again."); 380 | } else { 381 | throw e; 382 | } 383 | } 384 | } 385 | ``` 386 | 387 | 这里的`catch`代码只会捕获`InputError`类型的异常,而其他类型的异常则不会在这里进行处理。如果又输入了不正确的值,那么系统会向用户准确报告错误——“绑定未定义”。 388 | 389 | ## 断言 390 | 391 | 断言(assertions)是程序内部的检查,用于验证某个东西是它应该是的方式。 它们并不是用于处理正常操作中可能出现的情况,而是发现程序员的错误。 392 | 393 | 例如,如果`firstElement`被描述为一个函数,永远不会在空数组上调用,我们可以这样写: 394 | 395 | ```js 396 | function firstElement(array) { 397 | if (array.length == 0) { 398 | throw new Error("firstElement called with []"); 399 | } 400 | return array[0]; 401 | } 402 | ``` 403 | 404 | 现在,它不会默默地返回未定义值(当你读取一个不存在的数组属性的时候),而是在你滥用它时立即干掉你的程序。 这使得这种错误不太可能被忽视,并且当它们发生时更容易找到它们的原因。 405 | 406 | 我不建议尝试为每种可能的不良输入编写断言。 这将是很多工作,并会产生非常杂乱的代码。 你会希望为很容易犯(或者你发现自己做过)的错误保留他们。 407 | 408 | ## 本章小结 409 | 410 | 错误和无效的输入十分常见。编程的一个重要部分是发现,诊断和修复错误。 如果你拥有自动化测试套件或向程序添加断言,则问题会变得更容易被注意。 411 | 412 | 我们常常需要使用优雅的方式来处理程序可控范围外的问题。如果问题可以就地解决,那么返回一个特殊的值来跟踪错误就是一个不错的解决方案。或者,异常也可能是可行的。 413 | 414 | 抛出异常会引发堆栈展开,直到遇到下一个封闭的`try/catch`块,或堆栈底部为止。`catch`块捕获异常后,会将异常值赋予`catch`块,`catch`块中应该验证异常是否是实际希望处理的异常,然后进行处理。为了有助于解决由于异常引起的不可预测的执行流,可以使用`finally`块来确保执行`try`块之后的代码。 415 | 416 | ## 习题 417 | 418 | ### 重试 419 | 420 | 假设有一个函数`primitiveMultiply`,在 20% 的情况下将两个数相乘,在另外 80% 的情况下会触发`MultiplicatorUnitFailure`类型的异常。编写一个函数,调用这个容易出错的函数,不断尝试直到调用成功并返回结果为止。 421 | 422 | 确保只处理你期望的异常。 423 | 424 | ```js 425 | class MultiplicatorUnitFailure extends Error {} 426 | 427 | function primitiveMultiply(a, b) { 428 | if (Math.random() < 0.2) { 429 | return a * b; 430 | } else { 431 | throw new MultiplicatorUnitFailure(); 432 | } 433 | } 434 | 435 | function reliableMultiply(a, b) { 436 | // Your code here. 437 | } 438 | 439 | console.log(reliableMultiply(8, 8)); 440 | // → 64 441 | ``` 442 | 443 | ### 上锁的箱子 444 | 445 | 考虑以下这个编写好的对象: 446 | 447 | ```js 448 | const box = { 449 | locked: true, 450 | unlock() { this.locked = false; }, 451 | lock() { this.locked = true; }, 452 | _content: [], 453 | get content() { 454 | if (this.locked) throw new Error("Locked!"); 455 | return this._content; 456 | } 457 | }; 458 | ``` 459 | 460 | 这是一个带锁的箱子。其中有一个数组,但只有在箱子被解锁时,才可以访问数组。不允许直接访问`_content`属性。 461 | 462 | 编写一个名为`withBoxUnlocked`的函数,接受一个函数类型的参数,其作用是解锁箱子,执行该函数,无论是正常返回还是抛出异常,在`withBoxUnlocked`函数返回前都必须锁上箱子。 463 | 464 | ```js 465 | const box = { 466 | locked: true, 467 | unlock() { this.locked = false; }, 468 | lock() { this.locked = true; }, 469 | _content: [], 470 | get content() { 471 | if (this.locked) throw new Error("Locked!"); 472 | return this._content; 473 | } 474 | }; 475 | 476 | function withBoxUnlocked(body) { 477 | // Your code here. 478 | } 479 | 480 | withBoxUnlocked(function() { 481 | box.content.push("gold piece"); 482 | }); 483 | 484 | try { 485 | withBoxUnlocked(function() { 486 | throw new Error("Pirates on the horizon! Abort!"); 487 | }); 488 | } catch (e) { 489 | console.log("Error raised:", e); 490 | } 491 | console.log(box.locked); 492 | // → true 493 | ``` 494 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JavaScript 编程精解 中文第三版 2 | 3 | 原书:[Eloquent JavaScript 3rd edition](http://eloquentjavascript.net/) 4 | 5 | 译者:[飞龙](https://github.com/wizardforcel) 6 | 7 | 自豪地采用[谷歌翻译](https://translate.google.cn/) 8 | 9 | 部分参考了[《JavaScript 编程精解(第 2 版)》](https://book.douban.com/subject/26707144/) 10 | 11 | + [在线阅读](https://www.gitbook.com/book/wizardforcel/eloquent-js-3e/details) 12 | + [PDF格式](https://www.gitbook.com/download/pdf/book/wizardforcel/eloquent-js-3e) 13 | + [EPUB格式](https://www.gitbook.com/download/epub/book/wizardforcel/eloquent-js-3e) 14 | + [MOBI格式](https://www.gitbook.com/download/mobi/book/wizardforcel/eloquent-js-3e) 15 | + [代码仓库](https://github.com/wizardforcel/eloquent-js-3e-zh) 16 | 17 | 18 | ## 赞助我 19 | 20 | ![](img/qr_alipay.png) 21 | 22 | ## 协议 23 | 24 | [CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/) 25 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | + [JavaScript 编程精解 中文第三版](README.md) 2 | + [零、前言](0.md) 3 | + [一、值,类型和运算符](1.md) 4 | + [二、程序结构](2.md) 5 | + [三、函数](3.md) 6 | + [四、数据结构:对象和数组](4.md) 7 | + [五、高阶函数](5.md) 8 | + [六、对象的秘密](6.md) 9 | + [七、项目:机器人](7.md) 10 | + [八、Bug 和错误](8.md) 11 | + [九、正则表达式](9.md) 12 | + [十、模块](10.md) 13 | + [十一、异步编程](11.md) 14 | + [十二、项目:编程语言](12.md) 15 | + [十三、浏览器中的 JavaScript](13.md) 16 | + [十四、文档对象模型](14.md) 17 | + [十五、处理事件](15.md) 18 | + [十六、项目:平台游戏](16.md) 19 | + [十七、在画布上绘图](17.md) 20 | + [十八、HTTP 和表单](18.md) 21 | + [十九、项目:像素艺术编辑器](19.md) 22 | + [二十、Node.js](20.md) 23 | + [二十一、项目:技能分享网站](21.md) -------------------------------------------------------------------------------- /cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wizardforcel/eloquent-js-3e-zh/097ef4522075bcdf3e167e38cce1b7fae034579c/cover.jpg -------------------------------------------------------------------------------- /img/0-0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wizardforcel/eloquent-js-3e-zh/097ef4522075bcdf3e167e38cce1b7fae034579c/img/0-0.jpg -------------------------------------------------------------------------------- /img/1-0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wizardforcel/eloquent-js-3e-zh/097ef4522075bcdf3e167e38cce1b7fae034579c/img/1-0.jpg -------------------------------------------------------------------------------- /img/10-0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wizardforcel/eloquent-js-3e-zh/097ef4522075bcdf3e167e38cce1b7fae034579c/img/10-0.jpg -------------------------------------------------------------------------------- /img/11-1.svg: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 | <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="445" height="210" viewBox="-2 -2 445 210"><style type="text/css"> 3 | @font-face { 4 | font-family: 'PT Mono'; 5 | font-style: normal; 6 | font-weight: 400; 7 | src: local('PT Mono'), local('PTMono-Regular'), url(http://themes.googleusercontent.com/static/fonts/ptmono/v1/jmle3kzCPnW8O7_gZGRDlQ.woff) format('woff'); 8 | } 9 | </style> 10 | <g><text x="0" y="14" font-family="PT Mono">synchronous, single thread of control</text><path d="M 0.5 40.5 L 20.5 40.5" stroke="#44f" stroke-width="5"></path><path d="M 20 40 L 180 40" stroke="#c22" stroke-width="2"></path><ellipse cx="20" cy="40" rx="5" ry="5" fill="black" width="10" height="10"></ellipse><path d="M 180.5 40.5 L 200.5 40.5" stroke="#44f" stroke-width="5"></path><path d="M 200 40 L 400 40" stroke="#c22" stroke-width="2"></path><ellipse cx="200" cy="40" rx="5" ry="5" fill="black" width="10" height="10"></ellipse><path d="M 400.5 40.5 L 440.5 40.5" stroke="#44f" stroke-width="5"></path><text x="0" y="71" font-family="PT Mono">synchronous, two threads of control</text><path d="M 0.5 95.5 L 20.5 95.5" stroke="#44f" stroke-width="5"></path><path d="M 20 95 L 180 95" stroke="#c22" stroke-width="2"></path><ellipse cx="20" cy="95" rx="5" ry="5" fill="black" width="10" height="10"></ellipse><path d="M 220.5 95.5 L 260.5 95.5" stroke="#44f" stroke-width="5"></path><path d="M 0.5 120.5 L 20.5 120.5" stroke="#44f" stroke-width="5"></path><path d="M 20 120 L 220 120" stroke="#c22" stroke-width="2"></path><ellipse cx="20" cy="120" rx="5" ry="5" fill="black" width="10" height="10"></ellipse><path d="M 220.5 120.5 L 240.5 120.5" stroke="#44f" stroke-width="5"></path><text x="0" y="151" font-family="PT Mono">asynchronous</text><path d="M 20 175 a 30 30 0 0 0 30 30 l 82 0 a 30 30 0 0 0 30 -30" stroke="#c22" fill="none" stroke-width="2"></path><path d="M 40 175 a 15 15 0 0 0 15 15 l 152 0 a 15 15 0 0 0 15 -15" stroke="#c22" fill="none" stroke-width="2"></path><path d="M 0.5 175.5 L 60.5 175.5" stroke="#44f" stroke-width="5"></path><path d="M 20 175 L 20 175" stroke="#c22" stroke-width="2"></path><ellipse cx="20" cy="175" rx="5" ry="5" fill="black" width="10" height="10"></ellipse><path d="M 40 175 L 40 175" stroke="#c22" stroke-width="2"></path><ellipse cx="40" cy="175" rx="5" ry="5" fill="black" width="10" height="10"></ellipse><path d="M 160.5 175.5 L 180.5 175.5" stroke="#44f" stroke-width="5"></path><path d="M 220.5 175.5 L 260.5 175.5" stroke="#44f" stroke-width="5"></path></g></svg> 11 | -------------------------------------------------------------------------------- /img/11-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wizardforcel/eloquent-js-3e-zh/097ef4522075bcdf3e167e38cce1b7fae034579c/img/11-2.png -------------------------------------------------------------------------------- /img/12-0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wizardforcel/eloquent-js-3e-zh/097ef4522075bcdf3e167e38cce1b7fae034579c/img/12-0.jpg -------------------------------------------------------------------------------- /img/12-1.svg: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 | <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="169" height="240" viewBox="-7 -10 169 240"><style> 3 | @font-face { 4 | font-family: 'PT Mono'; 5 | font-style: normal; 6 | font-weight: 400; 7 | src: local('PT Mono'), local('PTMono-Regular'), url(http://themes.googleusercontent.com/static/fonts/ptmono/v1/jmle3kzCPnW8O7_gZGRDlQ.woff) format('woff'); 8 | } 9 | .ast-text { font-family: "PT Mono"; font-size: 14px; stroke: none; } 10 | </style> 11 | <g><ellipse cx="0" cy="0" rx="5" ry="5" fill="black" width="10" height="10"></ellipse><text x="13" y="4.5" class="ast-text">do</text><ellipse cx="30" cy="20" rx="5" ry="5" fill="black" width="10" height="10"></ellipse><text x="43" y="24.5" class="ast-text">define</text><path d="M 0.5 5.5 L 0.5 10.5 A 10 10 0 0 0 10.5 20.5 L 25.5 20.5" stroke="black" fill="none"></path><path d="M 0 0 L 3 8 L 0 7 L -3 8 Z" stroke="none" fill="rgb(0, 0, 0)" transform="translate(25.5 20.5) rotate(90) scale(1)"></path><ellipse cx="60" cy="40" rx="5" ry="5" fill="black" width="10" height="10"></ellipse><text x="73" y="44.5" class="ast-text">x</text><path d="M 30.5 25.5 L 30.5 30.5 A 10 10 0 0 0 40.5 40.5 L 55.5 40.5" stroke="black" fill="none"></path><path d="M 0 0 L 3 8 L 0 7 L -3 8 Z" stroke="none" fill="rgb(0, 0, 0)" transform="translate(55.5 40.5) rotate(90) scale(1)"></path><ellipse cx="60" cy="60" rx="5" ry="5" fill="black" width="10" height="10"></ellipse><text x="73" y="64.5" class="ast-text">10</text><path d="M 30.5 25.5 L 30.5 50.5 A 10 10 0 0 0 40.5 60.5 L 55.5 60.5" stroke="black" fill="none"></path><path d="M 0 0 L 3 8 L 0 7 L -3 8 Z" stroke="none" fill="rgb(0, 0, 0)" transform="translate(55.5 60.5) rotate(90) scale(1)"></path><ellipse cx="30" cy="80" rx="5" ry="5" fill="black" width="10" height="10"></ellipse><text x="43" y="84.5" class="ast-text">if</text><path d="M 0.5 5.5 L 0.5 70.5 A 10 10 0 0 0 10.5 80.5 L 25.5 80.5" stroke="black" fill="none"></path><path d="M 0 0 L 3 8 L 0 7 L -3 8 Z" stroke="none" fill="rgb(0, 0, 0)" transform="translate(25.5 80.5) rotate(90) scale(1)"></path><ellipse cx="60" cy="100" rx="5" ry="5" fill="black" width="10" height="10"></ellipse><text x="73" y="104.5" class="ast-text">></text><path d="M 30.5 85.5 L 30.5 90.5 A 10 10 0 0 0 40.5 100.5 L 55.5 100.5" stroke="black" fill="none"></path><path d="M 0 0 L 3 8 L 0 7 L -3 8 Z" stroke="none" fill="rgb(0, 0, 0)" transform="translate(55.5 100.5) rotate(90) scale(1)"></path><ellipse cx="90" cy="120" rx="5" ry="5" fill="black" width="10" height="10"></ellipse><text x="103" y="124.5" class="ast-text">x</text><path d="M 60.5 105.5 L 60.5 110.5 A 10 10 0 0 0 70.5 120.5 L 85.5 120.5" stroke="black" fill="none"></path><path d="M 0 0 L 3 8 L 0 7 L -3 8 Z" stroke="none" fill="rgb(0, 0, 0)" transform="translate(85.5 120.5) rotate(90) scale(1)"></path><ellipse cx="90" cy="140" rx="5" ry="5" fill="black" width="10" height="10"></ellipse><text x="103" y="144.5" class="ast-text">5</text><path d="M 60.5 105.5 L 60.5 130.5 A 10 10 0 0 0 70.5 140.5 L 85.5 140.5" stroke="black" fill="none"></path><path d="M 0 0 L 3 8 L 0 7 L -3 8 Z" stroke="none" fill="rgb(0, 0, 0)" transform="translate(85.5 140.5) rotate(90) scale(1)"></path><ellipse cx="60" cy="160" rx="5" ry="5" fill="black" width="10" height="10"></ellipse><text x="73" y="164.5" class="ast-text">print</text><path d="M 30.5 85.5 L 30.5 150.5 A 10 10 0 0 0 40.5 160.5 L 55.5 160.5" stroke="black" fill="none"></path><path d="M 0 0 L 3 8 L 0 7 L -3 8 Z" stroke="none" fill="rgb(0, 0, 0)" transform="translate(55.5 160.5) rotate(90) scale(1)"></path><ellipse cx="90" cy="180" rx="5" ry="5" fill="black" width="10" height="10"></ellipse><text x="103" y="184.5" class="ast-text">"large"</text><path d="M 60.5 165.5 L 60.5 170.5 A 10 10 0 0 0 70.5 180.5 L 85.5 180.5" stroke="black" fill="none"></path><path d="M 0 0 L 3 8 L 0 7 L -3 8 Z" stroke="none" fill="rgb(0, 0, 0)" transform="translate(85.5 180.5) rotate(90) scale(1)"></path><ellipse cx="60" cy="200" rx="5" ry="5" fill="black" width="10" height="10"></ellipse><text x="73" y="204.5" class="ast-text">print</text><path d="M 30.5 85.5 L 30.5 190.5 A 10 10 0 0 0 40.5 200.5 L 55.5 200.5" stroke="black" fill="none"></path><path d="M 0 0 L 3 8 L 0 7 L -3 8 Z" stroke="none" fill="rgb(0, 0, 0)" transform="translate(55.5 200.5) rotate(90) scale(1)"></path><ellipse cx="90" cy="220" rx="5" ry="5" fill="black" width="10" height="10"></ellipse><text x="103" y="224.5" class="ast-text">"small"</text><path d="M 60.5 205.5 L 60.5 210.5 A 10 10 0 0 0 70.5 220.5 L 85.5 220.5" stroke="black" fill="none"></path><path d="M 0 0 L 3 8 L 0 7 L -3 8 Z" stroke="none" fill="rgb(0, 0, 0)" transform="translate(85.5 220.5) rotate(90) scale(1)"></path></g></svg> -------------------------------------------------------------------------------- /img/13-0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wizardforcel/eloquent-js-3e-zh/097ef4522075bcdf3e167e38cce1b7fae034579c/img/13-0.jpg -------------------------------------------------------------------------------- /img/14-0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wizardforcel/eloquent-js-3e-zh/097ef4522075bcdf3e167e38cce1b7fae034579c/img/14-0.jpg -------------------------------------------------------------------------------- /img/14-1.svg: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 | <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="369" height="429" viewBox="-268 -358 369 429"><style type="text/css"> 3 | @font-face { 4 | font-family: 'PT Mono'; 5 | font-style: normal; 6 | font-weight: 400; 7 | src: local('PT Mono'), local('PTMono-Regular'), url(http://themes.googleusercontent.com/static/fonts/ptmono/v1/jmle3kzCPnW8O7_gZGRDlQ.woff) format('woff'); 8 | } 9 | .leaf { 10 | font-family: "Georgia"; 11 | font-size: 15px; 12 | } 13 | .wraptext { 14 | font-family: "PT Mono"; 15 | font-size: 14px; 16 | } 17 | .wrap { 18 | border-radius: 4px; 19 | stroke-width: 1px; stroke: #666; 20 | fill: none; 21 | } 22 | </style> 23 | <g><text x="0" y="16" class="leaf">here</text><text x="0" y="-11" class="wraptext">a</text><rect x="-11.5" y="-28.5" width="57" height="61" class="wrap" rx="4" ry="4"></rect><text x="57" y="16" class="leaf">.</text><text x="-230" y="16" class="leaf">I also wrote a book! Read it</text><text x="-230" y="-19.5" class="wraptext">p</text><rect x="-241.5" y="-37.5" width="316" height="82" class="wrap" rx="4" ry="4"></rect><text x="-230" y="-65.5" class="leaf">Hello, I am Marijn and this is...</text><text x="-230" y="-92.5" class="wraptext">p</text><rect x="-241.5" y="-110.5" width="316" height="61" class="wrap" rx="4" ry="4"></rect><text x="-230" y="-138.5" class="leaf">My home page</text><text x="-230" y="-165.5" class="wraptext">h1</text><rect x="-241.5" y="-183.5" width="316" height="61" class="wrap" rx="4" ry="4"></rect><text x="-241.5" y="-194.5" class="wraptext">body</text><rect x="-253.5" y="-212.5" width="340" height="269" class="wrap" rx="4" ry="4"></rect><text x="-230" y="-252.5" class="leaf">My home page</text><text x="-230" y="-279.5" class="wraptext">title</text><rect x="-241.5" y="-297.5" width="316" height="61" class="wrap" rx="4" ry="4"></rect><text x="-241.5" y="-308.5" class="wraptext">head</text><rect x="-253.5" y="-326.5" width="340" height="102" class="wrap" rx="4" ry="4"></rect><text x="-253.5" y="-337.5" class="wraptext">html</text><rect x="-265.5" y="-355.5" width="364" height="424" class="wrap" rx="4" ry="4"></rect></g></svg> 24 | -------------------------------------------------------------------------------- /img/14-2.svg: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 | <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="439" height="259" viewBox="-2 -2 439 259"><style type="text/css"> 3 | @font-face { 4 | font-family: 'PT Mono'; 5 | font-style: normal; 6 | font-weight: 400; 7 | src: local('PT Mono'), local('PTMono-Regular'), url(http://themes.googleusercontent.com/static/fonts/ptmono/v1/jmle3kzCPnW8O7_gZGRDlQ.woff) format('woff'); 8 | } 9 | .box { 10 | stroke: #666; 11 | fill: url(#boxgrad); 12 | border-radius: 4px; 13 | } 14 | .leafbox { 15 | fill: url(#leafgrad); 16 | } 17 | .boxtext { 18 | font-size: 18px; 19 | font-family: "PT Mono"; 20 | stroke: none; 21 | fill: black; 22 | } 23 | .leafboxtext { font-family: "Georgia"; } 24 | .arrow { stroke: black; } 25 | </style> 26 | <defs> 27 | <linearGradient id="boxgrad" x1="0" x2="0" y1="0" y2="1"> 28 | <stop stop-color="#f8f8f8" offset="0%"></stop> 29 | <stop stop-color="#e8e8f2" offset="100%"></stop> 30 | </linearGradient> 31 | <linearGradient id="leafgrad" x1="0" x2="0" y1="0" y2="1"> 32 | <stop stop-color="#f8f8f8" offset="0%"></stop> 33 | <stop stop-color="#caf0f0" offset="100%"></stop> 34 | </linearGradient> 35 | </defs> 36 | <g><rect x="0.5" y="0.5" width="66" height="30" class="box" rx="4" ry="4"></rect><text x="9.700000000000001" y="23" class="boxtext" fill="black" stroke="none">html</text><rect x="91.5" y="0.5" width="66" height="30" class="box" rx="4" ry="4"></rect><text x="100.7" y="23" class="boxtext" fill="black" stroke="none">head</text><path d="M 66.5 15.5 L 91.5 15.5" class="arrow" fill="none"></path><path d="M 0 0 L 3 8 L 0 7 L -3 8 Z" stroke="none" fill="rgb(0, 0, 0)" transform="translate(91.5 15.5) rotate(90) scale(1)"></path><rect x="182.5" y="0.5" width="78" height="30" class="box" rx="4" ry="4"></rect><text x="191.7" y="23" class="boxtext" fill="black" stroke="none">title</text><path d="M 157.5 15.5 L 182.5 15.5" class="arrow" fill="none"></path><path d="M 0 0 L 3 8 L 0 7 L -3 8 Z" stroke="none" fill="rgb(0, 0, 0)" transform="translate(182.5 15.5) rotate(90) scale(1)"></path><rect x="285.5" y="0.5" width="149" height="29" class="box leafbox" rx="4" ry="4"></rect><text x="294.3" y="22.25" class="boxtext leafboxtext" fill="black" stroke="none">My home page</text><path d="M 260.5 15.5 L 285.5 15.5" class="arrow" fill="none"></path><path d="M 0 0 L 3 8 L 0 7 L -3 8 Z" stroke="none" fill="rgb(0, 0, 0)" transform="translate(285.5 15.5) rotate(90) scale(1)"></path><rect x="91.5" y="45.5" width="66" height="30" class="box" rx="4" ry="4"></rect><text x="100.7" y="68" class="boxtext" fill="black" stroke="none">body</text><path d="M 74.5 15.5 L 74.5 56.5 A 4 4 0 0 0 78.5 60.5 L 91.5 60.5" class="arrow" fill="none"></path><path d="M 0 0 L 3 8 L 0 7 L -3 8 Z" stroke="none" fill="rgb(0, 0, 0)" transform="translate(91.5 60.5) rotate(90) scale(1)"></path><rect x="182.5" y="45.5" width="78" height="30" class="box" rx="4" ry="4"></rect><text x="191.7" y="68" class="boxtext" width="78" fill="black" stroke="none">h1</text><path d="M 157.5 60.5 L 182.5 60.5" class="arrow" fill="none"></path><path d="M 0 0 L 3 8 L 0 7 L -3 8 Z" stroke="none" fill="rgb(0, 0, 0)" transform="translate(182.5 60.5) rotate(90) scale(1)"></path><rect x="285.5" y="45.5" width="149" height="29" class="box leafbox" rx="4" ry="4"></rect><text x="294.3" y="67.25" class="boxtext leafboxtext" fill="black" stroke="none">My home page</text><path d="M 260.5 60.5 L 285.5 60.5" class="arrow" fill="none"></path><path d="M 0 0 L 3 8 L 0 7 L -3 8 Z" stroke="none" fill="rgb(0, 0, 0)" transform="translate(285.5 60.5) rotate(90) scale(1)"></path><rect x="182.5" y="90.5" width="78" height="30" class="box" rx="4" ry="4"></rect><text x="191.7" y="113" class="boxtext" width="78" fill="black" stroke="none">p</text><path d="M 165.5 60.5 L 165.5 101.5 A 4 4 0 0 0 169.5 105.5 L 182.5 105.5" class="arrow" fill="none"></path><path d="M 0 0 L 3 8 L 0 7 L -3 8 Z" stroke="none" fill="rgb(0, 0, 0)" transform="translate(182.5 105.5) rotate(90) scale(1)"></path><rect x="285.5" y="90.5" width="149" height="29" class="box leafbox" rx="4" ry="4"></rect><text x="294.3" y="112.25" class="boxtext leafboxtext" width="149" fill="black" stroke="none">Hello! I am...</text><path d="M 260.5 105.5 L 285.5 105.5" class="arrow" fill="none"></path><path d="M 0 0 L 3 8 L 0 7 L -3 8 Z" stroke="none" fill="rgb(0, 0, 0)" transform="translate(285.5 105.5) rotate(90) scale(1)"></path><rect x="182.5" y="135.5" width="78" height="30" class="box" rx="4" ry="4"></rect><text x="191.7" y="158" class="boxtext" width="78" fill="black" stroke="none">p</text><path d="M 165.5 60.5 L 165.5 146.5 A 4 4 0 0 0 169.5 150.5 L 182.5 150.5" class="arrow" fill="none"></path><path d="M 0 0 L 3 8 L 0 7 L -3 8 Z" stroke="none" fill="rgb(0, 0, 0)" transform="translate(182.5 150.5) rotate(90) scale(1)"></path><rect x="285.5" y="135.5" width="149" height="29" class="box leafbox" rx="4" ry="4"></rect><text x="294.3" y="157.25" class="boxtext leafboxtext" width="149" fill="black" stroke="none">I also wrote...</text><path d="M 260.5 150.5 L 285.5 150.5" class="arrow" fill="none"></path><path d="M 0 0 L 3 8 L 0 7 L -3 8 Z" stroke="none" fill="rgb(0, 0, 0)" transform="translate(285.5 150.5) rotate(90) scale(1)"></path><rect x="376.5" y="180.5" width="58" height="29" class="box leafbox" rx="4" ry="4"></rect><text x="385.3" y="202.25" class="boxtext leafboxtext" fill="black" stroke="none">here</text><rect x="285.5" y="180.5" width="66" height="30" class="box" rx="4" ry="4"></rect><text x="294.7" y="203" class="boxtext" width="66" fill="black" stroke="none">a</text><path d="M 268.5 150.5 L 268.5 191.5 A 4 4 0 0 0 272.5 195.5 L 285.5 195.5" class="arrow" fill="none"></path><path d="M 0 0 L 3 8 L 0 7 L -3 8 Z" stroke="none" fill="rgb(0, 0, 0)" transform="translate(285.5 195.5) rotate(90) scale(1)"></path><path d="M 351.5 195.5 L 376.5 195.5" class="arrow" fill="none"></path><path d="M 0 0 L 3 8 L 0 7 L -3 8 Z" stroke="none" fill="rgb(0, 0, 0)" transform="translate(376.5 195.5) rotate(90) scale(1)"></path><rect x="285.5" y="225.5" width="23" height="29" class="box leafbox" rx="4" ry="4"></rect><text x="294.3" y="247.25" class="boxtext leafboxtext" fill="black" stroke="none">.</text><path d="M 268.5 150.5 L 268.5 236.5 A 4 4 0 0 0 272.5 240.5 L 285.5 240.5" class="arrow" fill="none"></path><path d="M 0 0 L 3 8 L 0 7 L -3 8 Z" stroke="none" fill="rgb(0, 0, 0)" transform="translate(285.5 240.5) rotate(90) scale(1)"></path></g></svg> 37 | -------------------------------------------------------------------------------- /img/14-3.svg: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 | <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="357" height="317" viewBox="-245 -250 357 317"><style type="text/css"> 3 | @font-face { 4 | font-family: 'PT Mono'; 5 | font-style: normal; 6 | font-weight: 400; 7 | src: local('PT Mono'), local('PTMono-Regular'), url(http://themes.googleusercontent.com/static/fonts/ptmono/v1/jmle3kzCPnW8O7_gZGRDlQ.woff) format('woff'); 8 | } 9 | .leaf { 10 | font-family: "Georgia"; 11 | font-size: 17px; 12 | } 13 | .wraptext, .linktext, .index { 14 | font-family: "PT Mono"; 15 | font-size: 14px; 16 | } 17 | .wrap { 18 | border-radius: 4px; 19 | stroke: #666; 20 | fill: none; 21 | } 22 | .link { stroke: #6aa; } 23 | .linktext, .index { fill: #6aa; } 24 | </style> 25 | <g><text x="-167" y="16" class="leaf">I also wrote a book! ...</text><text x="-167" y="-11" class="wraptext">p</text><rect x="-178.5" y="-28.5" width="191" height="61" class="wrap" rx="4" ry="4"></rect><text x="-167" y="-68.5" class="leaf">Hello, I am Marijn...</text><text x="-167" y="-95.5" class="wraptext">p</text><rect x="-178.5" y="-113.5" width="191" height="61" class="wrap" rx="4" ry="4"></rect><text x="-167" y="-153.5" class="leaf">My home page</text><text x="-167" y="-180.5" class="wraptext">h1</text><rect x="-178.5" y="-198.5" width="191" height="61" class="wrap" rx="4" ry="4"></rect><text x="-178.5" y="-209.5" class="wraptext">body</text><rect x="-190.5" y="-227.5" width="215" height="272" class="wrap" rx="4" ry="4"></rect><text x="-230.5" y="-180.5" class="index">0</text><text x="-230.5" y="-95.5" class="index">1</text><text x="-230.5" y="-11" class="index">2</text><rect x="-242.5" y="-204.5" width="32" height="209" class="wrap" rx="4" ry="4"></rect><path d="M -210.5 -184.5 L -178.5 -184.5" class="link" fill="none"></path><path d="M 0 0 L 3 8 L 0 7 L -3 8 Z" stroke="none" fill="rgb(102, 170, 170)" transform="translate(-178.5 -184.5) rotate(90) scale(1)"></path><path d="M -210.5 -99.5 L -178.5 -99.5" class="link" fill="none"></path><path d="M 0 0 L 3 8 L 0 7 L -3 8 Z" stroke="none" fill="rgb(102, 170, 170)" transform="translate(-178.5 -99.5) rotate(90) scale(1)"></path><path d="M -210.5 -15.5 L -178.5 -15.5" class="link" fill="none"></path><path d="M 0 0 L 3 8 L 0 7 L -3 8 Z" stroke="none" fill="rgb(102, 170, 170)" transform="translate(-178.5 -15.5) rotate(90) scale(1)"></path><path d="M -175.5 -227.5 L -175.5 -232.5 A 7 7 0 0 0 -182.5 -239.5 L -219.5 -239.5 A 7 7 0 0 0 -226.5 -232.5 L -226.5 -204.5" class="link" fill="none"></path><path d="M 0 0 L 3 8 L 0 7 L -3 8 Z" stroke="none" fill="rgb(102, 170, 170)" transform="translate(-226.5 -204.5) rotate(180) scale(1)"></path><text x="-170.5" y="-235.5" class="linktext">childNodes</text><path d="M -42.5 -227.5 L -42.5 -232.5 A 7 7 0 0 0 -49.5 -239.5 L -55.5 -239.5 A 7 7 0 0 0 -62.5 -232.5 L -62.5 -198.5" class="link" fill="none"></path><path d="M 0 0 L 3 8 L 0 7 L -3 8 Z" stroke="none" fill="rgb(102, 170, 170)" transform="translate(-62.5 -198.5) rotate(180) scale(1)"></path><text x="-38" y="-235.5" class="linktext">firstChild</text><path d="M -42.5 44.5 L -42.5 49.5 A 7 7 0 0 1 -49.5 56.5 L -55.5 56.5 A 7 7 0 0 1 -62.5 49.5 L -62.5 32.5" class="link" fill="none"></path><path d="M 0 0 L 3 8 L 0 7 L -3 8 Z" stroke="none" fill="rgb(102, 170, 170)" transform="translate(-62.5 32.5) scale(1)"></path><text x="-38" y="61.5" class="linktext">lastChild</text><path d="M -158.5 -113.5 L -158.5 -137.5" class="link" fill="none"></path><path d="M 0 0 L 3 8 L 0 7 L -3 8 Z" stroke="none" fill="rgb(102, 170, 170)" transform="translate(-158.5 -137.5) scale(1)"></path><text x="-153.5" y="-121" class="linktext">previousSibling</text><path d="M -158.5 -52.5 L -158.5 -28.5" class="link" fill="none"></path><path d="M 0 0 L 3 8 L 0 7 L -3 8 Z" stroke="none" fill="rgb(102, 170, 170)" transform="translate(-158.5 -28.5) rotate(180) scale(1)"></path><text x="-153.5" y="-36" class="linktext">nextSibling</text><path d="M 12.5 -82.5 L 31.5 -82.5 A 7 7 0 0 0 38.5 -89.5 L 38.5 -95.5 A 7 7 0 0 0 31.5 -102.5 L 31.5 -102.5 L 24.5 -102.5" class="link" fill="none"></path><path d="M 0 0 L 3 8 L 0 7 L -3 8 Z" stroke="none" fill="rgb(102, 170, 170)" transform="translate(24.5 -102.5) rotate(270) scale(1)"></path><text x="29.5" y="-66" class="linktext">parentNode</text></g></svg> 26 | -------------------------------------------------------------------------------- /img/14-4.svg: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 | <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="252" height="167" viewBox="-131 -84 252 167"><style type="text/css"> 3 | @font-face { 4 | font-family: 'PT Mono'; 5 | font-style: normal; 6 | font-weight: 400; 7 | src: local('PT Mono'), local('PTMono-Regular'), url(http://themes.googleusercontent.com/static/fonts/ptmono/v1/jmle3kzCPnW8O7_gZGRDlQ.woff) format('woff'); 8 | } 9 | .caption { font-family: 'PT Mono'; font-size: 13px; } 10 | </style> 11 | <g><path d="M 0.5 -79.5 L 0.5 80.5" stroke="black"></path><path d="M -79.5 0.5 L 80.5 0.5" stroke="black"></path><path d="M 14.7 3 A 15 15 0 1 1 10.6 -10.6" stroke="black" fill="none"></path><path d="M 12.5 -8.5 L 13.5 -7.5" stroke="black"></path><path d="M 0 0 L 3 8 L 0 7 L -3 8 Z" stroke="none" fill="rgb(0, 0, 0)" transform="translate(13.5 -7.5) rotate(135) scale(1)"></path><ellipse cx="0" cy="0" rx="60" ry="60" stroke="black" fill="none" width="120" height="120"></ellipse><ellipse cx="42.426406871192846" cy="42.426406871192846" rx="3" ry="3" fill="#44f" width="6" height="6"></ellipse><path d="M 0.5 42.5 L 39.5 42.5" stroke="#44f"></path><path d="M 0 0 L 3 8 L 0 7 L -3 8 Z" stroke="none" fill="rgb(68, 68, 255)" transform="translate(39.5 42.5) rotate(90) scale(1)"></path><path d="M 42.5 0.5 L 42.5 39.5" stroke="#44f"></path><path d="M 0 0 L 3 8 L 0 7 L -3 8 Z" stroke="none" fill="rgb(68, 68, 255)" transform="translate(42.5 39.5) rotate(180) scale(1)"></path><text x="32.426406871192846" y="64.42640687119285" class="caption" fill="#44f">cos(¼π)</text><text x="62.426406871192846" y="29.426406871192846" class="caption" fill="#44f">sin(¼π)</text><ellipse cx="-30.000000000544134" cy="-51.961524226752175" rx="3" ry="3" fill="#0a0" width="6" height="6"></ellipse><path d="M 0.5 -51.5 L -27.5 -51.5" stroke="#0a0"></path><path d="M 0 0 L 3 8 L 0 7 L -3 8 Z" stroke="none" fill="rgb(0, 170, 0)" transform="translate(-27.5 -51.5) rotate(270) scale(1)"></path><path d="M -30.5 0.5 L -30.5 -48.5" stroke="#0a0"></path><path d="M 0 0 L 3 8 L 0 7 L -3 8 Z" stroke="none" fill="rgb(0, 170, 0)" transform="translate(-30.5 -48.5) scale(1)"></path><text x="-84.00000000054413" y="-69.96152422675218" class="caption" fill="#0a0">cos(-⅔π)</text><text x="-129.00000000054413" y="-29.961524226752175" class="caption" fill="#0a0">sin(-⅔π)</text></g></svg> 12 | -------------------------------------------------------------------------------- /img/15-0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wizardforcel/eloquent-js-3e-zh/097ef4522075bcdf3e167e38cce1b7fae034579c/img/15-0.jpg -------------------------------------------------------------------------------- /img/16-0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wizardforcel/eloquent-js-3e-zh/097ef4522075bcdf3e167e38cce1b7fae034579c/img/16-0.jpg -------------------------------------------------------------------------------- /img/16-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wizardforcel/eloquent-js-3e-zh/097ef4522075bcdf3e167e38cce1b7fae034579c/img/16-1.png -------------------------------------------------------------------------------- /img/16-2.svg: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 | <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="125" height="105" viewBox="-2 -2 125 105"><g><rect x="40" y="20" width="40" height="60" fill="#e0e0ff"></rect><g><path d="M 20.5 0.5 L 20.5 100.5" width="120" height="100" stroke-width="1" stroke="#888"></path><path d="M 40.5 0.5 L 40.5 100.5" width="120" height="100" stroke-width="1" stroke="#888"></path><path d="M 60.5 0.5 L 60.5 100.5" width="120" height="100" stroke-width="1" stroke="#888"></path><path d="M 80.5 0.5 L 80.5 100.5" width="120" height="100" stroke-width="1" stroke="#888"></path><path d="M 100.5 0.5 L 100.5 100.5" width="120" height="100" stroke-width="1" stroke="#888"></path><path d="M 0.5 20.5 L 120.5 20.5" width="120" height="100" stroke-width="1" stroke="#888"></path><path d="M 0.5 40.5 L 120.5 40.5" width="120" height="100" stroke-width="1" stroke="#888"></path><path d="M 0.5 60.5 L 120.5 60.5" width="120" height="100" stroke-width="1" stroke="#888"></path><path d="M 0.5 80.5 L 120.5 80.5" width="120" height="100" stroke-width="1" stroke="#888"></path></g><rect x="55" y="37" width="16" height="30" fill="#404040"></rect></g></svg> -------------------------------------------------------------------------------- /img/17-0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wizardforcel/eloquent-js-3e-zh/097ef4522075bcdf3e167e38cce1b7fae034579c/img/17-0.jpg -------------------------------------------------------------------------------- /img/17-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wizardforcel/eloquent-js-3e-zh/097ef4522075bcdf3e167e38cce1b7fae034579c/img/17-1.png -------------------------------------------------------------------------------- /img/17-2.svg: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 | <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="635" height="224" viewBox="-82 -82 635 224"><style type="text/css"> 3 | @font-face { 4 | font-family: 'PT Mono'; 5 | font-style: normal; 6 | font-weight: 400; 7 | src: local('PT Mono'), local('PTMono-Regular'), url(http://themes.googleusercontent.com/static/fonts/ptmono/v1/jmle3kzCPnW8O7_gZGRDlQ.woff) format('woff'); 8 | } 9 | .caption { font-family: 'PT Mono'; font-size: 13px; } 10 | </style> 11 | <g><path d="M 0.5 -79.5 L 0.5 80.5" stroke="black"></path><path d="M -79.5 0.5 L 80.5 0.5" stroke="black"></path><path d="M 0.5 -79.5 L 0.5 80.5" stroke="#0a0" transform="translate(50 50)"></path><path d="M -79.5 0.5 L 80.5 0.5" stroke="#0a0" transform="translate(50 50)"></path><path d="M 0.5 0.5 L 50.5 50.5" stroke="#0a0"></path><path d="M 0 0 L 3 8 L 0 7 L -3 8 Z" stroke="none" fill="rgb(0, 170, 0)" transform="translate(50.5 50.5) rotate(135) scale(1)"></path><path d="M 0.5 -79.5 L 0.5 80.5" stroke="#44f" transform="translate(50 50) rotate(20)"></path><path d="M -79.5 0.5 L 80.5 0.5" stroke="#44f" transform="translate(50 50) rotate(20)"></path><path d="M 110.5 50.5 L 110.5 72.5" stroke="#44f"></path><path d="M 0 0 L 3 8 L 0 7 L -3 8 Z" stroke="none" fill="rgb(68, 68, 255)" transform="translate(110.5 72.5) rotate(180) scale(1)"></path><text x="75" y="27" fill="#0a0" class="caption">translate(50, 50)</text><text x="130" y="67" fill="#44f" class="caption">rotate(0.1*Math.PI)</text><path d="M 0.5 -79.5 L 0.5 80.5" stroke="black" transform="translate(360 0)"></path><path d="M -79.5 0.5 L 80.5 0.5" stroke="black" transform="translate(360 0)"></path><path d="M 0.5 -79.5 L 0.5 80.5" stroke="#0a0" transform="translate(360 0) rotate(20)"></path><path d="M -79.5 0.5 L 80.5 0.5" stroke="#0a0" transform="translate(360 0) rotate(20)"></path><path d="M 360.5 -59.5 L 380.5 -59.5" stroke="#0a0"></path><path d="M 0 0 L 3 8 L 0 7 L -3 8 Z" stroke="none" fill="rgb(0, 170, 0)" transform="translate(380.5 -59.5) rotate(90) scale(1)"></path><path d="M 0.5 -79.5 L 0.5 80.5" stroke="#44f" transform="translate(360 0) rotate(20) translate(50, 50)"></path><path d="M -79.5 0.5 L 80.5 0.5" stroke="#44f" transform="translate(360 0) rotate(20) translate(50, 50)"></path><path d="M 360.5 0.5 L 390.5 65.5" stroke="#44f"></path><path d="M 0 0 L 3 8 L 0 7 L -3 8 Z" stroke="none" fill="rgb(68, 68, 255)" transform="translate(390.5 65.5) rotate(155.22485943116808) scale(1)"></path><text x="390" y="-48" fill="#0a0" class="caption">rotate(0.1*Math.PI)</text><text x="415" y="57" fill="#44f" class="caption">translate(50, 50)</text></g></svg> 12 | -------------------------------------------------------------------------------- /img/17-3.svg: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 | <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="324" height="152" viewBox="-62 -84 324 152"><style type="text/css"> 3 | @font-face { 4 | font-family: 'PT Mono'; 5 | font-style: normal; 6 | font-weight: 400; 7 | src: local('PT Mono'), local('PTMono-Regular'), url(http://themes.googleusercontent.com/static/fonts/ptmono/v1/jmle3kzCPnW8O7_gZGRDlQ.woff) format('woff'); 8 | } 9 | .caption { font-family: 'PT Mono'; font-size: 13px; } 10 | </style> 11 | <g><path d="M 0.5 -59.5 L 0.5 60.5" stroke="black"></path><path d="M 0 0 L 3 8 L 0 7 L -3 8 Z" stroke="none" fill="rgb(0, 0, 0)" transform="translate(0.5 60.5) rotate(180) scale(1)"></path><path d="M -59.5 0.5 L 60.5 0.5" stroke="black"></path><path d="M 0 0 L 3 8 L 0 7 L -3 8 Z" stroke="none" fill="rgb(0, 0, 0)" transform="translate(60.5 0.5) rotate(90) scale(1)"></path><path d="M 0.5 -59.5 L 0.5 60.5" stroke="black" transform="translate(200 0) scale(-1 1)"></path><path d="M 0 0 L 3 8 L 0 7 L -3 8 Z" transform="translate(200 0) scale(-1 1) translate(0.5 60.5) rotate(180) scale(1)" stroke="none" fill="rgb(0, 0, 0)"></path><path d="M -59.5 0.5 L 60.5 0.5" stroke="black" transform="translate(200 0) scale(-1 1)"></path><path d="M 0 0 L 3 8 L 0 7 L -3 8 Z" transform="translate(200 0) scale(-1 1) translate(60.5 0.5) rotate(90) scale(1)" stroke="none" fill="rgb(0, 0, 0)"></path><path d="M 100.5 -64.5 L 100.5 65.5" stroke="#888"></path><text x="76" y="-70" fill="#888" class="caption">mirror</text><path d="M -15 -25 L 30 0 L -15 25 z" stroke="#0a0" fill="none" transform="translate(100 0)"></path><path d="M -15 -25 L 30 0 L -15 25 z" stroke="#44f" fill="none" transform="translate(200 0)"></path><path d="M -15 -25 L 30 0 L -15 25 z" stroke="#f44" fill="none" transform="scale(-1 1)"></path><path d="M -15 -25 L 30 0 L -15 25 z" stroke="black" fill="none" transform="scale(-1 1) translate(-100 0)"></path><text x="72" y="-18" fill="#0a0" class="caption">1</text><text x="172" y="-18" fill="#44f" class="caption">2</text><text x="20" y="-18" fill="#f44" class="caption">3</text><text x="120" y="-18" fill="black" class="caption">4</text></g></svg> 12 | -------------------------------------------------------------------------------- /img/17-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wizardforcel/eloquent-js-3e-zh/097ef4522075bcdf3e167e38cce1b7fae034579c/img/17-4.png -------------------------------------------------------------------------------- /img/17-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wizardforcel/eloquent-js-3e-zh/097ef4522075bcdf3e167e38cce1b7fae034579c/img/17-5.png -------------------------------------------------------------------------------- /img/18-0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wizardforcel/eloquent-js-3e-zh/097ef4522075bcdf3e167e38cce1b7fae034579c/img/18-0.jpg -------------------------------------------------------------------------------- /img/19-0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wizardforcel/eloquent-js-3e-zh/097ef4522075bcdf3e167e38cce1b7fae034579c/img/19-0.jpg -------------------------------------------------------------------------------- /img/19-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wizardforcel/eloquent-js-3e-zh/097ef4522075bcdf3e167e38cce1b7fae034579c/img/19-1.png -------------------------------------------------------------------------------- /img/19-2.svg: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 | <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="355" height="155" viewBox="-2 -2 355 155"><g><rect x="0" y="15" width="75" height="15" fill="#bbbbff"></rect><rect x="75" y="30" width="60" height="15" fill="#bbbbff"></rect><rect x="135" y="45" width="15" height="60" fill="#bbbbff"></rect><rect x="60" y="105" width="75" height="15" fill="#bbbbff"></rect><rect x="75" y="45" width="15" height="30" fill="#bbbbff"></rect><rect x="45" y="30" width="15" height="75" fill="#bbbbff"></rect><rect x="90" y="120" width="30" height="30" fill="#bbbbff"></rect><rect x="15" y="90" width="15" height="30" fill="#bbbbff"></rect><g><path d="M 15.5 0.5 L 15.5 150.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 30.5 0.5 L 30.5 150.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 45.5 0.5 L 45.5 150.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 60.5 0.5 L 60.5 150.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 75.5 0.5 L 75.5 150.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 90.5 0.5 L 90.5 150.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 105.5 0.5 L 105.5 150.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 120.5 0.5 L 120.5 150.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 135.5 0.5 L 135.5 150.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 0.5 15.5 L 150.5 15.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 0.5 30.5 L 150.5 30.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 0.5 45.5 L 150.5 45.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 0.5 60.5 L 150.5 60.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 0.5 75.5 L 150.5 75.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 0.5 90.5 L 150.5 90.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 0.5 105.5 L 150.5 105.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 0.5 120.5 L 150.5 120.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 0.5 135.5 L 150.5 135.5" width="150" height="150" stroke-width="1" stroke="#444"></path><rect x="0.5" y="0.5" width="150" height="150" stroke-width="1" stroke="#444" fill="none"></rect></g><ellipse cx="112.5" cy="67.5" rx="3.75" ry="3.75" fill="#a33" width="7.5" height="7.5"></ellipse><rect x="275" y="75" width="15" height="30" fill="#ffe6c0"></rect><rect x="290" y="45" width="45" height="60" fill="#ffe6c0"></rect><rect x="260" y="30" width="15" height="75" fill="#ffe6c0"></rect><rect x="200" y="15" width="75" height="15" fill="#bbbbff"></rect><rect x="275" y="30" width="60" height="15" fill="#bbbbff"></rect><rect x="335" y="45" width="15" height="60" fill="#bbbbff"></rect><rect x="260" y="105" width="75" height="15" fill="#bbbbff"></rect><rect x="275" y="45" width="15" height="30" fill="#bbbbff"></rect><rect x="245" y="30" width="15" height="75" fill="#bbbbff"></rect><rect x="290" y="120" width="30" height="30" fill="#bbbbff"></rect><rect x="215" y="90" width="15" height="30" fill="#bbbbff"></rect><g><path d="M 215.5 0.5 L 215.5 150.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 230.5 0.5 L 230.5 150.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 245.5 0.5 L 245.5 150.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 260.5 0.5 L 260.5 150.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 275.5 0.5 L 275.5 150.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 290.5 0.5 L 290.5 150.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 305.5 0.5 L 305.5 150.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 320.5 0.5 L 320.5 150.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 335.5 0.5 L 335.5 150.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 200.5 15.5 L 350.5 15.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 200.5 30.5 L 350.5 30.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 200.5 45.5 L 350.5 45.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 200.5 60.5 L 350.5 60.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 200.5 75.5 L 350.5 75.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 200.5 90.5 L 350.5 90.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 200.5 105.5 L 350.5 105.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 200.5 120.5 L 350.5 120.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 200.5 135.5 L 350.5 135.5" width="150" height="150" stroke-width="1" stroke="#444"></path><rect x="200.5" y="0.5" width="150" height="150" stroke-width="1" stroke="#444" fill="none"></rect></g><ellipse cx="312.5" cy="67.5" rx="3.75" ry="3.75" fill="#a33" width="7.5" height="7.5"></ellipse></g></svg> -------------------------------------------------------------------------------- /img/19-3.svg: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 | <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="355" height="155" viewBox="-2 -2 355 155"><g><rect x="30" y="15" width="15" height="15" fill="#000"></rect><rect x="45" y="30" width="15" height="15" fill="#000"></rect><rect x="45" y="45" width="15" height="15" fill="#000"></rect><rect x="60" y="60" width="15" height="15" fill="#000"></rect><rect x="75" y="75" width="15" height="15" fill="#000"></rect><rect x="90" y="90" width="15" height="15" fill="#000"></rect><rect x="90" y="105" width="15" height="15" fill="#000"></rect><rect x="105" y="120" width="15" height="15" fill="#000"></rect><g><path d="M 15.5 0.5 L 15.5 150.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 30.5 0.5 L 30.5 150.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 45.5 0.5 L 45.5 150.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 60.5 0.5 L 60.5 150.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 75.5 0.5 L 75.5 150.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 90.5 0.5 L 90.5 150.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 105.5 0.5 L 105.5 150.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 120.5 0.5 L 120.5 150.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 135.5 0.5 L 135.5 150.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 0.5 15.5 L 150.5 15.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 0.5 30.5 L 150.5 30.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 0.5 45.5 L 150.5 45.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 0.5 60.5 L 150.5 60.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 0.5 75.5 L 150.5 75.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 0.5 90.5 L 150.5 90.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 0.5 105.5 L 150.5 105.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 0.5 120.5 L 150.5 120.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 0.5 135.5 L 150.5 135.5" width="150" height="150" stroke-width="1" stroke="#444"></path><rect x="0.5" y="0.5" width="150" height="150" stroke-width="1" stroke="#444" fill="none"></rect></g><ellipse cx="37.5" cy="22.5" rx="3.75" ry="3.75" fill="#fff" width="7.5" height="7.5"></ellipse><ellipse cx="112.5" cy="127.5" rx="3.75" ry="3.75" fill="#fff" width="7.5" height="7.5"></ellipse><rect x="230" y="15" width="15" height="15" fill="#000"></rect><rect x="230" y="30" width="15" height="15" fill="#000"></rect><rect x="245" y="30" width="15" height="15" fill="#000"></rect><rect x="245" y="45" width="15" height="15" fill="#000"></rect><rect x="245" y="60" width="15" height="15" fill="#000"></rect><rect x="260" y="60" width="15" height="15" fill="#000"></rect><rect x="260" y="75" width="15" height="15" fill="#000"></rect><rect x="275" y="75" width="15" height="15" fill="#000"></rect><rect x="275" y="90" width="15" height="15" fill="#000"></rect><rect x="290" y="90" width="15" height="15" fill="#000"></rect><rect x="290" y="105" width="15" height="15" fill="#000"></rect><rect x="290" y="120" width="15" height="15" fill="#000"></rect><rect x="305" y="120" width="15" height="15" fill="#000"></rect><g><path d="M 215.5 0.5 L 215.5 150.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 230.5 0.5 L 230.5 150.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 245.5 0.5 L 245.5 150.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 260.5 0.5 L 260.5 150.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 275.5 0.5 L 275.5 150.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 290.5 0.5 L 290.5 150.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 305.5 0.5 L 305.5 150.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 320.5 0.5 L 320.5 150.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 335.5 0.5 L 335.5 150.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 200.5 15.5 L 350.5 15.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 200.5 30.5 L 350.5 30.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 200.5 45.5 L 350.5 45.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 200.5 60.5 L 350.5 60.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 200.5 75.5 L 350.5 75.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 200.5 90.5 L 350.5 90.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 200.5 105.5 L 350.5 105.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 200.5 120.5 L 350.5 120.5" width="150" height="150" stroke-width="1" stroke="#444"></path><path d="M 200.5 135.5 L 350.5 135.5" width="150" height="150" stroke-width="1" stroke="#444"></path><rect x="200.5" y="0.5" width="150" height="150" stroke-width="1" stroke="#444" fill="none"></rect></g><ellipse cx="237.5" cy="22.5" rx="3.75" ry="3.75" fill="#fff" width="7.5" height="7.5"></ellipse><ellipse cx="312.5" cy="127.5" rx="3.75" ry="3.75" fill="#fff" width="7.5" height="7.5"></ellipse></g></svg> -------------------------------------------------------------------------------- /img/2-0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wizardforcel/eloquent-js-3e-zh/097ef4522075bcdf3e167e38cce1b7fae034579c/img/2-0.jpg -------------------------------------------------------------------------------- /img/2-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wizardforcel/eloquent-js-3e-zh/097ef4522075bcdf3e167e38cce1b7fae034579c/img/2-1.png -------------------------------------------------------------------------------- /img/2-2.svg: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 | <!-- Created with Inkscape (http://www.inkscape.org/) --> 3 | 4 | <svg 5 | xmlns:dc="http://purl.org/dc/elements/1.1/" 6 | xmlns:cc="http://creativecommons.org/ns#" 7 | xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" 8 | xmlns:svg="http://www.w3.org/2000/svg" 9 | xmlns="http://www.w3.org/2000/svg" 10 | xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" 11 | xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" 12 | width="204.48096" 13 | height="19.999929" 14 | id="svg3216" 15 | version="1.1" 16 | inkscape:version="0.48.4 r9939" 17 | sodipodi:docname="New document 8"> 18 | <defs 19 | id="defs3218"> 20 | <marker 21 | inkscape:stockid="Arrow1Send" 22 | orient="auto" 23 | refY="0" 24 | refX="0" 25 | id="Arrow1Send" 26 | style="overflow:visible"> 27 | <path 28 | id="path3774" 29 | d="M 0,0 5,-5 -12.5,0 5,5 0,0 z" 30 | style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt" 31 | transform="matrix(-0.2,0,0,-0.2,-1.2,0)" 32 | inkscape:connector-curvature="0" /> 33 | </marker> 34 | </defs> 35 | <sodipodi:namedview 36 | id="base" 37 | pagecolor="#ffffff" 38 | bordercolor="#666666" 39 | borderopacity="1.0" 40 | inkscape:pageopacity="0.0" 41 | inkscape:pageshadow="2" 42 | inkscape:zoom="0.35" 43 | inkscape:cx="156.78125" 44 | inkscape:cy="-138.56231" 45 | inkscape:document-units="px" 46 | inkscape:current-layer="layer1" 47 | showgrid="false" 48 | fit-margin-top="0" 49 | fit-margin-left="0" 50 | fit-margin-right="0" 51 | fit-margin-bottom="0" 52 | inkscape:window-width="1600" 53 | inkscape:window-height="875" 54 | inkscape:window-x="0" 55 | inkscape:window-y="25" 56 | inkscape:window-maximized="0" /> 57 | <metadata 58 | id="metadata3221"> 59 | <rdf:RDF> 60 | <cc:Work 61 | rdf:about=""> 62 | <dc:format>image/svg+xml</dc:format> 63 | <dc:type 64 | rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> 65 | <dc:title></dc:title> 66 | </cc:Work> 67 | </rdf:RDF> 68 | </metadata> 69 | <g 70 | inkscape:label="Layer 1" 71 | inkscape:groupmode="layer" 72 | id="layer1" 73 | transform="translate(-218.21875,-373.79994)"> 74 | <path 75 | style="fill:none;stroke:#000000;stroke-width:8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#Arrow1Send)" 76 | d="m 218.236,384.24914 190.41375,0" 77 | id="path5135" 78 | inkscape:connector-curvature="0" 79 | sodipodi:nodetypes="cc" /> 80 | </g> 81 | </svg> 82 | -------------------------------------------------------------------------------- /img/2-3.svg: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 | <!-- Created with Inkscape (http://www.inkscape.org/) --> 3 | 4 | <svg 5 | xmlns:dc="http://purl.org/dc/elements/1.1/" 6 | xmlns:cc="http://creativecommons.org/ns#" 7 | xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" 8 | xmlns:svg="http://www.w3.org/2000/svg" 9 | xmlns="http://www.w3.org/2000/svg" 10 | xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" 11 | xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" 12 | width="205.10103" 13 | height="85.750778" 14 | id="svg3283" 15 | version="1.1" 16 | inkscape:version="0.48.4 r9939" 17 | sodipodi:docname="New document 11"> 18 | <defs 19 | id="defs3285"> 20 | <marker 21 | inkscape:stockid="Arrow1Send" 22 | orient="auto" 23 | refY="0" 24 | refX="0" 25 | id="Arrow1Send" 26 | style="overflow:visible"> 27 | <path 28 | id="path3774" 29 | d="M 0,0 5,-5 -12.5,0 5,5 0,0 z" 30 | style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt" 31 | transform="matrix(-0.2,0,0,-0.2,-1.2,0)" 32 | inkscape:connector-curvature="0" /> 33 | </marker> 34 | <marker 35 | inkscape:stockid="Arrow1Send" 36 | orient="auto" 37 | refY="0" 38 | refX="0" 39 | id="marker3266" 40 | style="overflow:visible"> 41 | <path 42 | id="path3268" 43 | d="M 0,0 5,-5 -12.5,0 5,5 0,0 z" 44 | style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt" 45 | transform="matrix(-0.2,0,0,-0.2,-1.2,0)" 46 | inkscape:connector-curvature="0" /> 47 | </marker> 48 | <marker 49 | inkscape:stockid="Arrow1Send" 50 | orient="auto" 51 | refY="0" 52 | refX="0" 53 | id="marker3270" 54 | style="overflow:visible"> 55 | <path 56 | id="path3272" 57 | d="M 0,0 5,-5 -12.5,0 5,5 0,0 z" 58 | style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt" 59 | transform="matrix(-0.2,0,0,-0.2,-1.2,0)" 60 | inkscape:connector-curvature="0" /> 61 | </marker> 62 | <marker 63 | inkscape:stockid="Arrow1Send" 64 | orient="auto" 65 | refY="0" 66 | refX="0" 67 | id="marker3274" 68 | style="overflow:visible"> 69 | <path 70 | id="path3276" 71 | d="M 0,0 5,-5 -12.5,0 5,5 0,0 z" 72 | style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt" 73 | transform="matrix(-0.2,0,0,-0.2,-1.2,0)" 74 | inkscape:connector-curvature="0" /> 75 | </marker> 76 | </defs> 77 | <sodipodi:namedview 78 | id="base" 79 | pagecolor="#ffffff" 80 | bordercolor="#666666" 81 | borderopacity="1.0" 82 | inkscape:pageopacity="0.0" 83 | inkscape:pageshadow="2" 84 | inkscape:zoom="0.35" 85 | inkscape:cx="1288.581" 86 | inkscape:cy="-271.41478" 87 | inkscape:document-units="px" 88 | inkscape:current-layer="layer1" 89 | showgrid="false" 90 | fit-margin-top="0" 91 | fit-margin-left="0" 92 | fit-margin-right="0" 93 | fit-margin-bottom="0" 94 | inkscape:window-width="1600" 95 | inkscape:window-height="875" 96 | inkscape:window-x="0" 97 | inkscape:window-y="25" 98 | inkscape:window-maximized="0" /> 99 | <metadata 100 | id="metadata3288"> 101 | <rdf:RDF> 102 | <cc:Work 103 | rdf:about=""> 104 | <dc:format>image/svg+xml</dc:format> 105 | <dc:type 106 | rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> 107 | <dc:title></dc:title> 108 | </cc:Work> 109 | </rdf:RDF> 110 | </metadata> 111 | <g 112 | inkscape:label="Layer 1" 113 | inkscape:groupmode="layer" 114 | id="layer1" 115 | transform="translate(913.58095,-175.19662)"> 116 | <g 117 | id="g3322" 118 | transform="matrix(0,-1,1,0,-1032.3622,-596.20924)"> 119 | <path 120 | style="fill:none;stroke:#000000;stroke-width:8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#Arrow1Send)" 121 | d="m -815.78349,118.91917 1.01015,27.77919" 122 | id="path4943" 123 | inkscape:connector-curvature="0" 124 | sodipodi:nodetypes="cc" /> 125 | <path 126 | style="fill:none;stroke:#000000;stroke-width:8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#Arrow1Send)" 127 | d="m -807.70227,168.41664 c 0,0 67.6802,38.3858 7.07106,95.9645" 128 | id="path5139" 129 | inkscape:connector-curvature="0" 130 | sodipodi:nodetypes="cc" /> 131 | <path 132 | sodipodi:nodetypes="cc" 133 | inkscape:connector-curvature="0" 134 | id="path5141" 135 | d="m -813.76319,274.48267 1.01015,35.35534" 136 | style="fill:none;stroke:#000000;stroke-width:8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#Arrow1Send)" /> 137 | <path 138 | sodipodi:nodetypes="cc" 139 | inkscape:connector-curvature="0" 140 | id="path5159" 141 | d="m -820.86915,168.41664 c 0,0 -67.68021,38.3858 -7.07106,95.9645" 142 | style="fill:none;stroke:#000000;stroke-width:8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#Arrow1Send)" /> 143 | <path 144 | transform="translate(-888.97485,115.42425)" 145 | d="m 80.307123,50.755318 c 0,3.347351 -2.713564,6.060915 -6.060915,6.060915 -3.347351,0 -6.060915,-2.713564 -6.060915,-6.060915 0,-3.347351 2.713564,-6.060915 6.060915,-6.060915 3.347351,0 6.060915,2.713564 6.060915,6.060915 z" 146 | sodipodi:ry="6.060915" 147 | sodipodi:rx="6.060915" 148 | sodipodi:cy="50.755318" 149 | sodipodi:cx="74.246208" 150 | id="path3001" 151 | style="fill:#ff0000;fill-opacity:1;stroke:none" 152 | sodipodi:type="arc" /> 153 | </g> 154 | </g> 155 | </svg> 156 | -------------------------------------------------------------------------------- /img/2-4.svg: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 | <!-- Created with Inkscape (http://www.inkscape.org/) --> 3 | 4 | <svg 5 | xmlns:dc="http://purl.org/dc/elements/1.1/" 6 | xmlns:cc="http://creativecommons.org/ns#" 7 | xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" 8 | xmlns:svg="http://www.w3.org/2000/svg" 9 | xmlns="http://www.w3.org/2000/svg" 10 | xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" 11 | xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" 12 | width="238.24316" 13 | height="104.35892" 14 | id="svg2" 15 | version="1.1" 16 | inkscape:version="0.48.4 r9939" 17 | sodipodi:docname="controlflow-nested-else.svg"> 18 | <defs 19 | id="defs4"> 20 | <marker 21 | inkscape:stockid="Arrow1Send" 22 | orient="auto" 23 | refY="0" 24 | refX="0" 25 | id="Arrow1Send" 26 | style="overflow:visible"> 27 | <path 28 | id="path3774" 29 | d="M 0,0 5,-5 -12.5,0 5,5 0,0 z" 30 | style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt" 31 | transform="matrix(-0.2,0,0,-0.2,-1.2,0)" 32 | inkscape:connector-curvature="0" /> 33 | </marker> 34 | <marker 35 | inkscape:stockid="Arrow2Mend" 36 | orient="auto" 37 | refY="0" 38 | refX="0" 39 | id="Arrow2Mend" 40 | style="overflow:visible"> 41 | <path 42 | id="path3786" 43 | style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" 44 | d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z" 45 | transform="scale(-0.6,-0.6)" 46 | inkscape:connector-curvature="0" /> 47 | </marker> 48 | <marker 49 | inkscape:stockid="Arrow1Mend" 50 | orient="auto" 51 | refY="0" 52 | refX="0" 53 | id="Arrow1Mend" 54 | style="overflow:visible"> 55 | <path 56 | id="path3768" 57 | d="M 0,0 5,-5 -12.5,0 5,5 0,0 z" 58 | style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt" 59 | transform="matrix(-0.4,0,0,-0.4,-4,0)" 60 | inkscape:connector-curvature="0" /> 61 | </marker> 62 | <marker 63 | inkscape:stockid="Arrow1Lend" 64 | orient="auto" 65 | refY="0" 66 | refX="0" 67 | id="Arrow1Lend" 68 | style="overflow:visible"> 69 | <path 70 | id="path3762" 71 | d="M 0,0 5,-5 -12.5,0 5,5 0,0 z" 72 | style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt" 73 | transform="matrix(-0.8,0,0,-0.8,-10,0)" 74 | inkscape:connector-curvature="0" /> 75 | </marker> 76 | </defs> 77 | <sodipodi:namedview 78 | id="base" 79 | pagecolor="#ffffff" 80 | bordercolor="#666666" 81 | borderopacity="1.0" 82 | inkscape:pageopacity="0.0" 83 | inkscape:pageshadow="2" 84 | inkscape:zoom="2.8" 85 | inkscape:cx="182.61224" 86 | inkscape:cy="45.356254" 87 | inkscape:document-units="px" 88 | inkscape:current-layer="layer1" 89 | showgrid="false" 90 | showguides="true" 91 | inkscape:guide-bbox="true" 92 | inkscape:window-width="1600" 93 | inkscape:window-height="875" 94 | inkscape:window-x="0" 95 | inkscape:window-y="25" 96 | inkscape:window-maximized="0" 97 | fit-margin-top="0" 98 | fit-margin-left="0" 99 | fit-margin-right="3" 100 | fit-margin-bottom="0" /> 101 | <metadata 102 | id="metadata7"> 103 | <rdf:RDF> 104 | <cc:Work 105 | rdf:about=""> 106 | <dc:format>image/svg+xml</dc:format> 107 | <dc:type 108 | rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> 109 | <dc:title></dc:title> 110 | </cc:Work> 111 | </rdf:RDF> 112 | </metadata> 113 | <g 114 | inkscape:label="Layer 1" 115 | inkscape:groupmode="layer" 116 | id="layer1" 117 | transform="translate(-222.69525,-394.46429)"> 118 | <path 119 | sodipodi:nodetypes="cc" 120 | inkscape:connector-curvature="0" 121 | id="path4943" 122 | d="m 226.69525,431.70744 27.77919,1.01015" 123 | style="fill:none;stroke:#000000;stroke-width:8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#Arrow1Send)" /> 124 | <path 125 | sodipodi:nodetypes="cc" 126 | inkscape:connector-curvature="0" 127 | id="path5139" 128 | d="m 274.76415,441.21724 c 2.14286,43.57143 36.60008,44.46591 47.39307,47.42819" 129 | style="fill:none;stroke:#000000;stroke-width:8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#Arrow1Send)" /> 130 | <path 131 | style="fill:none;stroke:#000000;stroke-width:8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#Arrow1Send)" 132 | d="m 411.18732,433.72774 35.35534,1.01015" 133 | id="path5141" 134 | inkscape:connector-curvature="0" 135 | sodipodi:nodetypes="cc" /> 136 | <path 137 | style="fill:none;stroke:#000000;stroke-width:8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#Arrow1Send)" 138 | d="m 278.69272,425.19321 c 0,0 60.8858,-56.96593 122.75021,-4.57106" 139 | id="path5159" 140 | inkscape:connector-curvature="0" 141 | sodipodi:nodetypes="cc" /> 142 | <path 143 | sodipodi:type="arc" 144 | style="fill:#ff0000;fill-opacity:1;stroke:none" 145 | id="path3001" 146 | sodipodi:cx="74.246208" 147 | sodipodi:cy="50.755318" 148 | sodipodi:rx="6.060915" 149 | sodipodi:ry="6.060915" 150 | d="m 80.307123,50.755318 a 6.060915,6.060915 0 1 1 -12.12183,0 6.060915,6.060915 0 1 1 12.12183,0 z" 151 | transform="matrix(0,1,1,0,223.20033,358.51608)" /> 152 | <path 153 | transform="matrix(0,1,1,0,291.77176,418.51608)" 154 | d="m 80.307123,50.755318 a 6.060915,6.060915 0 1 1 -12.12183,0 6.060915,6.060915 0 1 1 12.12183,0 z" 155 | sodipodi:ry="6.060915" 156 | sodipodi:rx="6.060915" 157 | sodipodi:cy="50.755318" 158 | sodipodi:cx="74.246208" 159 | id="path3002" 160 | style="fill:#ff0000;fill-opacity:1;stroke:none" 161 | sodipodi:type="arc" /> 162 | <path 163 | style="fill:none;stroke:#000000;stroke-width:8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#Arrow1Send)" 164 | d="m 341.54987,484.43152 c 0,0 0.96327,-38.82118 55.9645,-46.85751" 165 | id="path3004" 166 | inkscape:connector-curvature="0" 167 | sodipodi:nodetypes="cc" /> 168 | <path 169 | sodipodi:nodetypes="cc" 170 | inkscape:connector-curvature="0" 171 | id="path3006" 172 | d="m 350.83558,490.14581 c 24.92879,-3.90484 44.71588,-30.84607 52.03593,-41.14323" 173 | style="fill:none;stroke:#000000;stroke-width:8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#Arrow1Send)" /> 174 | </g> 175 | </svg> 176 | -------------------------------------------------------------------------------- /img/2-5.svg: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 | <!-- Created with Inkscape (http://www.inkscape.org/) --> 3 | 4 | <svg 5 | xmlns:dc="http://purl.org/dc/elements/1.1/" 6 | xmlns:cc="http://creativecommons.org/ns#" 7 | xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" 8 | xmlns:svg="http://www.w3.org/2000/svg" 9 | xmlns="http://www.w3.org/2000/svg" 10 | xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" 11 | xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" 12 | width="206.31815" 13 | height="72.428215" 14 | id="svg2" 15 | version="1.1" 16 | inkscape:version="0.48.4 r9939" 17 | sodipodi:docname="controlflow-loop.svg"> 18 | <defs 19 | id="defs4"> 20 | <marker 21 | inkscape:stockid="Arrow1Send" 22 | orient="auto" 23 | refY="0" 24 | refX="0" 25 | id="Arrow1Send" 26 | style="overflow:visible"> 27 | <path 28 | id="path3774" 29 | d="M 0,0 5,-5 -12.5,0 5,5 0,0 z" 30 | style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt" 31 | transform="matrix(-0.2,0,0,-0.2,-1.2,0)" 32 | inkscape:connector-curvature="0" /> 33 | </marker> 34 | <marker 35 | inkscape:stockid="Arrow2Mend" 36 | orient="auto" 37 | refY="0" 38 | refX="0" 39 | id="Arrow2Mend" 40 | style="overflow:visible"> 41 | <path 42 | id="path3786" 43 | style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" 44 | d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z" 45 | transform="scale(-0.6,-0.6)" 46 | inkscape:connector-curvature="0" /> 47 | </marker> 48 | <marker 49 | inkscape:stockid="Arrow1Mend" 50 | orient="auto" 51 | refY="0" 52 | refX="0" 53 | id="Arrow1Mend" 54 | style="overflow:visible"> 55 | <path 56 | id="path3768" 57 | d="M 0,0 5,-5 -12.5,0 5,5 0,0 z" 58 | style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt" 59 | transform="matrix(-0.4,0,0,-0.4,-4,0)" 60 | inkscape:connector-curvature="0" /> 61 | </marker> 62 | <marker 63 | inkscape:stockid="Arrow1Lend" 64 | orient="auto" 65 | refY="0" 66 | refX="0" 67 | id="Arrow1Lend" 68 | style="overflow:visible"> 69 | <path 70 | id="path3762" 71 | d="M 0,0 5,-5 -12.5,0 5,5 0,0 z" 72 | style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt" 73 | transform="matrix(-0.8,0,0,-0.8,-10,0)" 74 | inkscape:connector-curvature="0" /> 75 | </marker> 76 | </defs> 77 | <sodipodi:namedview 78 | id="base" 79 | pagecolor="#ffffff" 80 | bordercolor="#666666" 81 | borderopacity="1.0" 82 | inkscape:pageopacity="0.0" 83 | inkscape:pageshadow="2" 84 | inkscape:zoom="2.8" 85 | inkscape:cx="30.225462" 86 | inkscape:cy="52.400366" 87 | inkscape:document-units="px" 88 | inkscape:current-layer="layer1" 89 | showgrid="false" 90 | showguides="true" 91 | inkscape:guide-bbox="true" 92 | inkscape:window-width="1600" 93 | inkscape:window-height="875" 94 | inkscape:window-x="0" 95 | inkscape:window-y="25" 96 | inkscape:window-maximized="0" 97 | fit-margin-top="0" 98 | fit-margin-left="0" 99 | fit-margin-right="0" 100 | fit-margin-bottom="0" /> 101 | <metadata 102 | id="metadata7"> 103 | <rdf:RDF> 104 | <cc:Work 105 | rdf:about=""> 106 | <dc:format>image/svg+xml</dc:format> 107 | <dc:type 108 | rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> 109 | <dc:title /> 110 | </cc:Work> 111 | </rdf:RDF> 112 | </metadata> 113 | <g 114 | inkscape:label="Layer 1" 115 | inkscape:groupmode="layer" 116 | id="layer1" 117 | transform="translate(-376.5106,-433.43911)"> 118 | <path 119 | sodipodi:nodetypes="csc" 120 | inkscape:connector-curvature="0" 121 | id="path5161" 122 | d="m 478.22234,451.00514 c 29.28572,23.75 27.61731,51.59627 -8.71166,51.57646 -36.44966,-0.0199 -18.10635,-25.27225 -9.3957,-39.5054" 123 | style="fill:none;stroke:#000000;stroke-width:8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#Arrow1Send)" /> 124 | <path 125 | style="fill:none;stroke:#000000;stroke-width:8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#Arrow1Send)" 126 | d="m 379.43917,445.98075 73.1512,-1.01015" 127 | id="path3003" 128 | inkscape:connector-curvature="0" 129 | sodipodi:nodetypes="cc" /> 130 | <path 131 | sodipodi:nodetypes="cc" 132 | inkscape:connector-curvature="0" 133 | id="path3005" 134 | d="m 482.59676,443.96045 87.76125,-1.01015" 135 | style="fill:none;stroke:#000000;stroke-width:8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#Arrow1Send)" /> 136 | <path 137 | transform="matrix(0,-1,1,0,422.09693,518.66704)" 138 | d="m 80.307123,50.755318 c 0,3.347351 -2.713564,6.060915 -6.060915,6.060915 -3.347351,0 -6.060915,-2.713564 -6.060915,-6.060915 0,-3.347351 2.713564,-6.060915 6.060915,-6.060915 3.347351,0 6.060915,2.713564 6.060915,6.060915 z" 139 | sodipodi:ry="6.060915" 140 | sodipodi:rx="6.060915" 141 | sodipodi:cy="50.755318" 142 | sodipodi:cx="74.246208" 143 | id="path3004" 144 | style="fill:#ff0000;fill-opacity:1;stroke:none" 145 | sodipodi:type="arc" /> 146 | </g> 147 | </svg> 148 | -------------------------------------------------------------------------------- /img/20-0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wizardforcel/eloquent-js-3e-zh/097ef4522075bcdf3e167e38cce1b7fae034579c/img/20-0.jpg -------------------------------------------------------------------------------- /img/21-0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wizardforcel/eloquent-js-3e-zh/097ef4522075bcdf3e167e38cce1b7fae034579c/img/21-0.jpg -------------------------------------------------------------------------------- /img/21-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wizardforcel/eloquent-js-3e-zh/097ef4522075bcdf3e167e38cce1b7fae034579c/img/21-1.png -------------------------------------------------------------------------------- /img/3-0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wizardforcel/eloquent-js-3e-zh/097ef4522075bcdf3e167e38cce1b7fae034579c/img/3-0.jpg -------------------------------------------------------------------------------- /img/4-0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wizardforcel/eloquent-js-3e-zh/097ef4522075bcdf3e167e38cce1b7fae034579c/img/4-0.jpg -------------------------------------------------------------------------------- /img/4-1.svg: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 | <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="505" height="345" viewBox="-2 -2 505 345"><defs> 3 | <g id="squirrel"> 4 | <path d="M 30 0 C 13 0 0 14 0 31 C 0 41 5 50 13 55 C 13 55 13 55 13 54 C 13 49 17 44 22 44 C 22 44 24 44 24 45 C 26 45 29 47 30 51 C 30 55 30 60 28 63 L 28 63 C 28 64 26 65 26 67 C 26 69 25 71 25 72 C 25 73 25 73 25 73 C 25 73 25 73 25 73 C 24 85 29 96 39 99 C 42 100 43 100 46 99 C 38 94 36 83 39 74 C 42 69 46 65 50 60 C 54 56 57 50 59 44 C 59 40 61 36 61 32 C 61 32 61 32 61 32 C 61 32 61 31 61 31 C 61 14 47 0 30 0 z" fill="#ea4" stroke="none"></path> 5 | <path d="M 74 14 C 70 17 67 19 64 23 C 64 23 64 23 64 23 C 64 24 64 24 64 26 C 62 32 68 40 76 41 C 86 44 93 40 96 33 C 95 29 91 26 84 23 C 82 22 79 22 78 21 L 74 14 z M 79 26 C 80 26 82 27 82 28 C 82 29 80 31 79 31 C 78 31 76 29 76 28 C 76 27 78 26 79 26 z" fill="#ea4" stroke="none"></path> 6 | <path d="M 68 41 C 68 41 68 41 68 41 L 68 41 L 68 41 C 64 46 57 56 43 71 C 43 72 42 73 42 74 C 39 81 39 88 43 94 C 43 94 45 94 45 95 C 45 95 45 95 45 95 C 46 95 46 95 46 95 C 46 95 46 95 46 95 C 46 96 47 96 47 96 C 49 97 50 97 50 99 C 53 99 55 100 57 100 L 57 100 L 96 100 C 96 100 96 100 96 100 C 99 100 100 97 100 95 C 100 94 99 91 96 91 L 96 91 L 79 91 L 78 91 L 75 91 C 75 91 75 91 75 91 C 75 91 76 91 76 90 C 76 90 78 90 78 90 C 78 90 78 90 78 90 C 78 88 78 88 78 88 C 79 88 79 88 79 88 C 79 88 79 88 79 88 C 84 83 86 77 82 73 C 79 71 76 69 74 68 C 72 68 72 68 71 68 C 71 68 71 68 70 68 C 70 68 70 68 70 68 C 70 68 68 68 68 68 C 68 68 68 68 68 68 C 68 68 68 68 68 68 C 67 68 67 68 67 68 C 67 68 67 68 67 68 C 67 68 66 68 66 68 C 66 68 66 68 66 68 C 64 68 64 69 64 69 C 63 69 63 69 62 69 C 62 69 62 69 62 69 C 61 71 61 71 61 71 C 59 71 59 71 59 72 C 59 72 59 72 59 72 C 58 72 58 72 58 72 C 58 72 58 72 58 72 C 57 73 57 73 57 73 C 57 73 57 73 57 73 C 57 73 57 73 57 73 C 55 74 55 74 55 74 C 55 74 55 74 55 74 C 55 74 55 76 55 76 C 54 76 54 76 54 77 C 54 77 54 77 54 77 L 51 76 C 51 74 53 74 53 73 C 54 72 55 71 57 69 C 61 67 64 65 70 65 C 70 65 71 65 71 65 C 72 65 74 65 74 67 C 74 65 75 65 75 64 C 76 60 79 58 82 56 C 82 56 82 56 82 56 C 83 55 84 55 86 55 L 88 55 C 88 56 91 59 93 59 C 96 59 97 56 97 54 C 97 51 96 49 93 49 L 93 49 L 79 49 L 78 45 C 75 44 71 42 68 41 z" fill="#ea4" stroke="none"></path> 7 | </g> 8 | <g id="pizza"> 9 | <path d="M 4 96 L 74 38 A 92 92 0 0 0 4 4 Z" stroke="#b44" stroke-width="8" fill="none" stroke-linejoin="round"></path> 10 | <circle cx="20" cy="20" r="10" fill="#b44"></circle> 11 | <circle cx="40" cy="30" r="9" fill="#b44"></circle> 12 | <circle cx="15" cy="66" r="11" fill="#b44"></circle> 13 | <circle cx="12" cy="36" r="10" fill="#b44"></circle> 14 | <circle cx="37" cy="51" r="11" fill="#b44"></circle> 15 | <circle cx="62" cy="34" r="8" fill="#b44"></circle> 16 | </g> 17 | </defs> 18 | <g><g><path d="M 250 0 L 250 340" width="500" height="340" stroke-width="2" stroke="black"></path><path d="M 0 170 L 500 170" width="500" height="340" stroke-width="2" stroke="black"></path><rect x="0" y="0" width="500" height="340" stroke-width="2" stroke="black" fill="none"></rect></g><use x="150" y="20" opacity="0.3" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#pizza"></use><use x="20" y="20" opacity="0.3" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#squirrel"></use><text x="20" y="156" font-family="Georgia" font-size="17">No squirrel, no pizza</text><text x="199" y="153" font-family="Georgia" font-size="31">76</text><use x="150" y="190" opacity="0.3" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#pizza"></use><use x="20" y="190" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#squirrel"></use><text x="20" y="326" font-family="Georgia" font-size="17">Squirrel, no pizza</text><text x="214.5" y="323" font-family="Georgia" font-size="31">4</text><use x="400" y="20" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#pizza"></use><use x="270" y="20" opacity="0.3" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#squirrel"></use><text x="270" y="156" font-family="Georgia" font-size="17">No squirrel, pizza</text><text x="464.5" y="153" font-family="Georgia" font-size="31">9</text><use x="400" y="190" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#pizza"></use><use x="270" y="190" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#squirrel"></use><text x="270" y="326" font-family="Georgia" font-size="17">Squirrel, pizza</text><text x="464.5" y="323" font-family="Georgia" font-size="31">1</text></g></svg> -------------------------------------------------------------------------------- /img/4-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wizardforcel/eloquent-js-3e-zh/097ef4522075bcdf3e167e38cce1b7fae034579c/img/4-2.jpg -------------------------------------------------------------------------------- /img/4-3.svg: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 | <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="393" height="76" viewBox="-2 -2 393 76"><style type="text/css"> 3 | @font-face { 4 | font-family: 'PT Mono'; 5 | font-style: normal; 6 | font-weight: 400; 7 | src: local('PT Mono'), local('PTMono-Regular'), url(http://themes.googleusercontent.com/static/fonts/ptmono/v1/jmle3kzCPnW8O7_gZGRDlQ.woff) format('woff'); 8 | } 9 | .objtext { font-family: "PT Mono"; font-size: 14px; stroke: none; } 10 | .objbox { border-radius: 2px; fill: white; stroke: black } 11 | .sep { stroke: #666 } 12 | </style> 13 | <g><g><rect x="0.5" y="0.5" width="104" height="51" class="objbox" rx="2" ry="2"></rect><text x="10" y="20" class="objtext">value: 1</text><text x="10" y="40" class="objtext">rest:</text></g><g><rect x="134.5" y="10.5" width="104" height="51" class="objbox" rx="2" ry="2"></rect><text x="144.5" y="30.5" class="objtext">value: 2</text><text x="144.5" y="50.5" class="objtext">rest:</text></g><path d="M 58.5 36.5 L 134.5 36.5" class="sep"></path><path d="M 0 0 L 3 8 L 0 7 L -3 8 Z" stroke="none" fill="rgb(102, 102, 102)" transform="translate(134.5 36.5) rotate(90) scale(1)"></path><g><rect x="268.5" y="20.5" width="120" height="51" class="objbox" rx="2" ry="2"></rect><text x="278.5" y="40.5" class="objtext">value: 3</text><text x="278.5" y="60.5" class="objtext">rest: null</text></g><path d="M 192.5 47.5 L 268.5 47.5" class="sep"></path><path d="M 0 0 L 3 8 L 0 7 L -3 8 Z" stroke="none" fill="rgb(102, 102, 102)" transform="translate(268.5 47.5) rotate(90) scale(1)"></path></g></svg> 14 | -------------------------------------------------------------------------------- /img/5-0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wizardforcel/eloquent-js-3e-zh/097ef4522075bcdf3e167e38cce1b7fae034579c/img/5-0.jpg -------------------------------------------------------------------------------- /img/5-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wizardforcel/eloquent-js-3e-zh/097ef4522075bcdf3e167e38cce1b7fae034579c/img/5-1.png -------------------------------------------------------------------------------- /img/6-0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wizardforcel/eloquent-js-3e-zh/097ef4522075bcdf3e167e38cce1b7fae034579c/img/6-0.jpg -------------------------------------------------------------------------------- /img/6-1.svg: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 | <svg xmlns="http://www.w3.org/2000/svg" width="621" height="274" viewBox="-4 -82 621 274"><style> 3 | @font-face { 4 | font-family: 'PT Mono'; 5 | font-style: normal; 6 | font-weight: 400; 7 | src: local('PT Mono'), local('PTMono-Regular'), url(http://themes.googleusercontent.com/static/fonts/ptmono/v1/jmle3kzCPnW8O7_gZGRDlQ.woff) format('woff'); 8 | } 9 | .objtext { font-family: "PT Mono"; font-size: 14px; stroke: none; } 10 | .objbox { border-radius: 2px; fill: white; stroke: black } 11 | .sep { stroke: #666 } 12 | </style> 13 | <g><g><rect x="210.5" y="133.5" width="202" height="55" class="objbox"></rect><text x="221.5" y="154.5" class="objtext">toString: <function></text><text x="221.5" y="176.5" class="objtext">...</text></g><g><rect x="121.5" y="83.5" width="178" height="55" class="objbox"></rect><text x="132.5" y="104.5" class="objtext">teeth: "small"</text><text x="132.5" y="126.5" class="objtext">speak: <function></text></g><g><rect x="0.5" y="0.5" width="242" height="88" class="objbox"></rect><text x="11" y="21" class="objtext">killerRabbit</text><text x="11" y="54" class="objtext">teeth: "long, sharp, ..."</text><text x="11" y="76" class="objtext">type: "killer"</text><path d="M 0.5 33.5 L 242.5 33.5" class="sep"></path></g><g><rect x="282.5" y="-79.5" width="114" height="66" class="objbox"></rect><text x="293.5" y="-58.5" class="objtext">Rabbit</text><text x="293.5" y="-25.5" class="objtext">prototype</text><path d="M 282.5 -46.5 L 396.5 -46.5" class="sep"></path></g><path d="M 329.5 -19.5 L 259.5 83.5" class="sep"></path><path d="M 0 0 L 3 8 L 0 7 L -3 8 Z" stroke="none" fill="rgb(102, 102, 102)" transform="translate(259.5 83.5) rotate(214.20048413026853) scale(1)"></path><g><rect x="426.5" y="-9.5" width="186" height="110" class="objbox"></rect><text x="437.5" y="11.5" class="objtext">Object</text><text x="437.5" y="44.5" class="objtext">create: <function></text><text x="437.5" y="66.5" class="objtext">prototype</text><text x="437.5" y="88.5" class="objtext">...</text><path d="M 426.5 23.5 L 612.5 23.5" class="sep"></path></g><path d="M 432.5 70.5 L 331.5 133.5" class="sep"></path><path d="M 0 0 L 3 8 L 0 7 L -3 8 Z" stroke="none" fill="rgb(102, 102, 102)" transform="translate(331.5 133.5) rotate(238.0456370629486) scale(1)"></path></g></svg> 14 | -------------------------------------------------------------------------------- /img/7-0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wizardforcel/eloquent-js-3e-zh/097ef4522075bcdf3e167e38cce1b7fae034579c/img/7-0.jpg -------------------------------------------------------------------------------- /img/7-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wizardforcel/eloquent-js-3e-zh/097ef4522075bcdf3e167e38cce1b7fae034579c/img/7-1.png -------------------------------------------------------------------------------- /img/8-0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wizardforcel/eloquent-js-3e-zh/097ef4522075bcdf3e167e38cce1b7fae034579c/img/8-0.jpg -------------------------------------------------------------------------------- /img/9-0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wizardforcel/eloquent-js-3e-zh/097ef4522075bcdf3e167e38cce1b7fae034579c/img/9-0.jpg -------------------------------------------------------------------------------- /img/9-1.svg: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 | <svg 3 | xmlns:svg="http://www.w3.org/2000/svg" 4 | xmlns="http://www.w3.org/2000/svg" 5 | height="166.23486" 6 | version="1.1" 7 | width="618.75934"> 8 | <path 9 | d="m 502.86787,89.225694 h 13.16396" 10 | style="fill:none;stroke:#000000;stroke-width:2.63279247" 11 | id="path4"/> 12 | <path 13 | d="m 405.45455,89.225694 h 13.16396" 14 | style="fill:none;stroke:#000000;stroke-width:2.63279247" 15 | id="path6"/> 16 | <path 17 | d="M 234.32304,89.225694 H 247.487" 18 | style="fill:none;stroke:#000000;stroke-width:2.63279247" 19 | id="path8"/> 20 | <path 21 | d="m 193.51476,89.225694 h 13.16396" 22 | style="fill:none;stroke:#000000;stroke-width:2.63279247" 23 | id="path10"/> 24 | <path 25 | d="m 110.5818,89.225694 h 13.16396" 26 | style="fill:none;stroke:#000000;stroke-width:2.63279247" 27 | id="path12"/> 28 | <path 29 | d="m 418.61851,89.225694 q 13.16396,0 13.16396,-13.16396 v -3.290991 q 0,-13.163962 13.16396,-13.163962 h 31.59351 q 13.16396,0 13.16396,13.163962 v 3.290991 q 0,13.16396 13.16397,13.16396" 30 | style="fill:none;stroke:#000000;stroke-width:2.63279247" 31 | id="path14"/> 32 | <path 33 | d="M 502.86787,89.225694 H 476.53994" 34 | style="fill:none;stroke:#000000;stroke-width:2.63279247" 35 | id="path16"/> 36 | <path 37 | d="m 418.61851,89.225694 h 26.32792" 38 | style="fill:none;stroke:#000000;stroke-width:2.63279247" 39 | id="path18"/> 40 | <path 41 | d="M 405.45455,89.225694 H 392.29059" 42 | style="fill:none;stroke:#000000;stroke-width:2.63279247" 43 | id="path20"/> 44 | <path 45 | d="m 247.487,89.225694 h 13.16396" 46 | style="fill:none;stroke:#000000;stroke-width:2.63279247" 47 | id="path22"/> 48 | <path 49 | d="m 392.29059,89.225694 q -13.16397,0 -13.16397,13.163956 v 19.74595 q 0,13.16396 -13.16396,13.16396 h 0" 50 | style="fill:none;stroke:#000000;stroke-width:2.63279247" 51 | id="path24"/> 52 | <path 53 | d="m 260.65096,89.225694 q 13.16397,0 13.16397,13.163956 v 19.74595 q 0,13.16396 13.16396,13.16396 h 0" 54 | style="fill:none;stroke:#000000;stroke-width:2.63279247" 55 | id="path26"/> 56 | <path 57 | d="m 392.29059,89.225694 c -13.16397,0 -13.16397,0 -39.49189,0" 58 | style="fill:none;stroke:#000000;stroke-width:2.63279247" 59 | id="path28"/> 60 | <path 61 | d="m 260.65096,89.225694 c 13.16397,0 13.16397,0 39.49189,0" 62 | style="fill:none;stroke:#000000;stroke-width:2.63279247" 63 | id="path30"/> 64 | <path 65 | d="m 392.29059,89.225694 q -13.16397,0 -13.16397,-13.16396 V 56.31579 q 0,-13.163962 -13.16396,-13.163962 h -16.45495" 66 | style="fill:none;stroke:#000000;stroke-width:2.63279247" 67 | id="path32"/> 68 | <path 69 | d="m 260.65096,89.225694 q 13.16397,0 13.16397,-13.16396 V 56.31579 q 0,-13.163962 13.16396,-13.163962 h 16.45495" 70 | style="fill:none;stroke:#000000;stroke-width:2.63279247" 71 | id="path34"/> 72 | <path 73 | d="m 180.3508,89.225694 q 13.16396,0 13.16396,13.163956 v 3.29099 q 0,13.16397 -13.16396,13.16397 h -43.44108 q -13.16396,0 -13.16396,-13.16397 v -3.29099 q 0,-13.163956 13.16396,-13.163956" 74 | style="fill:none;stroke:#000000;stroke-width:2.63279247" 75 | id="path36"/> 76 | <path 77 | d="M 193.51476,89.225694 H 180.3508" 78 | style="fill:none;stroke:#000000;stroke-width:2.63279247" 79 | id="path38"/> 80 | <path 81 | d="m 123.74576,89.225694 h 13.16396" 82 | style="fill:none;stroke:#000000;stroke-width:2.63279247" 83 | id="path40"/> 84 | <desc 85 | id="desc42">Created with Raphaël 2.1.0</desc> 86 | <defs 87 | id="defs44" /> 88 | <rect 89 | x="206.67871" 90 | y="72.770744" 91 | width="27.64432" 92 | height="32.909904" 93 | r="0" 94 | rx="3.9491887" 95 | ry="3.9491887" 96 | style="fill:#dae9e5;stroke:#dae9e5;stroke-width:1.31639624" 97 | id="rect46" /> 98 | <text 99 | x="220.50089" 100 | y="89.225693" 101 | font="10px "Arial"" 102 | style="font-size:15.79675484px;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;text-anchor:middle;fill:#000000;stroke:none;font-family:Arial" 103 | font-size="12px" 104 | id="text48"> 105 | <tspan 106 | dy="5.9237828" 107 | id="tspan50">" "</tspan> 108 | </text> 109 | <rect 110 | x="21.562796" 111 | y="72.66703" 112 | width="89.122711" 113 | height="33.117336" 114 | r="0" 115 | rx="2.7850847" 116 | ry="3.974081" 117 | style="fill:#bada55;stroke:#bada55;stroke-width:1.10896111" 118 | id="rect52" /> 119 | <text 120 | x="47.394775" 121 | y="89.225693" 122 | font="10px "Arial"" 123 | style="font-size:15.79675484px;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;text-anchor:middle;fill:#000000;stroke:none;font-family:Arial" 124 | font-size="12px" 125 | id="text54"> 126 | <tspan 127 | id="tspan56"></tspan> 128 | </text> 129 | <text 130 | x="66.728333" 131 | y="94.361168" 132 | font="10px "Arial"" 133 | style="font-size:15.79675484px;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;text-anchor:middle;fill:#000000;stroke:none;font-family:Arial" 134 | font-size="12px" 135 | id="text3087"> 136 | <tspan 137 | id="tspan3089">boundary</tspan> 138 | </text> 139 | <rect 140 | x="515.90363" 141 | y="72.642578" 142 | width="81.317352" 143 | height="33.166233" 144 | r="0" 145 | rx="2.5411673" 146 | ry="3.979948" 147 | style="fill:#bada55;stroke:#bada55;stroke-width:1.06006885" 148 | id="rect58" /> 149 | <text 150 | x="556.56232" 151 | y="94.663254" 152 | font="10px "Arial"" 153 | style="font-size:15.79675484px;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;text-anchor:middle;fill:#000000;stroke:none;font-family:Arial" 154 | font-size="12px" 155 | id="text60"> 156 | <tspan 157 | id="tspan62">boundary</tspan> 158 | </text> 159 | <rect 160 | x="247.487" 161 | y="13.532914" 162 | width="157.96754" 163 | height="151.38556" 164 | r="0" 165 | rx="3.9491887" 166 | ry="3.9491887" 167 | style="fill:none;stroke:#a0a0a0;stroke-width:2.63279247;stroke-dasharray:7.89837727, 2.63279242" 168 | id="rect64" /> 169 | <text 170 | x="276.44772" 171 | y="4.9763379" 172 | font="10px "Arial"" 173 | style="font-size:13.16396236px;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;text-anchor:middle;fill:#000000;stroke:none;font-family:Arial" 174 | font-size="10px" 175 | id="text66"> 176 | <tspan 177 | dy="4.6073866" 178 | id="tspan68">Group #1</tspan> 179 | </text> 180 | <rect 181 | x="286.97888" 182 | y="118.8446" 183 | width="78.983772" 184 | height="32.909904" 185 | r="0" 186 | rx="3.9491887" 187 | ry="3.9491887" 188 | style="fill:#dae9e5;stroke:#dae9e5;stroke-width:1.31639624" 189 | id="rect70" /> 190 | <text 191 | x="326.47076" 192 | y="135.29956" 193 | font="10px "Arial"" 194 | style="font-size:15.79675484px;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;text-anchor:middle;fill:#000000;stroke:none;font-family:Arial" 195 | font-size="12px" 196 | id="text72"> 197 | <tspan 198 | dy="5.9237828" 199 | id="tspan74">"chicken"</tspan> 200 | </text> 201 | <rect 202 | x="300.14285" 203 | y="72.770744" 204 | width="52.655849" 205 | height="32.909904" 206 | r="0" 207 | rx="3.9491887" 208 | ry="3.9491887" 209 | style="fill:#dae9e5;stroke:#dae9e5;stroke-width:1.31639624" 210 | id="rect76" /> 211 | <text 212 | x="326.47076" 213 | y="89.225693" 214 | font="10px "Arial"" 215 | style="font-size:15.79675484px;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;text-anchor:middle;fill:#000000;stroke:none;font-family:Arial" 216 | font-size="12px" 217 | id="text78"> 218 | <tspan 219 | dy="5.9237828" 220 | id="tspan80">"cow"</tspan> 221 | </text> 222 | <rect 223 | x="303.43384" 224 | y="26.696877" 225 | width="46.073868" 226 | height="32.909904" 227 | r="0" 228 | rx="3.9491887" 229 | ry="3.9491887" 230 | style="fill:#dae9e5;stroke:#dae9e5;stroke-width:1.31639624" 231 | id="rect82" /> 232 | <text 233 | x="326.47076" 234 | y="43.151829" 235 | font="10px "Arial"" 236 | style="font-size:15.79675484px;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;text-anchor:middle;fill:#000000;stroke:none;font-family:Arial" 237 | font-size="12px" 238 | id="text84"> 239 | <tspan 240 | dy="5.9237828" 241 | id="tspan86">"pig"</tspan> 242 | </text> 243 | <rect 244 | x="136.90971" 245 | y="72.770744" 246 | width="43.441074" 247 | height="32.909904" 248 | r="0" 249 | rx="3.9491887" 250 | ry="3.9491887" 251 | style="fill:#bada55;stroke:#bada55;stroke-width:1.31639624" 252 | id="rect88" /> 253 | <text 254 | x="158.63025" 255 | y="89.225693" 256 | font="10px "Arial"" 257 | style="font-size:15.79675484px;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;text-anchor:middle;fill:#000000;stroke:none;font-family:Arial" 258 | font-size="12px" 259 | id="text90"> 260 | <tspan 261 | dy="5.9237828" 262 | id="tspan92">digit</tspan> 263 | </text> 264 | <rect 265 | x="444.94644" 266 | y="72.770744" 267 | width="31.59351" 268 | height="32.909904" 269 | r="0" 270 | rx="3.9491887" 271 | ry="3.9491887" 272 | style="fill:#dae9e5;stroke:#dae9e5;stroke-width:1.31639624" 273 | id="rect94" /> 274 | <text 275 | x="460.74319" 276 | y="89.225693" 277 | font="10px "Arial"" 278 | style="font-size:15.79675484px;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;text-anchor:middle;fill:#000000;stroke:none;font-family:Arial" 279 | font-size="12px" 280 | id="text96"> 281 | <tspan 282 | dy="5.9237828" 283 | id="tspan98">"s"</tspan> 284 | </text> 285 | <path 286 | d="M 7.8983762,89.225694 H 21.062338" 287 | style="fill:none;stroke:#000000;stroke-width:2.63279247" 288 | id="path100"/> 289 | <circle 290 | cx="10" 291 | cy="80.5" 292 | r="5" 293 | style="fill:#6b6659;stroke:#000000;stroke-width:2" 294 | id="circle102" 295 | transform="matrix(1.3163962,0,0,1.3163962,-5.2655848,-16.744199)" /> 296 | <path 297 | d="M 610.86099,88.62152 H 597.69703" 298 | style="fill:none;stroke:#000000;stroke-width:2.63279247" 299 | id="path104"/> 300 | <circle 301 | cx="530" 302 | cy="80.5" 303 | r="5" 304 | style="fill:#6b6659;stroke:#000000;stroke-width:2" 305 | id="circle106" 306 | transform="matrix(1.3163962,0,0,1.3163962,-86.82901,-17.348373)" /> 307 | </svg> 308 | -------------------------------------------------------------------------------- /img/9-2.svg: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 | <svg xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" version="1.1" width="578" height="342"> 3 | <defs> 4 | <style type="text/css">svg { 5 | background-color: #fff; } 6 | 7 | .root text, 8 | .root tspan { 9 | font: 12px Arial; } 10 | 11 | .root path { 12 | fill-opacity: 0; 13 | stroke-width: 2px; 14 | stroke: #000; } 15 | 16 | .root circle { 17 | fill: #6b6659; 18 | stroke-width: 2px; 19 | stroke: #000; } 20 | 21 | .anchor text, .any-character text { 22 | fill: #fff; } 23 | 24 | .anchor rect, .any-character rect { 25 | fill: #6b6659; } 26 | 27 | .escape text, .charset-escape text, .literal text { 28 | fill: #000; } 29 | 30 | .escape rect, .charset-escape rect { 31 | fill: #bada55; } 32 | 33 | .literal rect { 34 | fill: #dae9e5; } 35 | 36 | .charset .charset-box { 37 | fill: #cbcbba; } 38 | 39 | .subexp .subexp-label tspan, 40 | .charset .charset-label tspan, 41 | .match-fragment .repeat-label tspan { 42 | font-size: 10px; } 43 | 44 | .repeat-label { 45 | cursor: help; } 46 | 47 | .subexp .subexp-label tspan, 48 | .charset .charset-label tspan { 49 | dominant-baseline: text-after-edge; } 50 | 51 | .subexp .subexp-box { 52 | stroke: #908c83; 53 | stroke-dasharray: 6,2; 54 | stroke-width: 2px; 55 | fill-opacity: 0; } 56 | 57 | .quote { 58 | fill: #908c83; } 59 | </style> 60 | </defs> 61 | <metadata> 62 | <rdf:rdf> 63 | <cc:license rdf:about="http://creativecommons.org/licenses/by/3.0/"> 64 | <cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"></cc:permits> 65 | <cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"></cc:permits> 66 | <cc:requires rdf:resource="http://creativecommons.org/ns#Notice"></cc:requires> 67 | <cc:requires rdf:resource="http://creativecommons.org/ns#Attribution"></cc:requires> 68 | <cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"></cc:permits> 69 | </cc:license> 70 | </rdf:rdf> 71 | </metadata> 72 | <desc>Created with Snap</desc><g class="root" transform="matrix(1.3,0,0,1.3,15,10)"><g class="regexp match" transform="matrix(1,0,0,1,10,0)"><path d="M92,127H112M282,127H302"></path><g class="match-fragment escape" transform="matrix(1,0,0,1,0,115)"><g class="label"><rect width="92" height="24" rx="3" ry="3"></rect><text x="0" y="0" transform="matrix(1,0,0,1,5,17)"><tspan>word boundary</tspan></text></g></g><g class="match-fragment subexp" transform="matrix(1,0,0,1,102,0)"><rect class="subexp-box" rx="3" ry="3" transform="matrix(1,0,0,1,0,11)" width="190" height="232"></rect><text x="0" y="0" class="subexp-label" transform="matrix(1,0,0,1,0,11)"><tspan>group #1</tspan></text><g class="regexp" transform="matrix(1,0,0,1,10,21)"><path d="M10,52.5q0,-10 10,-10M160,52.5q0,-10 -10,-10M10,121.5q0,10 10,10M160,121.5q0,10 -10,10M10,180q0,10 10,10M160,180q0,10 -10,10M0,106q10,0 10,-10V52.5M170,106q-10,0 -10,-10V52.5M0,106q10,0 10,10V180M170,106q-10,0 -10,10V180"></path><g class="regexp-matches" transform="matrix(1,0,0,1,20,0)"><path d="M0,42.5h27.5M112.5,42.5H130M0,131.5h10M130,131.5H130M0,190h46M79,190H130"></path><g class="match" transform="matrix(1,0,0,1,17.5,0)"><path d="M45,42.5H70"></path><g class="match-fragment" transform="matrix(1,0,0,1,0,0)"><path d="M10,42.5q-10,0 -10,10v21.5q0,10 10,10h35q10,0 10,-10v-21.5q0,-10 -10,-10M55,57.5l5,-5m-5,5l-5,-5"></path><g class="charset" transform="matrix(1,0,0,1,10,0)"><rect class="charset-box" rx="3" ry="3" transform="matrix(1,0,0,1,0,11)" width="35" height="63"></rect><text x="0" y="0" class="charset-label" transform="matrix(1,0,0,1,0,11)"><tspan>One of:</tspan></text><g transform="matrix(1,0,0,1,5,16)"><g class="literal" transform="matrix(1,0,0,1,0,0)"><g class="label"><rect width="25" height="24" rx="3" ry="3"></rect><text x="0" y="0" transform="matrix(1,0,0,1,5,17)"><tspan class="quote">“</tspan><tspan>0</tspan><tspan class="quote">”</tspan></text></g></g><g class="literal" transform="matrix(1,0,0,1,0,29)"><g class="label"><rect width="25" height="24" rx="3" ry="3"></rect><text x="0" y="0" transform="matrix(1,0,0,1,5,17)"><tspan class="quote">“</tspan><tspan>1</tspan><tspan class="quote">”</tspan></text></g></g></g></g></g><g class="match-fragment literal" transform="matrix(1,0,0,1,70,30.5)"><g class="label"><rect width="25" height="24" rx="3" ry="3"></rect><text x="0" y="0" transform="matrix(1,0,0,1,5,17)"><tspan class="quote">“</tspan><tspan>b</tspan><tspan class="quote">”</tspan></text></g></g></g><g class="match" transform="matrix(1,0,0,1,0,89)"><path d="M80,42.5H105"></path><g class="match-fragment" transform="matrix(1,0,0,1,0,0)"><path d="M10,42.5q-10,0 -10,10v21.5q0,10 10,10h70q10,0 10,-10v-21.5q0,-10 -10,-10M90,57.5l5,-5m-5,5l-5,-5"></path><g class="charset" transform="matrix(1,0,0,1,10,0)"><rect class="charset-box" rx="3" ry="3" transform="matrix(1,0,0,1,0,11)" width="70" height="63"></rect><text x="0" y="0" class="charset-label" transform="matrix(1,0,0,1,0,11)"><tspan>One of:</tspan></text><g transform="matrix(1,0,0,1,5,16)"><g class="charset-escape" transform="matrix(1,0,0,1,13.5,0)"><g class="label"><rect width="33" height="24" rx="3" ry="3"></rect><text x="0" y="0" transform="matrix(1,0,0,1,5,17)"><tspan>digit</tspan></text></g></g><g class="charset-range" transform="matrix(1,0,0,1,0,29)"><text x="0" y="0" transform="matrix(1,0,0,1,30,16)">-</text><g class="literal" transform="matrix(1,0,0,1,0,0)"><g class="label"><rect width="25" height="24" rx="3" ry="3"></rect><text x="0" y="0" transform="matrix(1,0,0,1,5,17)"><tspan class="quote">“</tspan><tspan>a</tspan><tspan class="quote">”</tspan></text></g></g><g class="literal" transform="matrix(1,0,0,1,39,0)"><g class="label"><rect width="21" height="24" rx="3" ry="3"></rect><text x="0" y="0" transform="matrix(1,0,0,1,5,17)"><tspan class="quote">“</tspan><tspan>f</tspan><tspan class="quote">”</tspan></text></g></g></g></g></g></g><g class="match-fragment literal" transform="matrix(1,0,0,1,105,30.5)"><g class="label"><rect width="25" height="24" rx="3" ry="3"></rect><text x="0" y="0" transform="matrix(1,0,0,1,5,17)"><tspan class="quote">“</tspan><tspan>h</tspan><tspan class="quote">”</tspan></text></g></g></g><g class="match match-fragment" transform="matrix(1,0,0,1,36,178)"><path d="M10,12q-10,0 -10,10v2q0,10 10,10h33q10,0 10,-10v-2q0,-10 -10,-10M53,27l5,-5m-5,5l-5,-5"></path><g class="escape" transform="matrix(1,0,0,1,10,0)"><g class="label"><rect width="33" height="24" rx="3" ry="3"></rect><text x="0" y="0" transform="matrix(1,0,0,1,5,17)"><tspan>digit</tspan></text></g></g></g></g></g></g><g class="match-fragment escape" transform="matrix(1,0,0,1,302,115)"><g class="label"><rect width="92" height="24" rx="3" ry="3"></rect><text x="0" y="0" transform="matrix(1,0,0,1,5,17)"><tspan>word boundary</tspan></text></g></g></g><path d="M10,127H0M404,127H414"></path><circle cx="0" cy="127" r="5"></circle><circle cx="414" cy="127" r="5"></circle></g></svg> 73 | -------------------------------------------------------------------------------- /img/9-3.svg: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 | <svg 3 | xmlns:svg="http://www.w3.org/2000/svg" 4 | xmlns="http://www.w3.org/2000/svg" 5 | height="180.30476" 6 | version="1.1" 7 | width="217.52544"> 8 | <path 9 | d="m 149.04519,91.683302 h 13.4275" 10 | style="fill:none;stroke:#000000;stroke-width:2.68549919" 11 | id="path4"/> 12 | <path 13 | d="m 135.6177,91.683302 q 13.42749,0 13.42749,13.427498 v 60.42373 q 0,13.42749 -13.42749,13.42749 H 34.911488 q -13.427496,0 -13.427496,-13.42749 V 105.1108 q 0,-13.427498 13.427496,-13.427498" 14 | style="fill:none;stroke:#000000;stroke-width:2.68549919" 15 | id="path6"/> 16 | <path 17 | d="M 149.04519,91.683302 H 135.6177" 18 | style="fill:none;stroke:#000000;stroke-width:2.68549919" 19 | id="path8"/> 20 | <path 21 | d="M 21.483992,91.683302 H 34.911488" 22 | style="fill:none;stroke:#000000;stroke-width:2.68549919" 23 | id="path10"/> 24 | <path 25 | d="M 135.6177,91.683302 H 122.1902" 26 | style="fill:none;stroke:#000000;stroke-width:2.68549919" 27 | id="path12"/> 28 | <path 29 | d="M 34.911488,91.683302 H 48.338983" 30 | style="fill:none;stroke:#000000;stroke-width:2.68549919" 31 | id="path14"/> 32 | <path 33 | d="m 108.76272,91.683302 q 13.42748,0 13.42748,13.427498 v 33.56873 q 0,13.4275 -13.42748,13.4275 H 61.766478 q -13.427495,0 -13.427495,-13.4275 V 105.1108 q 0,-13.427498 13.427495,-13.427498" 34 | style="fill:none;stroke:#000000;stroke-width:2.68549919" 35 | id="path16"/> 36 | <path 37 | d="M 122.1902,91.683302 H 108.76272" 38 | style="fill:none;stroke:#000000;stroke-width:2.68549919" 39 | id="path18"/> 40 | <path 41 | d="M 48.338983,91.683302 H 61.766478" 42 | style="fill:none;stroke:#000000;stroke-width:2.68549919" 43 | id="path20"/> 44 | <desc 45 | id="desc22">Created with Raphaël 2.1.0</desc> 46 | <defs 47 | id="defs24" /> 48 | <rect 49 | x="162.4727" 50 | y="74.898933" 51 | width="33.568737" 52 | height="33.568737" 53 | r="0" 54 | rx="4.0282488" 55 | ry="4.0282488" 56 | style="fill:#dae9e5;stroke:#dae9e5;stroke-width:1.3427496" 57 | id="rect26" /> 58 | <text 59 | x="179.25706" 60 | y="91.683304" 61 | font="10px "Arial"" 62 | style="font-size:16.11299515px;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;text-anchor:middle;fill:#000000;stroke:none;font-family:Arial" 63 | font-size="12px" 64 | id="text28"> 65 | <tspan 66 | dy="6.0423727" 67 | id="tspan30">"b"</tspan> 68 | </text> 69 | <rect 70 | x="34.911488" 71 | y="13.803831" 72 | width="100.70621" 73 | height="151.7307" 74 | r="0" 75 | rx="4.0282488" 76 | ry="4.0282488" 77 | style="fill:none;stroke:#a0a0a0;stroke-width:2.68549919;stroke-dasharray:8.05649719, 2.68549906" 78 | id="rect32" /> 79 | <text 80 | x="64.451981" 81 | y="5.0759606" 82 | font="10px "Arial"" 83 | style="font-size:13.427495px;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;text-anchor:middle;fill:#000000;stroke:none;font-family:Arial" 84 | font-size="10px" 85 | id="text34"> 86 | <tspan 87 | dy="4.6996231" 88 | id="tspan36">Group #1</tspan> 89 | </text> 90 | <rect 91 | x="61.766479" 92 | y="44.687069" 93 | width="46.996235" 94 | height="93.99247" 95 | r="0" 96 | rx="4.0282488" 97 | ry="4.0282488" 98 | style="fill:#cbcbba;stroke:#cbcbba;stroke-width:1.3427496" 99 | id="rect38" /> 100 | <text 101 | x="85.264595" 102 | y="35.959198" 103 | font="10px "Arial"" 104 | style="font-size:13.427495px;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;text-anchor:middle;fill:#000000;stroke:none;font-family:Arial" 105 | font-size="10px" 106 | id="text40"> 107 | <tspan 108 | dy="4.6996231" 109 | id="tspan42">One of:</tspan> 110 | </text> 111 | <rect 112 | x="68.480232" 113 | y="98.397057" 114 | width="33.568737" 115 | height="33.568737" 116 | r="0" 117 | rx="4.0282488" 118 | ry="4.0282488" 119 | style="fill:#dae9e5;stroke:#dae9e5;stroke-width:1.3427496" 120 | id="rect44" /> 121 | <text 122 | x="85.264595" 123 | y="115.18143" 124 | font="10px "Arial"" 125 | style="font-size:16.11299515px;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;text-anchor:middle;fill:#000000;stroke:none;font-family:Arial" 126 | font-size="12px" 127 | id="text46"> 128 | <tspan 129 | dy="6.0423727" 130 | id="tspan48">"1"</tspan> 131 | </text> 132 | <rect 133 | x="68.480232" 134 | y="51.400818" 135 | width="33.568737" 136 | height="33.568737" 137 | r="0" 138 | rx="4.0282488" 139 | ry="4.0282488" 140 | style="fill:#dae9e5;stroke:#dae9e5;stroke-width:1.3427496" 141 | id="rect50" /> 142 | <text 143 | x="85.264595" 144 | y="68.185188" 145 | font="10px "Arial"" 146 | style="font-size:16.11299515px;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;text-anchor:middle;fill:#000000;stroke:none;font-family:Arial" 147 | font-size="12px" 148 | id="text52"> 149 | <tspan 150 | dy="6.0423727" 151 | id="tspan54">"0"</tspan> 152 | </text> 153 | <path 154 | d="M 8.0564968,91.683302 H 21.483992" 155 | style="fill:none;stroke:#000000;stroke-width:2.68549919" 156 | id="path56"/> 157 | <circle 158 | cx="10" 159 | cy="81" 160 | r="5" 161 | style="fill:#6b6659;stroke:#000000;stroke-width:2" 162 | id="circle58" 163 | transform="matrix(1.3427496,0,0,1.3427496,-5.3709984,-17.079408)" /> 164 | <path 165 | d="M 209.46892,91.683302 H 196.04143" 166 | style="fill:none;stroke:#000000;stroke-width:2.68549919" 167 | id="path60"/> 168 | <circle 169 | cx="160" 170 | cy="81" 171 | r="5" 172 | style="fill:#6b6659;stroke:#000000;stroke-width:2" 173 | id="circle62" 174 | transform="matrix(1.3427496,0,0,1.3427496,-5.3709984,-17.079408)" /> 175 | </svg> 176 | -------------------------------------------------------------------------------- /img/qr_alipay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wizardforcel/eloquent-js-3e-zh/097ef4522075bcdf3e167e38cce1b7fae034579c/img/qr_alipay.png -------------------------------------------------------------------------------- /styles/ebook.css: -------------------------------------------------------------------------------- 1 | /* GitHub stylesheet for MarkdownPad (http://markdownpad.com) */ 2 | /* Author: Nicolas Hery - http://nicolashery.com */ 3 | /* Version: b13fe65ca28d2e568c6ed5d7f06581183df8f2ff */ 4 | /* Source: https://github.com/nicolahery/markdownpad-github */ 5 | 6 | /* RESET 7 | =============================================================================*/ 8 | 9 | html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video { 10 | margin: 0; 11 | padding: 0; 12 | border: 0; 13 | } 14 | 15 | /* BODY 16 | =============================================================================*/ 17 | 18 | body { 19 | font-family: Helvetica, arial, freesans, clean, sans-serif; 20 | font-size: 14px; 21 | line-height: 1.6; 22 | color: #333; 23 | background-color: #fff; 24 | padding: 20px; 25 | max-width: 960px; 26 | margin: 0 auto; 27 | } 28 | 29 | body>*:first-child { 30 | margin-top: 0 !important; 31 | } 32 | 33 | body>*:last-child { 34 | margin-bottom: 0 !important; 35 | } 36 | 37 | /* BLOCKS 38 | =============================================================================*/ 39 | 40 | p, blockquote, ul, ol, dl, table, pre { 41 | margin: 15px 0; 42 | } 43 | 44 | /* HEADERS 45 | =============================================================================*/ 46 | 47 | h1, h2, h3, h4, h5, h6 { 48 | margin: 20px 0 10px; 49 | padding: 0; 50 | font-weight: bold; 51 | -webkit-font-smoothing: antialiased; 52 | } 53 | 54 | h1 tt, h1 code, h2 tt, h2 code, h3 tt, h3 code, h4 tt, h4 code, h5 tt, h5 code, h6 tt, h6 code { 55 | font-size: inherit; 56 | } 57 | 58 | h1 { 59 | font-size: 24px; 60 | border-bottom: 1px solid #ccc; 61 | color: #000; 62 | } 63 | 64 | h2 { 65 | font-size: 18px; 66 | color: #000; 67 | } 68 | 69 | h3 { 70 | font-size: 14px; 71 | } 72 | 73 | h4 { 74 | font-size: 14px; 75 | } 76 | 77 | h5 { 78 | font-size: 14px; 79 | } 80 | 81 | h6 { 82 | color: #777; 83 | font-size: 14px; 84 | } 85 | 86 | body>h2:first-child, body>h1:first-child, body>h1:first-child+h2, body>h3:first-child, body>h4:first-child, body>h5:first-child, body>h6:first-child { 87 | margin-top: 0; 88 | padding-top: 0; 89 | } 90 | 91 | a:first-child h1, a:first-child h2, a:first-child h3, a:first-child h4, a:first-child h5, a:first-child h6 { 92 | margin-top: 0; 93 | padding-top: 0; 94 | } 95 | 96 | h1+p, h2+p, h3+p, h4+p, h5+p, h6+p { 97 | margin-top: 10px; 98 | } 99 | 100 | /* LINKS 101 | =============================================================================*/ 102 | 103 | a { 104 | color: #4183C4; 105 | text-decoration: none; 106 | } 107 | 108 | a:hover { 109 | text-decoration: underline; 110 | } 111 | 112 | /* LISTS 113 | =============================================================================*/ 114 | 115 | ul, ol { 116 | padding-left: 30px; 117 | } 118 | 119 | ul li > :first-child, 120 | ol li > :first-child, 121 | ul li ul:first-of-type, 122 | ol li ol:first-of-type, 123 | ul li ol:first-of-type, 124 | ol li ul:first-of-type { 125 | margin-top: 0px; 126 | } 127 | 128 | ul ul, ul ol, ol ol, ol ul { 129 | margin-bottom: 0; 130 | } 131 | 132 | dl { 133 | padding: 0; 134 | } 135 | 136 | dl dt { 137 | font-size: 14px; 138 | font-weight: bold; 139 | font-style: italic; 140 | padding: 0; 141 | margin: 15px 0 5px; 142 | } 143 | 144 | dl dt:first-child { 145 | padding: 0; 146 | } 147 | 148 | dl dt>:first-child { 149 | margin-top: 0px; 150 | } 151 | 152 | dl dt>:last-child { 153 | margin-bottom: 0px; 154 | } 155 | 156 | dl dd { 157 | margin: 0 0 15px; 158 | padding: 0 15px; 159 | } 160 | 161 | dl dd>:first-child { 162 | margin-top: 0px; 163 | } 164 | 165 | dl dd>:last-child { 166 | margin-bottom: 0px; 167 | } 168 | 169 | /* CODE 170 | =============================================================================*/ 171 | 172 | pre, code, tt { 173 | font-size: 12px; 174 | font-family: Consolas, "Liberation Mono", Courier, monospace; 175 | } 176 | 177 | code, tt { 178 | margin: 0 0px; 179 | padding: 0px 0px; 180 | white-space: nowrap; 181 | border: 1px solid #eaeaea; 182 | background-color: #f8f8f8; 183 | border-radius: 3px; 184 | } 185 | 186 | pre>code { 187 | margin: 0; 188 | padding: 0; 189 | white-space: pre; 190 | border: none; 191 | background: transparent; 192 | } 193 | 194 | pre { 195 | background-color: #f8f8f8; 196 | border: 1px solid #ccc; 197 | font-size: 13px; 198 | line-height: 19px; 199 | overflow: auto; 200 | padding: 6px 10px; 201 | border-radius: 3px; 202 | } 203 | 204 | pre code, pre tt { 205 | background-color: transparent; 206 | border: none; 207 | } 208 | 209 | kbd { 210 | -moz-border-bottom-colors: none; 211 | -moz-border-left-colors: none; 212 | -moz-border-right-colors: none; 213 | -moz-border-top-colors: none; 214 | background-color: #DDDDDD; 215 | background-image: linear-gradient(#F1F1F1, #DDDDDD); 216 | background-repeat: repeat-x; 217 | border-color: #DDDDDD #CCCCCC #CCCCCC #DDDDDD; 218 | border-image: none; 219 | border-radius: 2px 2px 2px 2px; 220 | border-style: solid; 221 | border-width: 1px; 222 | font-family: "Helvetica Neue",Helvetica,Arial,sans-serif; 223 | line-height: 10px; 224 | padding: 1px 4px; 225 | } 226 | 227 | /* QUOTES 228 | =============================================================================*/ 229 | 230 | blockquote { 231 | border-left: 4px solid #DDD; 232 | padding: 0 15px; 233 | color: #777; 234 | } 235 | 236 | blockquote>:first-child { 237 | margin-top: 0px; 238 | } 239 | 240 | blockquote>:last-child { 241 | margin-bottom: 0px; 242 | } 243 | 244 | /* HORIZONTAL RULES 245 | =============================================================================*/ 246 | 247 | hr { 248 | clear: both; 249 | margin: 15px 0; 250 | height: 0px; 251 | overflow: hidden; 252 | border: none; 253 | background: transparent; 254 | border-bottom: 4px solid #ddd; 255 | padding: 0; 256 | } 257 | 258 | /* TABLES 259 | =============================================================================*/ 260 | 261 | table th { 262 | font-weight: bold; 263 | } 264 | 265 | table th, table td { 266 | border: 1px solid #ccc; 267 | padding: 6px 13px; 268 | } 269 | 270 | table tr { 271 | border-top: 1px solid #ccc; 272 | background-color: #fff; 273 | } 274 | 275 | table tr:nth-child(2n) { 276 | background-color: #f8f8f8; 277 | } 278 | 279 | /* IMAGES 280 | =============================================================================*/ 281 | 282 | img { 283 | max-width: 100% 284 | } --------------------------------------------------------------------------------