JavaScript权威指南(原书第7版) [美] David Flanagan 著,李松峰 译 机械工业出版社

  编辑推荐

  适读人群 :本书适合希望学习Web编程语言的初、中级程序员和希望精通JavaScript的程序员阅读。

  近25年来,这本“犀牛书”凭着完整的内容、细致的讲解以及海量针对性的示例而受到全球读者的一致好评。经过几代前端人的口口相传,成为了名副其实的JavaScript权威指南!

  本版已经更新到涵盖JavaScript的2020版。书中令人深思、富有启发性的示例随处可见。

  这本“犀牛书”影响着每一位前端人,一直是几十万JavaScript程序员必读的技术著作,在很多工程师心目中有着至高无上的地位。如果你由于种种原因错过了它之前的版本,那一定不要再错过这一版了!

  内容简介

  本书介绍JavaScript语言和由浏览器与Node实现的JavaScript API。本书适合有一定编程经验、想学习JavaScript读者,也适合已经在使用JavaScript但希望更深入地理解进而真正掌握这门语言的程序员。

  本书的目标是全面地讲解JavaScript语言,对JavaScript程序中可能用到的重要的客户端API和服务器端API提供深入的介绍。本书篇幅较长,内容非常详尽,相信认真研究本书的读者都能获益良多。

  作者简介

  David Flanagan从1995起就开始使用JavaScript并写作本书的第1版。他拥有麻省理工学院计算机科学与工程学位,目前是VMware的一名软件工程师。

  精彩书评

  ★“本书包含的JavaScript知识是前所未有的。作者对这门语言有极其精深的理解,跟着作者的脚步,你将穿过JavaScript的重重迷雾,探索令人叹为观止的真知,让你的JavaScript代码质量和编程效率更上一层楼,最终折服于本书的惊人魅力。”

  —Schalk Neethling,MDN Web Docs的资深前端工程师

  ★“David Flanagan带领读者领略了JavaScript的全景,包括语言及其生态,展现在读者眼前的是一幅巨细无遗的美丽画卷。”

  —Sarah Wachs,前端开发者、Women Who Code Berlin负责人

  ★“任何有志于最大限度地利用JavaScript特性(包括最前沿的特性)来高效产出代码的开发者,都能通过这本全面且权威的著作收获满满。”

  —Brian Sletten,Bosatsu Consulting总裁

  目录

  ●第1章 JavaScript简介5

  1.1 探索JavaScript7

  1.2 Hello World8

  1.3 JavaScript之旅9

  1.4 示例:字符频率柱形图14

  1.5 小结17

  ●第2章 词法结构18

  2.1 JavaScript程序的文本18

  2.2 注释19

  2.3 字面量19

  2.4 标识符和保留字19

  2.5 Unicode20

  2.6 可选的分号21

  2.7 小结23

  ●第3章 类型、值和变量24

  3.1 概述与定义24

  3.2 数值26

  3.3 文本32

  3.4 布尔值38

  3.5 null与undefined39

  3.6 符号40

  3.7 全局对象41

  3.8 不可修改的原始值与可修改的对象引用42

  3.9 类型转换44

  3.10 变量声明与赋值51

  3.11 小结58

  ●第4章 表达式与操作符59

  4.1 主表达式59

  4.2 对象和数组初始化程序60

  4.3 函数定义表达式61

  4.4 属性访问表达式62

  4.5 调用表达式64

  4.6 对象创建表达式65

  4.7 操作符概述66

  4.8 算术表达式70

  4.9 关系表达式75

  4.10 逻辑表达式79

  4.11 赋值表达式82

  4.12 求值表达式83

  4.13 其他操作符86

  4.14 小结91

  ●第5章 语句92

  5.1 表达式语句93

  5.2 复合语句与空语句93

  5.3 条件语句94

  5.4 循环语句99

  5.5 跳转语句106

  5.6 其他语句113

  5.7 声明117

  5.8 小结119

  ●第6章 对象120

  6.1 对象简介120

  6.2 创建对象121

  6.3 查询和设置属性124

  6.4 删除属性128

  6.5 测试属性129

  6.6 枚举属性130

  6.7 扩展对象131

  6.8 序列化对象133

  6.9 对象方法133

  6.10 对象字面量扩展语法135

  6.11 小结141

  ●第7章 数组143

  7.1 创建数组144

  7.2 读写数组元素146

  7.3 稀疏数组148

  7.4 数组长度148

  7.5 添加和删除数组元素149

  7.6 迭代数组150

  7.7 多维数组151

  7.8 数组方法152

  7.9 类数组对象163

  7.10 作为数组的字符串165

  7.11 小结166

  ●第8章 函数167

  8.1 定义函数167

  8.2 调用函数172

  8.3 函数实参与形参177

  8.4 函数作为值184

  8.5 函数作为命名空间187

  8.6 闭包188

  8.7 函数属性、方法与构造函数192

  8.8 函数式编程196

  8.9 小结201

  ●第9章 类202

  9.1 类和原型203

  9.2 类和构造函数204

  9.3 使用class关键字的类209

  9.4 为已有类添加方法215

  9.5 子类216

  9.6 小结226

  ●第10章 模块227

  10.1 基于类、对象和闭包的模块227

  10.2 Node中的模块230

  10.3 ES6中的模块232

  10.4 小结242

  ●第11章 JavaScript标准库243

  11.1 集合与映射244

  11.2 定型数组与二进制数据249

  11.3 正则表达式与模式匹配255

  11.4 日期与时间273

  11.5 Error类276

  11.6 JSON序列化与解析277

  11.7 国际化API280

  11.8 控制台API287

  11.9 URL API290

  11.10 计时器293

  11.11 小结294

  ●第12章 迭代器与生成器295

  12.1 迭代器原理296

  12.2 实现可迭代对象296

  12.3 生成器300

  12.4 高级生成器特性303

  12.5 小结306

  ●第13章 异步JavaScript307

  13.1 使用回调的异步编程307

  13.2 期约(Promise)311

  13.3 async和await330

  13.4 异步迭代332

  13.5 小结338

  ●第14章 元编程340

  14.1 属性的特性340

  14.2 对象的可扩展能力345

  14.3 prototype特性346

  14.4 公认符号347

  14.5 模板标签354

  14.6 反射API356

  14.7 代理对象359

  14.8 小结365

  ●第15章 浏览器中的JavaScript367

  15.1 Web编程基础369

  15.2 事件382

  15.3 操作DOM391

  15.4 操作CSS406

  15.5 文档几何与滚动412

  15.6 Web组件416

  15.7 可伸缩矢量图形427

  15.8 < canvas > 与图形434

  15.9 Audio API453

  15.10 位置、导航与历史455

  15.11 网络463

  15.12 存储479

  15.13 工作线程与消息传递490

  15.14 示例:曼德布洛特集合496

  15.15 小结及未来阅读建议508

  ●第16章 Node服务器端JavaScript515

  16.1 Node编程基础516

  16.2 Node默认异步520

  16.3 缓冲区523

  16.4 事件与EventEmitter525

  16.5 流526

  16.6 进程、CPU和操作系统细节536

  16.7 操作文件537

  16.8 HTTP客户端与服务器547

  16.9 非HTTP网络服务器及客户端551

  16.10 操作子进程554

  16.11 工作线程558

  16.12 小结566

  ●第17章 JavaScript工具和扩展568

  17.1 使用ESLint检查代码569

  17.2 使用Prettier格式化代码570

  17.3 使用Jest做单元测试570

  17.4 使用npm管理依赖包573

  17.5 代码打包574

  17.6 使用Babel转译576

  17.7 JSX:JavaScript中的标记表达式577

  17.8 使用Flow检查类型581

  17.9 小结595

  精彩书摘

  第1章

  JavaScript简介

  JavaScript是Web编程语言。绝大多数网站都使用JavaScript,所有现代Web浏览器(无论是桌面、平板还是手机浏览器,书中以后统称为浏览器)都包含JavaScript解释器,这让JavaScript成为有史以来部署最广泛的编程语言。过去十年,Node.js让浏览器之外的JavaScript编程成为可能,Node的巨大成功意味着JavaScript如今也是软件开发者最常用的编程语言。无论你是从头开始,还是已经在工作中使用JavaScript,本书都能帮你掌握这门语言。

  如果你已经熟悉其他编程语言,那有必要知道JavaScript是一门高级、动态、解释型编程语言,非常适合面向对象和函数式编程风格。JavaScript的变量是无类型的,它的语法大致与Java相仿,但除此之外这两门语言之间没有任何关系。JavaScript从Scheme借鉴了一类(first class)函数,从不太知名的Self借鉴了基于原型的继承。但要阅读本书或学习JavaScript不需要了解这些语言,也不必熟悉这些术语。

  JavaScript这个名字相当有误导性。除了表面上语法相似,它与Java是完全不同的两门编程语言。JavaScript经历了很长时间才从一门脚本语言成长为一门健壮高效的通用语言,适合开发代码量巨大的重要软件工程和项目。

  JavaScript:名字、版本和模式

  JavaScript是Netscape在Web诞生初期创造的。严格来讲,JavaScript是经Sun Microsystems(现Oracle)授权使用的一个注册商标,用于描述Netscape(现Mozilla)对这门语言的实现。Netscape将这门语言提交给Ecma International译注1进

  行标准化,由于商标问题,这门语言的标准版本沿用了别扭的名字“ECMAScript”。实践中,大家仍然称这门语言为JavaScript。本书在讨论这门语言的标准及版本时使用“ECMAScript”及其缩写“ES”。

  2010年以来,几乎所有浏览器都支持ECMAScript标准第5版。本书以ES5作为兼容性基准,不再讨论这门语言的更早版本。ES6发布于2015年,增加了重要的新特性(包括类和模块语法)。这些新特性把JavaScript从一门脚本语言转变为一门适合大规模软件工程的严肃、通用语言。从ES6开始,ECMAScript规范改为每年发布一次,语言的版本也以发布的年份来标识(ES2016、ES2017、ES2018、ES2019和ES2020)。

  随着JavaScript的发展,语言设计者也在尝试纠正早期(ES5之前)版本中的缺陷。为了保证向后兼容,无论一个特性的问题有多严重,也不能把它删除。但在ES5及之后,程序可以选择切换到JavaScript的严格模式。在这种模式下,一些早期的语言错误会得到纠正。本书5.6.3节将介绍切换到这种模式使用的use strict指令。该节也会总结传统JavaScript与严格JavaScript的区别。在ES6及之后,使用新语言特性经常会隐式触发严格模式。例如,如果使用ES6的class关键字或者创建ES6模块,类和模块中的所有代码都会自动切换到严格模式。在这些上下文中,不能使用老旧、有缺陷的特性。本书会介绍JavaScript的传统特性,但会细心地指出它们在严格模式下无法使用。

  为了好用,每种语言都必须有一个平台或标准库,用于执行包括基本输入和输出在内的基本操作。核心JavaScript语言定义了最小限度的API,可以操作数值、文本、数组、集合、映射等,但不包含任何输入和输出功能。输入和输出(以及更复杂的特性,如联网、存储和图形处理)是内嵌JavaScript的“宿主环境”的责任。

  浏览器是JavaScript最早的宿主环境,也是JavaScript代码最常见的运行环境。浏览器环境允许JavaScript代码从用户的鼠标和键盘或者通过发送HTTP请求获取输入,也允许JavaScript代码通过HTML和CSS向用户显示输出。

  2010年以后,JavaScript代码又有了另一个宿主环境。与限制JavaScript只能使用浏览器提供的API不同,Node给予了JavaScript访问整个操作系统的权限,允许JavaScript程序读写文件、通过网络发送和接收数据,以及发送和处理HTTP请求。Node是实现Web服务器的一种流行方式,也是编写可以替代shell脚本的简单实用脚本的便捷工具。

  本书大部分内容聚焦JavaScript语言本身。第11章讲述JavaScript标准库,第15章介绍浏览器宿主环境,第16章介绍Node宿主环境。

  全书首先从底层基础讲起,然后逐步过渡到高级及更高层次的抽象。这些章节的安排多多少少考虑了阅读的先后次序。不过学习一门新语言不可能是一个线性的过程,对一门语言的描述也不可能是线性的。毕竟每个语言特性都可能与其他特性有关系。本书的交叉引用非常多,有的指向前面的章节,有的指向后面的章节。本章会先快速地过一遍这门语言,介绍一些对理解后续章节的深入剖析有帮助的关键特性。如果你是一名JavaScript程序员,可以跳过这一章(但在跳过之前,读一读本章末尾的示例1-1应该会让你很开心)。

  1.1 探索JavaScript

  学习一门新编程语言,很重要的是尝试书中的示例,然后修改这些示例并再次运行,以验证自己对这门语言的理解。为此,你需要一个JavaScript解释器。

  要尝试少量JavaScript代码,最简单的方式就是打开浏览器的Web开发者工具(按F12、Ctrl+Shift+I或Command+Option+I),然后选择Console(控制台)标签页。之后就可以在提示符后面输入代码,并在输入的同时看到结果。浏览器开发者工具经常以一组面板的形式出现在浏览器窗口底部或右侧,不过也可以把它们拆分为独立的窗口(如图1-1所示),这样通常更加方便。

  图1-1:Firefox开发者工具中的JavaScript控制台

  尝试JavaScript代码的另一种方式是下载并安装Node(下载地址https://nodejs.org/)。安装完Node之后,可以打开终端窗口,然后输入node并回车,像下面这样开始交互式JavaScript会话:

  1.2 Hello World

  当需要试验更长的代码块时,这种以行为单位的交互环境可能就不合适了。此时可能需要使用一个文本编辑器来编写代码。写完之后,可以把JavaScript代码复制粘贴到JavaScript控制台或Node会话。或者,可以把代码保存成一个文件(保存JavaScript代码的文件通常使用扩展名.js),再使用Node来运行这个JavaScript代码文件:

  如果像这样在非交互模式下使用Node,那它不会自动打印所有运行的代码的值,因此你需要自己打印。可以使用console.log()函数在终端窗口或在浏览器开发者工具的控制台中显示文本和其他JavaScript值。例如,如果你创建一个hello.js文件,其中包含这行代码:

  并使用node hello.js来执行这个文件,可以看到打印出的消息“Hello World!”。

  如果你想在浏览器的JavaScript控制台看到同样的消息,则需要创建一个新文件,例如叫hello.html,然后把以下内容放进去:

  然后像下面这样在浏览器中使用file://URL加载hello.html:

  打开开发者工具窗口,就可以在控制台中看到这个问候了。

  1.3 JavaScript之旅

  本节通过代码示例对JavaScript语言做一个简单介绍。在本章之后,我们会深入JavaScript的最底层。第2章将解释JavaScript注释、分号和Unicode字符集。第3章会更有意思一些,将解释JavaScript变量以及可以赋给这些变量的值。

  下面我们来看一些例子,其中包含了第2章和第3章的重点内容。

  JavaScript程序可以操作的另外两个非常重要的类型是对象和数组,分别将在第6章和第7章中介绍。不过,因为它们实在太重要了,所以在那两章之前你也会多次看到它们。

  代码示例中的注释语法

  你可能注意到了,前面代码中有的注释是以箭头(=>)开头的。这些箭头是在模拟交互式JavaScript环境(例如浏览器控制台),在纸质书上展示注释前面的代码产生的值。

  // =>注释也充当一种断言,我曾写过一个工具,专门测试代码并验证它能产生这种注释中指定的值。这应该(我希望)可以减少本书代码的错误。

  有两种相关的注释/断言风格。如果你看到// a == 42形式的注释,那意味着在注释前面的代码运行之后,变量a的值将是42。如果你看到// !形式的注释,那意味着注释前面的代码抛出了异常(而注释中!后面的内容通常会解释抛出的是什么异常)。

  这样的注释在本书中随处可见。

  这里展示的在中括号内罗列出数组元素以及在大括号中将对象属性名映射为属性值的语法被称为初始化表达式(initializer expression),也是第4章的一个主题。表达式在JavaScript中就是一个短语,可以求值产生一个值。例如,使用.或[]引用对象属性的值或数组元素就是表达式。

  JavaScript构造表达式的一个最常见方式是使用操作符:

  如果JavaScript表达式像短语,那JavaScript语句就像完整的句子。语句是第5章的主题。简单地说,表达式只用于计算值,什么也不做,即不以任何方式改变程序的状态。而语句没有值,但却会改变状态。前面我们已经看到了变量声明和赋值语句。另外还有一类语句叫控制结构,例如条件和循环。在介绍完函数之后,我们会看到它们的示例。

  函数是一个有名字、有参数的JavaScript代码块,只要定义一次就可以反复调用。第8章会正式介绍函数,但在之前你也会多次看到它们,就像对象和数组一样。下面是几个简单的示例:

  ES6及之后,有一种定义函数的简写方式。这种简洁的语法使用=>来分隔参数列表和函数体,因此以这种方式定义的函数被称为箭头函数。箭头函数经常用于把一个未命名函数作为参数传给另一个函数。前面的函数用箭头函数重写后如下所示:

  在通过对象使用函数时,我们称其为方法:

  现在,按照约定,我们再介绍几个函数,它们的函数体演示了常用的JavaScript控制结构语句:

  JavaScript支持面向对象的编程风格,但与“经典的”面向对象编程语言非常不一样。第9章将详细介绍JavaScript中的面向对象编程,包含很多示例。下面是一个非常简单的示例,演示了如何定义一个JavaScript类以表示几何平面上的一个点。作为这个类的实例的对象有一个方法,叫作distance(),用于计算该点与原点的距离:

  对JavaScript基础语法和能力的介绍之旅到此就要结束了。但本书后续还有很多章,分别自成一体地介绍了这门语言的其他特性。

  第10章 模块

  展示文件或脚本中的JavaScript代码如何使用其他文件和脚本中定义的JavaScript函数和类。

  第11章 JavaScript标准库

  展示所有JavaScript程序都可以使用的内置函数和类,包括像映射、集合这样重要的数据结构,还有用于文本模式匹配的正则表达式类,以及序列化JavaScript数据结构的函数,等等。

  第12章 迭代器与生成器

  解释for/of循环的原理,以及如何定义可以在for/of中使用的类。该章还介绍生成器函数及yield语句。

  第13章 异步JavaScript

  该章深入探讨JavaScript的异步编程,涵盖回调与事件、基于期约的API,以及async和await关键字。虽然核心JavaScript语言并非异步的,但浏览器和Node中的API默认都是异步的。该章解释使用这些API的技术。

  第14章 元编程

  介绍一些高级JavaScript特性,为其他JavaScript程序员编写代码库的读者可能会感兴趣。

  第15章 浏览器中的JavaScript

  介绍浏览器宿主环境,解释浏览器如何执行JavaScript代码,涵盖浏览器定义的大多数重要API。该章是迄今为止这本书中最长的一章。

  第16章 Node服务器端JavaScript

  介绍Node宿主环境,涵盖基础编程模型、数据结构和需要理解的最重要的API。

  第17章 JavaScript工具和扩展

  涵盖广泛应用并有效提升开发者效率的工具及语言扩展。

  1.4 示例:字符频率柱形图

  本章最后展示一个虽短但并不简单的JavaScript程序。示例1-1是一个Node程序,它从标准输入读取文本,计算该文本的字符频率柱形图,然后打印出来。可以像下面这样调用这个程序,分析它自己源代码的字符频率:

  这个示例使用了一些高级JavaScript特性,有意让大家看看真正的JavaScript程序长什么样。不过,即使你不理解这些代码也没关系,其中用到的特性本书后续章节都会介绍。

  示例1-1:使用JavaScript计算字符频率柱形图

  1.5 小结

  本书以自底向上的方式解释JavaScript。这意味着要先从较低层次的注释、标识符、变量和类型讲起,然后在此基础上介绍表达式、语句、对象和函数。接着介绍更高层次的语言抽象,例如类和模块。本书的书名包含“权威”二字是认真的,接下来的章节对这门语言的解释可能详细得令人反感。然而,想要真正掌握JavaScript必须理解这些细节,希望你能花时间从头到尾读完这本书。不过,不要一上来就想着这样做。假如某一节内容你怎么也看不懂,可以先跳过去。等你对这门语言有了一个整体的了解时,可以再回来了解那些细节。

  前言/序言

  本书介绍JavaScript语言和由浏览器与Node实现的JavaScript API。本书适合有一定编程经验、想学习JavaScript读者,也适合已经在使用JavaScript但希望更深入地理解进而真正掌握这门语言的程序员。

  本书的目标是全面地讲解JavaScript语言,对JavaScript程序中可能用到的重要的客户端API和服务器端API提供深入的介绍。本书篇幅较长,内容非常详尽,相信认真研究本书的读者都能获益良多。

  【译者序】

  翻译这本“犀牛书”是我十几年来的一个夙愿。尽管由于种种原因错过了原书第5版和第6版,但终于还是得偿所愿。2021年是我从事技术翻译的第十五个年头。因此,本书也是我倾注多年经验翻译而成的。

  虽然翻译本书前前后后花了6个多月,但囿于工作和生活的压力,我确实做不到对书中每一句话都反复推敲。我当然知道“好译文是改出来的”,但翻译也是一门“遗憾的艺术”,所以我的翻译肯定不是完美无缺的。如果要我对这本书(或者说对我近十年来翻译出版的所有技术专著,包括2020年上市的“红宝书”第4版)的翻译过程打个比方,我想最贴切的比喻莫过于即兴视奏:面对一本从未见过的乐谱,你必须从奏响第一个音符开始,一气呵成地把整首曲子演奏完。演奏开始后,唯一的目标就是全神贯注,心无旁骛,快速看懂每个音符、每个节奏,尽最大努力把内容按照原样准确无误地呈现出来。当然,不同的是,翻译过程中虽然也有假想的读者存在,但这些“读者”并不妨碍我在发觉之前章节的翻译有问题时回过头去修正。

  这其实正是我期望的理想翻译状态,即“一边阅读,一边翻译”。技术图书翻译属于非文学翻译或者技术翻译的范畴。技术翻译的主要目的是译文准确、通顺,确保其当时当下的实用性。除此之外,对文笔或修辞的技巧无须做过高要求。一本优秀的技术图书,最终让读者受益的是它的内容和思想,而不是它的文字。文字作为形式或载体固然重要,但从译者的角度来说,不让自己的文字成为传达内容的阻碍就是最大的贡献。回顾我的技术翻译生涯,十几年来从未间断翻译实践。随着翻译经验的不断积累,我对翻译的认知也经历了深入浅出的过程。从最初的“翻译即翻译”,到后来的“翻译即写作”,再到如今的“翻译即阅读”,经历了几次较大的扬弃。“翻译”和“写作”,强调的其实是“转换”和“表达”,而“阅读”强调的则是对原文的理解。某种程度上,这可能也说明自己已经比较成功地解决了“转换”和“表达”的问题,从而可以把精力更多地放到“阅读”和“理解”上。

  JavaScript无疑是一门成功的语言,而且是世界上使用最多的语言。这本“犀牛书”在很多工程师心目中有着至高无上的地位。如果你由于种种原因错过了它之前的版本,那一定不要再错过这一版了。在我看来,尽管市面上讲解JavaScript语言和技术的专著层出不穷,但像这本书这样能够贴近ECMAScript和W3C规范的著作并不多见。ECMAScript和W3C规范是用英文写的,这对母语为中文的工程师无疑是个巨大的障碍。希望本书在字里行间流露出的与各种规范千丝万缕的联系,能够时刻提醒每一位读者多花一些时间去研究语言本身和规范本身。这不仅仅是个“知其然,也知其所以然”的问题,更是一个追赶和超越的问题。相信再过5年、10年、20年,中文开发者社区一定能够涌现出更多屹立在时代潮头的工程师和作者。

  李松峰