JavaScript 词法

文法是编译原理中对语言写法的一种规定,文法分为词法和语法。

JavaScript 引擎在执行代码时,会先进行词法解析。

词法的最小语义单元是:token,翻译为“标记”或“词”。从字符串到 token 的整个过程是没有结构的,只要符合 token 的规则,就构成 token。词法分析技术上可以使用状态机或者正则表达式来进行。

一般来说,词法设计不会包含冲突。不过,JavaScript 中有一些特别之处:

  • 除法和正则表达式冲突问题

同样是斜杠运算符///= 是除法运算符,但是两个斜杠括起来就是正则表达式 /abc/

  • 字符串模板和 } 冲突问题

字符串模板语Hello, ${name},理论上,${}内部可以放任何 JavaScript 代码,但是因为这些代码最后需要以}结尾,所以,这部分代码不允许出现}运算符,但是有例外情况:

console.log(`Hello, ${function() {}}`)

输入分类

词法分析过程,JavaScript 源码文本会被从左到右扫描,并被转换成一系列输入元素:

  • WhiteSpace 空白字符

  • LineTerminator 换行符

  • Comment 注释

  • Token 词

    • IdentifierName 标识符名称,例如定义的变量名或关键字

    • Punctuator 符号,运算符和大括号等符号

    • NumbericLiteral 数字直接量,就是数字

    • StringLiteral 字符串直接量,就是直接用单引号或双引号引起来的字符串

    • Template 字符串模板,用反引号 ` 括起来的直接量

注:直接量(literal),就是程序中能直接使用的数据值。

空白符号 WhiteSpace

空白符提升了源码的可读性,并将 token 区分开。这些符号通常不影响代码的功能。在压缩代码的过程中会移除源码的空白,减少数据传输量。

编码
名称
缩写
说明
字符串中写法

U+0009

缩进 TAB 符(制表符)

<HT><TAB>

水平制表符

U+000B

垂直方向 TAB 符(垂直制表符)

<VT>

垂直制表符

\v

U+000C

分页符

<FF>

分页符

\f

U+0020

空格

<SP>

空格

U+00A0

非断行空格

<NBSP>

文字排版中在该空格处不会换行

HTML 中用 &nbsp; 生成的就是它

U+200C

零宽非连接符(ES5)

<ZWNJ>

放置在一些经常被当成连字的字符之间,用于将他们分别以独立形式展示

U+200D

零宽连接符(ES5)

<ZWJ>

放置在一些通常不会被标记为连字的字符之间,用于将这些字符以连字形式展示

U+FEFF

零宽非断行空格(ES5)

<ZWNBSP>旧称<BOM>

在以 UTF 格式编码的文件中,常常在文件首插入一个额外的 U+FEFF,解析 UTF 文件的程序可以根据 U+FEFF 的表示方法猜测文件采用哪种 UTF 编码方式

换行符 LineTerminator

除了空白符外,换行符也可以提高源码的可读性,不同的是,换行符还可以影响 JavaScript 代码的执行,也会影响自动分号补全的执行。

编码
名称
缩写
说明
字符串中写法

U+000A

换行符

<LF>

正常的换行符

U+000D

回车符

<CR>

真正意义上的“回车”

U+2028

行分隔符

<LS>

U+2029

段分隔符

<PS>

注释 Comment

注释用来在源码中增加提示、笔记、建议、警告等信息,可以帮助阅读和理解源码。在调试时,可以用来将一段代码屏蔽掉。

单行注释 single-line comment

使用 //,会将该行中符号以后的文本都视为注释,除了四种 LineTerminator 之外,所有字符都可以作为单行注释。

多行注释 multiple-line comment

使用 /* */,这种方式更加灵活,可以在单行内使用多行注释,当然可以实现多行的注释,甚至可以用在代码中当做行内注释(可读性会变差,谨慎使用)

多行注释中允许自由地出现 MultiLineNotAsteriskChar ,也就是除了 * 之外的所有字符。除了最后的结束位置,其他的任何 * 之后,不能出现正斜杠 /

需要注意:多行注释中是否包含换行符号,会对 JavaScript 语法产生影响,对于 “no line terminator”规则来说,带换行的多行注释与换行符是等效的。

标识符名称 IdentifierName

IdentifierName 可以是 Identifier(就是我们自己定义的变量、函数)、NullLiteral(null 直接量)、BooleanLiteral 或者 keyword(关键字),在 ObjectLiteral 中,IdentifierName 还可以直接当做属性名称使用。仅当不是保留字时,IdentifierName 会被解析成 Identifier。

IdentifierName 可以以美元符$、下划线_、或者字母开始,除了开始字符以外,IdentifierName 中还可以使用连接标记、数字、以及连接符号。

JavaScript 中的一切都是区分大小写的,即关键字、变量、函数名和所有的标识符(Identifier)都要区分大小写。

关键字

关键字属于 IdentifierName ,这些关键字可用于表示控制语句的开始或结束,或者用于执行特定操作等。在 JavaScript 中,关键字有:

  • await

  • break

  • case

  • catch

  • class

  • const

  • continue

  • debugger

  • default

  • delete

  • do

  • else

  • export

  • extends

  • finally

  • for

  • function

  • if

  • import

  • in

  • instanceof

  • new

  • return

  • super

  • switch

  • this

  • throw

  • try

  • typeof

  • var

  • void

  • while

  • with

  • yield

为未来使用而保留的关键字

  • enum

在严格模式下,还有一些额外的为了未来使用而保留的关键字

  • implements

  • interface

  • package

  • private

  • protected

  • public

  • static

关键字的使用

事实上关键字(保留字)是仅针对标识符(Identifier)的词法定义而言的,而不是标识符名(IdentifierName)的文法定义,例如下面的例子就不排斥关键字作为标识符名。

a = { import: "test" }
a.import
a["import"]

但是下面的就会报错,函数声明的标识符不能使用关键字

function import() {} // 报错

符号 Punctuator

因为前面提到的除法和正则问题, //= 两个运算符被拆封为 DivPunctuator,因为前面提到的字符串模板问题, } 也被独立拆分。加在一起,所有的符号为:


{ ( ) [ ] . ... ; , < > <= >= == != === !== + - * % ** ++ -- << >> >>> & | ^ ! ~ && || ? : = += -= *= %= **= <<= >>= >>>= &= |= ^= => / /= }

数字直接量 NumbericLiteral

JavaScript 规范中规定的数字直接量可以支持四种写法:十进制数、二进制整数、八进制整数和十六进制整数。

十进制的 Number 可以带小数,小数点前后部分都可以省略,但是不能同时省略。

.01
12.
12.01

12.toString() // 因为 12. 会被当作省略了小数点后面部分的数字,而单独看成一个整体,就会报错
// 可以增加一个空格,让点单独成为一个 token,或者再加一个点。
12 .toString()
12..toString()

十进制数直接量还支持科学计数法,这里的 e 后面部分,只允许使用整数。

10.24E+2
10.24e-2
10.24e2

十进制数直接量可以以 0 开头,但是如果 0 以后的最高位比 8 小,数值将会被认为是八进制,不会报错,但得到的值可能不是期望的。

// 谨慎使用 0 开头的数值:
0888 // 转换为十进制 888
0777 // 转换为八进制 777,十进制 511

当以 0x 0b 0o 开头是,表示特定进制的整数,这几种进制都不支持小数,也不支持科学计数法。

0xFA  //十六进制整数
0o73  //八进制整数
0b10000 //二进制整数

字符串直接量 StringLiteral

字符串直接量支持单引号和双引号两种写法,区别仅仅是写法不同,在双引号字符串直接量中,双引号必须转义,在单引号字符串直接量中,单引号必须转义。字符串中其他必须转义的字符是 \ 和所有的换行符。

JavaScript 中支持四种转义,单字符转义有特别意义的字符包括 SingleEscapeCharacter 定义的 9 中。此外还有数字、x 和 u。

// 十六进制转义序列
'\xA9' //"©"

// Unicode 转义序列
'\u00A9' //"©"

// Unicode 编码转义
'\u{2F804}' //"你"

正则表达式直接量 RegularExpressionLiteral

正则表达式由 Body 和 Flags 两部分组成:

/RegularExpressionBody/Flags

其中 Body 部分至少有一个字符,以避免当成是行注释符号。第一个字符不能是 * ,因为 /*跟多行注释有语法冲突。正则表达式有自己的语法规则,在词法阶段,仅会对它做简单解析。

字符串模板

模板就是一个有反引号括起来的,可以在中间插入代码的字符串。模板支持添加处理函数的写法,这时模板的各段会被拆开,传递给函数当参数:

function f(){
    console.log(arguments);
}

var a = "world"
var b = "ha"
f`Hello ${a}!${b}~`; //[["hello","!","~"],"word","ha"]

零宽非断行空格,零宽非连接符,零宽连接符的应用

  1. 隐形水印(隐形指纹)

  2. 加密信息分享

  3. 逃脱敏感词过滤

以下几篇文章是详细介绍:

Last updated

Was this helpful?