第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必须理解这些细节,希望你能花时间从头到尾读完这本书。不过,不要一上来就想着这样做。假如某一节内容你怎么也看不懂,可以先跳过去。等你对这门语言有了一个整体的了解时,可以再回来了解那些细节。