本书该部分(第2 – 12章)描述Ja v aScript语言核心。这部分是该语言的主要参考资料。学习之初通读一遍该部分,以 后在遇到JavaScript的难点时 , 回到这里重新查阅相关内容以巩固知识的掌握:
第2章 词法结构
第3章 类型、值和变址第4章 表达式和运算符第5章 语旬
第6章 对象第7章 数组第8章 函数
第9章 类和模块
第10章 正则表达式的模式匹配第11章 JavaScript的子集和扩展第12章 服务器端JavaScript
第二章:词法结构
编程语言的词法结构是一套基础性规则,用来描述如何使用这门语言来编写程序。作为 语法的基础,它规定了诸如变址名是什么样的、怎么写注释,以及程序语句之间如何分 隔等规则。本章用很短的篇幅来介绍JavaScript的词法结构。
2.1 字符集
JavaScript程序是用Unicode 字符集编写的。Unicode 是ASCII 和Latin- I的超集, 并支持地球上几乎所有在用的语言。ECMAScript 3要求Ja vaScri pt的实现必须支持Unicode 2.1及后续版本, EC MAScript 5则要求支持Unicode 3及后续版本。可以参考3.2 节的 "边栏” 来了解更多关于Unicode 和JavaScript的信息。
2.1.1 区分大小写
J a v aS c r i p t是区分大小写的语言。也就是说 , 关键字、变址、函数名和所有的标识符 (i d e n t ifi e r ) 都必须采取一致的大小写形式。比如, 关键字 "w hi 1 e " 必须写成"while" , 而不能写成 "Whil e" 或 者 "WH IL E" 。 同样, "on l i ne" 、 "On li ne" 、 "On li ne" 和 "ONLI NE" 是4个不同的变最名。
但需要注意的是, HT ML并不区分大小写(尽管XHT ML区分大小写)。由于它和客户端Ja vaSc ript 联系紧密, 因此这点区别很容易混淆译注1 。 许多客户端Ja vaSc ri pt对象和属性与它们所表示的HTMU 示签和属性同名。在 HT ML中, 这些标签和属性名可以使用大写也可以是小写, 而在JavaScript 中则必须是小写。例如, 在HTML中设置事件处理程序时, onc li ck 属性可以写成onCli c k, 但在JavaScript 代码(或者XHTML 文档)中, 必须使用小写的oncl i ck 。
2.1.1 空格、换行符 和格式控制符
JavaScript会忽略程序中标 识 ( token ) 译注2之间的空格。多数情况下, JavaScript同样会忽略换行符 (2.5节提到了一种意外 情形)。由千可以在代码中随意使用空格和换行 , 因此可以采用整齐、一致的缩进来形成统一的编码风格,从而提高代码的可读性。
除了可以识别普通的空格符 ( \uoo20 ) , JavaScript还可以识别如下这些表 示空格的字符: 水平制表符 ( \uooog ) 、垂直制表符 ( \uOOOB) 、换页符 ( \uoooc ) 、不中断空白(\ uOOAO)、 字节序标记 ( \ uFEFF) , 以及在Unicode中所有Zs类别的字符译注3。 JavaScript 将如下字符识别为行结束符: 换行符 ( \uoooA ) , 回车符 ( \uOOOD) , 行分隔符
(\u2028) , 段分隔符 ( \ u2 029) 。回车符加换行符在一起被解析为一个单行结束符。
Unico de格式控制字符(Cf类译注4 ) , 比如“从右至左书写标记" (\u200F)和“从左至右书写标记" (\u200E ) 译注s . 控制着文本的视觉显示,这对于一些非英语文本的正确显示来说是至关重要的,这些字符可以用在JavaS cirpt的注释、字符串直接量和正则表达式直接最中,但不能用在标识符(比如,变量名)中。但有个例外,零宽连接符 (\u200D)和零宽非连接符 (\uFEFF) 译注6是可以出现在标识符中的,但不能作为标识 符的首字符。上文也提到了,字节序标记格式控制符(\uFEFF)被当成了空格来对待。
2.1.2 Unicode转义序列
在有些计算机硬件和软 件里, 无法显示或输入 U nico de 字符全集。为了支持那些使用老旧技术的程序员, Ja vaS c r i p t定义了一种特殊序列, 使用6个ASC II 字符来代表任意16位Unicode内码。这些Unicode转义序列均以\u为前缀, 其后跟随4个十六进制数(使用数字以及大写或小写的字母A – F表示)。这种Unicode转义写法可以用在JavaScript字符串直接量.、正则表达式直接量和标识符中(关键字除外)。例,如字符e的Unicode转义写法为\uOOE9 , 如下两个JavaScript字符串是完全一样的:
"cafe" === "caf\uOOe9" //=>true
Unicode 转义写法也可以出现在注释中, 但由千JavaScript 会将注释忽略 , 它们只是被当成上下文中的ASCII字符处理, 而且并不会被解析为其对应的Unicode 字符。
2.1.1 标准化
Unicode 允许使用多种方法对同一个字符进行编码。比如, 字符 "e" 可以使用Unicode 字符\uOOE9表示, 也可以使用普通的ASCII字符e跟随一个语调符\u0301。在文本编辑器 中,这两种编码的显示结果一模一样,但它们的二进制编码表示是不一样的,在计算机 里也不相等。Unicode标准为所有字符定义了一个首选的编码格式, 并给出了一个标准化的处理方式将文本转换为一种适合比较的标准格式, JavaScript会认为它正在解析的程序代码已经是这种标准格式,不会再对其标识符、字符串或正则表达式作标准化处理。
2.2 注释
JavaScript支持两种格式的注释。在行尾 "II" 之后的文本都会被JavaScript当做注释忽略掉的。此外, "!*"和"*!"之间的文本也会当做注释,这种注释可以跨行书写,但不能有嵌套的注释。下面都是合法的JavaScript注释:
//这里是单行注释 /*这里是一段注释*III这里是另一段注释 /* *这又是一段注释 *这里的注释可以连写多行 */
2.3直接量
所谓直接址 (literal ) , 就是程序中直接使用的数据值。下面列出的都是直接址:
第3章会详细讲 解数字和字符串直 接量。正则表达式直 接扯会在第 10章讲解。更多复杂的表达方式(参见4.2节)可以写成数组或对象直接址, 例如
{ x:1, y:2} //对象 [1,2,3,4,5] II 数组
2.4 标识符和保留字
标识符就是一 个名字。在Ja vaS c r i p t中,标 识符用来对变量和函数进行命名, 或者用做Ja vaS cri pt 代码中某些循环语句中的跳转位置的标记。 J avaS cri p t标识符必须以字母、下划线(_)或美元符($)开始。后续的字符可以是字母、数字、下划线或美元符(数字 是不允许作为首字符出现的 ,以 便Ja vaScript 可以轻易区分开标识符和数字)。下面是合法的标识符:
i my_variable_name V13 _dummy $str
出千可移植性和易千书写的考虑, 通常我们只使用AS C II 字母和数字来书写标识符。然而需要注意的是, J a vaS c r ipt 允许标识符中出现Unicode 字符全集中的字母和数字。 (从技术上讲, E C M AS cr i pt标准也允许在标识符的首字符后面出现 Unic ode 字符集中的 M n 类、Mc类和Pc类译注7 ) 。由此 , 程序员也可以使用非英语语言或数学符号来书写标识符:
var si = true; var π = 3.14;
和其他任何编程语言一样, Ja vaScript 保留了一些标识符为自己所用。这些 “保留字“不能用做普通的标识符,下面会讲到。
保留字
Ja vaScript 把一些标识符拿出来用做自己的关键字。因此 ,就 不能再在程序中把这些关键字用做标识符了:
JavaScript 同样保留了一些关键字,这些 关键字在当前的语言版本中并没有使用, 但在未来版本中可能会用到。EC MAScript 5保留了这些关键字:
class const enum export extends import super
此外,下 面这些关键字在普通的JavaScript 代码中是合法的, 但是在严格模式下是保留字:
implements let private public yield interface package protected static
严格模式同样对下面的标识符的使用做了严格限制,它们并不完全是保留字,但不能用做变量名、函数名或参数名:
arguments eval
ECMAScript 3将Java的所有关键字都列为自己的保留字,尽管这 些保留字在ECM AScript 5 中放宽了限制, 但如果你希望代码能在基千EC M AScript 3实现的解释器上运行的话 , 应当避免使用这些关键字作为标识符:
JavaScript 预定义了很多全局变最和函数, 应当避免把它们的名字用做变量名 和函数名:
J av aS c rip t的具体实现可能定义独有的全局变扯和函数 , 每一种特定的J av aS c rip t运行环境(客户端、服务器端等)都有自己的一个全局属性列表,这一点是需要牢记的。参照 第四部分的Window 对象来了解客户端Ja vaScript 中定义的全局变扯和函数列表。
2.5可选的分号
和其他许多编程语言一样, JavaScript 使用分号(;)将语句(参见第 5章)分隔开。这对增强代码的可读性和整洁性是非常重要的:缺少分隔符,一条语句的结束就成了下一条 语句的开始, 反之亦然。在 JavaScript 中, 如果语句各自独占一行, 通常可以省略语句之间的分号(程序结尾或右花括号 "}" 之前的分号也可以省略)。许多JavaScript 程序员
(包括本书中的示例代码)使用分号来明确标记语旬的结束,即使在并不完全需要分号 的时候也是如此。另一种风格就是,在任何可以省略分号的地方都将其省略,只有在不 得不用的时候才使用分号。不管采用哪种编程风格 , 关千Ja vaScript 中可选分号的问题有几个细节需要注意。
考虑如下代码,因为两条语句用两行书写,第一个分号是可以省略掉的:
a=3; b=4;
如果按照如下格式书写,第一个分号则不能省略掉:
a= 3; b = 4;
需要注意的是, Ja vaSc ri pt并不是在所有换行处都填补分号:只 有在缺少了分号就无法正确解析代码的时候, JavaS c ri pt 才会填补分号。换句话讲 (类似下面代码中的两处异常), 如果当前语句和随后的非空格字符不能当成一个整体来解析的话, JavaScript就在当前语句行结束处填补分号。看一下如下代码:
var a a = 3 console.log(a)
JavaScript将其解析为:
var a; a= 3; console.log(a);
JavaScript 给第一行换行处添加了分号, 因为如果没有分号, JavaScript 就无法解析代码var a a。第二个a可以单独当做一条语句 "a; " , 但JavaScript并没有给第二行结尾填补分号, 因为它可以和第三行内容一起解析成 " a=3; " 。
这些语句的分隔规则会导致一些意想不到的情形,这段代码写成了两行,看起来是两条 独立的语句:
vary= x + f (a+b).toString()
但第二行的圆括号却和第一行的f 组成了一个函数调用, JavaScript会把这段代码看做 :
var y = x + f{a+b).toString();
而这段代码的本意并不是这样。为了能让上述代码解析为两条不同的语句,必须手动填 写行尾的显式分号。
通常来讲,如果一条语句以"("、 "["、 "!"、 "+"或"-"开始,那么它极有可能和前一条语句合在一起解析。以"!"、 "+"和" – "开始的语句并不常见,而以
"(" 和 "[" 开始的语句则非常常见, 至少在一些Ja vaScript 编码风格中是很普遍的。有些程序员喜欢保守地在语句前加上一个分号,这样哪怕之前的语句被修改了、分号被误删除了,当前语句还是会正确地解析:
var x = o II 这里省略了分号 ; [x,x+1,x+2].forEach(console.log) I I 前面的分号保证了正确地语句解析
如果当前语句和下一行语句无法合并解析 , Ja v aSc ri pt则在第一行后填补分号 , 这是通用规则, 但有两个例外。第一个例外是在涉及r et urn 、br e a k和co nti nue 语句(参见第5 章)的场景中。如果这三个关键字后紧跟着换行, JavaScrip t则会在换行处填补分号。例如,这段代码:
return true;
JavaScript会解析成 :
return; true;
而代码的本意是这样:
return true;
也就是说, 在r et ur n、br eak 和conti nue 和随后的表达式之间不能有换行。如果添加了换行,程序则只有在极特殊的情况下才会报错,而且程序的调试非常不方便。
第二个例外是在涉及 "++ " 和 " – – " 运算符(见4.8 节)的时候。这些运算符可以 作为表达式的前缀,也可以当做表达式的后缀。如果将其用做后缀表达式,它和表达式应当 在同一行。否则,行尾将填补分号,同时"++"或"–"将会作为下一行代码的前缀 操作符并与之一起解析,例如,这段代码:
X ++ y
这段代码将解析为 "x; ++y" ' 而不是 "x++; y" 。