JavaScript 的文法:词法
文法是编译原理中对语言的写法的一种规定,一般来说,文法分成词法和语法两种。
词法规定了语言的最小语义单元:token
,可以翻译成“标记”或者“词”,在此专栏文章中,统一把 token 翻译成词。
从字符到词的整个过程是没有结构的,只要符合词的规则,就构成词,一般来说,词法设计不会包含冲突。词法分析技术上可以使用状态机或者正则表达式来进行。
概述
先看 JavaScript 的词法定义。JavaScript 源代码中的输入可以这样分类:
-
WhiteSpace 空白字符
-
LineTerminator 换行符
-
Comment 注释
-
Token 词
- IdentifierName 标识符名称,典型案例是我们使用的变量名,注意这里关键字也包含在内了。
- Punctuator 符号,我们使用的运算符和大括号等符号。
- NumericLiteral 数字直接量,就是我们写的数字。
- StringLiteral 字符串直接量,就是我们用单引号或者双引号引起来的直接量。
- Template 字符串模板,用反引号` 括起来的直接量
这个设计符合比较通用的编程语言设计方式,不过,JavaScript 中有一些特别之处,下面讲特别在哪里。
-
除法和正则表达式冲突问题
JavaScript 不但支持除法运算符“
/
”和“/=
”,还支持用斜杠括起来的正则表达式“/abc/
”但是,这时候对词法分析来说,其实是没有办法处理的,所以 JavaScript 的解决方案是定义两组词法,然后靠语法分析传一个标志给词法分析器,让它来决定使用哪
一套词法。 -
是字符串模板
模板语法大概是这样的:
`Hello, ${name}`
理论上,“ ${ } ”内部可以放任何 JavaScript 表达式代码,而这些代码是以“ } ” 结尾的,也就是说,这部分词法不允许出现“ } ”运算符。
是否允许“ } ”的两种情况,与除法和正则表达式的两种情况相乘就是四种词法定义,所以你在 JavaScript 标准中,可以看到四种定义:
- InputElementDiv;
- InputElementRegExp;
- InputElementRegExpOrTemplateTail;
- InputElementTemplateTail。
为了解决这两个问题,标准中还不得不把除法、正则表达式直接量和“ } ”从 token 中单独抽出来,用词上,也把原本的 Token 改为 CommonToken。在本文档,我们依然把它们归类到 token 来理解。
对一般的语言的词法分析过程来说,都会丢弃除了 token 之外的输入,但是对 JavaScript 来说,不太一样,换行符和注释还会影响语法分析过程,这个将会在语法部分详细讲解(所以要实现 JavaScript 的解释器,词法分析和语法分析非常麻烦,需要来回传递信息)。
空白符号 Whitespace
说起空白符号,想必给大家留下的印象就是空格,但是实际上,JavaScript 可以支持更多空白符号。
<HT>
(或称<TAB>
) 是 U+0009,是缩进 TAB 符,也就是字符串中写的 \t 。<VT>
是 U+000B,也就是垂直方向的 TAB 符\v
,这个字符在键盘上很难打出来,所以很少用到。<FF>
是 U+000C,Form Feed,分页符,字符串直接量中写作\f
,现代已经很少有打印源程序的事情发生了,所以这个字符在 JavaScript 源代码中很少用到。<SP>
是 U+0020,就是最普通的空格了。<NBSP>
是 U+00A0,非断行空格,它是 SP 的一个变体,在文字排版中,可以避免因为空格在此处发生断行,其它方面和普通空格完全一样。多数的 JavaScript
编辑环境都会把它当做普通空格(因为一般源代码编辑环境根本就不会自动折行……)。HTML 中,很多人喜欢用的 最后生成的就是它了。<ZWNBSP>
(旧称<BOM>
) 是 U+FEFF,这是 ES5 新加入的空白符,是 Unicode 中的零宽非断行空格,在以 UTF 格式编码的文件中,常常在文件首插入一个额外的
U+FEFF,解析 UTF 文件的程序可以根据 U+FEFF 的表示方法猜测文件采用哪种 UTF 编码方式。这个字符也叫做“bit order mark”。
此外,JavaScript 支持所有的 Unicode 中的空格分类下的空格.
换行符 LineTerminator
接下来我们来看看换行符,JavaScript 中只提供了 4 种字符作为换行符。
<LF>
<CR>
<LS>
<PS>
其中,<LF>
是 U+000A,就是最正常换行符,在字符串中的\n
<CR>
是 U+000D,这个字符真正意义上的“回车”,在字符串中是\r
,在一部分 Windows 风格文本编辑器中,换行是两个字符\r\n
。
<LS>
是 U+2028,是 Unicode 中的行分隔符。<PS>
是 U+2029,是 Unicode 中的段落分隔符。
大部分 LineTerminator 在被词法分析器扫描出之后,会被语法分析器丢弃,但是换行符会影响 JavaScript 的两个重要语法特性:自动插入分号和“no line terminator”规则。
注释 Comment
JavaScript 的注释分为单行注释和多行注释两种:
/* MultiLineCommentChars */
// SingleLineCommentChars
多行注释中允许自由地出现MultiLineNotAsteriskChar
,也就是除了*
之外的所有字符。而每一个*
之后,不能出现正斜杠符/
。
除了四种 LineTerminator 之外,所有字符都可以作为单行注释。
我们需要注意,多行注释中是否包含换行符号,会对 JavaScript 语法产生影响,对于“no line terminator”规则来说,带换行的多行注释与换行符是等效的。
标识符名称 IdentifierName
IdentifierName
可以以美元符$
下划线_
或者 Unicode 字母开始,除了开始字符以外,IdentifierName
中还可以使用 Unicode 中的连接标记、数字、以及连接符
号。IdentifierName
的任意字符可以使用 JavaScript 的 Unicode 转义写法,使用 Unicode 转义写法时,没有任何字符限制。IdentifierName
可以是Identifier
、NullLiteral
、BooleanLiteral
或者keyword
,在ObjectLiteral
中,IdentifierName
还可以被直接当做属性名称使用。仅当不是保留字的时候,IdentifierName
会被解析为Identifier
。
注意<ZWNJ>
和<ZWJ>
是 ES5 新加入的两个格式控制字符,它们都是 0 宽的。
(这节没看完…)