词法基础

课后整理 2020-12-20

JavaScript语法就是指构成合法的JavaScript代码的所有规则和特征的集合,它包括词法和句法。词法包括字符编码、命名规则、标识符、关键字、注释规则、特殊字符用法等。

字符编码

JavaScript程序使用Unicode字符集编写。Unicode字符集中每个字符使用两个字节来表示,这意味着用户可以使用中文来命名JavaScript变量。

【示例】启动Dreamweaver,新建文档,保存为test.html,在页面嵌入<script>标签,然后在该标签中输入下面代码,则可以正常执行,效果如图1所示。

<!doctype  html>
<html>
<head>
<meta  charset="utf-8">
<title></title>
<script>
var 人名 =   "老张";
function 睡觉(谁){
    alert(谁 + ":快睡觉!都半夜鸡叫了。");
}
睡觉(人名);
</script>
</head>
<body>
</body>
</html>

图1  使用中文编写脚本运行效果

【注意】

在第1、2版本中,ECMAScript标准只允许Unicode字符出现在注释,或者引号包含的字符串中,其他地方必须使用ASCII字符集。考虑到JavaScript版本的兼容性,以及开发习惯,不建议使用汉字来命名变量或函数名。

【提示】

由于JavaScript脚本一般都寄存在网页中,并最终由浏览器来解释,因此在考虑到JavaScript语言编码的同时,还要顾及嵌入页面的字符编码,以及浏览器支持的编码。一般建议保持页面字符编码与JavaScript编码一致,避免出现乱码。

区分大小写

JavaScript严格区分大小。为了避免输入错误,用户可以采用一致的字符大小写形式,例如,遵循习惯所有字符都采用小写形式,这样可以有效减少输入错误。不过有两点例外。

(1)定义JavaScript构造函数时,可以让函数名首字母大写。

【示例1】下面脚本调用预定义的构造函数Date(),创建一个时间对象,最后把时间对象转换为字符串显示出来。

d =  new Date();            // 获取当前日期和时间
alert(d.toString());           // 显示日期

(2)如果变量名是多个词语连写,可以考虑部分字符大写。

例如,骆驼命名法就是在名称中每一个逻辑断点都有一个大写字母来标记。

printEmployeePaychecks();

如果使用下画线命名法,则可以按如下方式输入:

print_employee_paychecks();

【提示】

在过渡型HTML版本中,HTML标签名和属性名是不区分大小写的,标签属性可以以任意大小写形式输入,但是JavaScript脚本在访问DOM节点时是区分大小的,为了避免错误,应该统一标签名和属性名为小写形式。

【示例2】下面代码在部分早期浏览器中JavaScript有可能仅会获取第一个div元素的节点,而忽略掉第2、3个div元素节点。

<div></div>
<Div></Div>
<DIV></DIV>
<script>
var div  =document.getElementsByTagName("div")
alert(div.length)
</script>

标识符

标识符(identifier)表示名称的意思,JavaScript标识符主要包括变量名、函数名、参数名和属性名。合法的标识符命名应该注意如下几条规则,这些规则与C语系其他语言基本相同。

【示例1】下面这两行代码中,先定义一个变量abc,变量abc中的a被转义序列表示,为变量abc初始化并在对话框中显示出来。

var \u0061bc = "标识符abc(变量)中a字符的Unicode转义序列是\u0061";
alert(\u0061bc);

在书写时,转义序列不是很方便,一般很少这样使用,只有在特殊情况使用转义序列来传递或显示特殊字符,如JavaScript关键字、程序脚本等。

【注意】

JavaScript有一些保留字,不能用作标识符:arguments、break、case、catch、class、const、continue、debugger、default、delete、do、else、enum、eval、export、extends、false、finally、for、function、if、implements、import、in、instanceof、interface、let、new、null、package、private、protected、public、return、static、super、switch、this、throw、true、try、typeof、var、void、while、with、yield。

另外,还有三个词虽然不是保留字,但是因为具有特别含义,也不应该用作标识符:Infinity、NaN、undefined。

【示例2】下面这些都是合法的标识符。

arg0
_tmp
$elem
π

【示例3】下面这些则是不合法的标识符。

1a  // 第一个字符不能是数字
23  // 同上
***  // 标识符不能包含星号
a+b  // 标识符不能包含加号
-d  // 标识符不能包含减号或连词线

【示例4】中文是合法的标识符,可以用作变量名。

var 临时变量 = 1;

直接量

直接量(literal)是在程序中直接显示出来的值,如字符串、数值、布尔值、正则表达式、对象初始化、数组初始化等。

【示例】下面代码分别定义了字符串、数值、布尔值、正则表达式、特殊值、对象和数组直接量。

"字符串直接量"       // 字符串直接量
123456             // 数值直接量
true               // 布尔值直接量
/^ab.*/g           // 正则表达式直接量
null               // 特殊值直接量
{a:1,b:2}          // 对象初始化直接量
[1,2]              // 数组初始化

关键字和保留字

ECMA-262描述了一组具有特定用途的关键字,这些关键字可用于表示控制语句的开始或结束,或者用于执行特定操作等。按照规则,关键字也是语言保留的,不能用作标识符。以下是ECMAScript的全部关键字,如表1所示。

表1  ECMAScript关键字

关键字 关键字 关键字 关键字 关键字
break delete function return typeof
case do if switch var
catch else in this void
continue false instanceof throw while
debugger finally new true with
default for null try  

ECMA-262还描述了另外一组不能用作标识符的保留宇。保留字就是在目前还没有任何特定的用途,但它们有可能在将来版本中被用作关键字。

class、const、enum、export、extends、import、super
implements、let、private、public、yield、interface、package、protected、static
arguments、eval

如果希望代码能在基于ECMAScript 3实现的解释器上运行的话,应当避免使用这些关键字作为标识符,如表2所示。

表2  ECMAScript 3保留字

保留字 保留字 保留字 保留字 保留字
abstract double goto native static
boolean enum implements package super
byte export import private synchronized
char extends int protected throws
class final interface public transient
const float long short volatile

JavaScript预定义了很多全局变量和函数,应当避免把它们的名字用做变量名和函数名,如表3所示。

表3  JavaScript预定义全局变量和函数

预定义 预定义 预定义 预定义 预定义
arguments encodeURL Infinity Number RegExp
Array encodeURLComponent isFinite Object String
Boolean Error isNaN parseFloat SyntaxError
Date eval JSON parseInt TypeError
decodeURL EvalError Math RangeError undefined
decodeURLComponent Function NaN ReferenceError URLError

JavaScript实现可能定义独有的全局变量和函数,每一种特定的JavaScript运行环境(客户端、服务器端等)都有自己的一个全局属性列表,这些都需要注意。

分隔符

空格、制表符、换行符、换页符等在JavaScript程序中被统称为分隔符,用来分隔代码中的各种记号(如标识符、关键字、直接量、注释等信息),在解析时,JavaScript会忽略这些分隔符。

【示例1】对于下面代码块:

function toStr(a){return  a.toString();}

可以使用如下任意格式进行排版:

function toStr(a){
    return a.toString();}
//或者:
function toStr(a){
    return a.toString();
}
//或者:
function  toStr(a)
{
    return a.toString();
}

用户可以根据阅读习惯格式代码显示。一般JavaScript编辑器也会提供代码格式化功能。

【拓展】

(1)分隔符虽然无实在意义,但是脚本中不能够缺少分隔符,特别是在标识符与关键字之间必须使用分隔符进行分隔,否则JavaScript会误认为它们是一个完整的标记而无法正确识别。

【示例2】在下面语句中,把关键字function与标识符toStr连在一起,以及把关键字return与toString标识符连在一起都是不正确的:

functiontoStr(a){returna.toString();}   // 错误写法

在它们之间必须使用分隔符进行分隔:

function  toStr(a){return  a.toString();}   // 正确写法

(2)JavaScript解析器一般采用最长行匹配原则,并在此基础上忽略代码中的分隔符。所谓最长行匹配原则,就是在一行内如果能够正确解析,那么就在一行内进行解析,否则会继续读取下一行代码,直到能够被正确解析为止。因此,用户不能够无节制地使用换行符。

【示例3】下面代码就会返回错误的信息。

function toStr(a){
    return
    a.toString();                           // 错误的换行
}
alert(toStr("abc"));                       // 返回undefined

这是因为return作为一个独立语句,JavaScript解析器可以正确解析它,虽然它后面没有分号来标识该句的结束,但是解析器在正确解析的前提下,会自动为其补加一个分号,以表示该句已经结束。最后解析的脚本就变成了如下样式:

function toStr(a){
    return;
    a.toString();
}
alert(toStr("abc"));                       // 返回undefined

出现这个错误的根源是因为分号不是JavaScript语句结束的唯一标志。很多时候,JavaScript会自动把换行符也看做是一句结束的标志。因此,当使用换行符时,应该防止类似错误的发生。上面代码的正确写法应该是:

function toStr(a){
    return a.toString();
}
alert(toStr("abc"));                       // 返回字符串abc

(3)不能在标识符、关键字等名称内插入分隔符,否则JavaScript会误认为它们是两个独立的标记并进行解析。

【示例4】在下面函数中,错误地使用空格把toString()方法分隔为两部分,则JavaScript会误认为它们是to关键字和String()自定义函数两个实词标记。

function toStr(a){ 
    return a.to String();                   // 错误分隔符
}

(4)如果分隔符位于字符串或者正则表达式直接量内,则JavaScript会认为它们是具有一定语义的字符并进行解析。

【示例5】在下面代码中,变量a和b被赋予相同的字符串,但是变量b中间插入了空格,则比较结果发现它们是不相同的。

var a = "空格、制表符和换行符";
var b = "空格 、 制表符 和 换行符";
alert((a==b).toString());                  // 返回false

注释

注释就是不被解析的语句行或段。JavaScript把位于“//”字符后一行内的所有字符视为注释信息,从而忽略掉。

【示例1】下面几条注释语句可以位于代码段的不同位置,分别描述不同区域代码的功能。

// 代码段前注释,概述代码段的功能
function toStr(a){                         // 语句间注释,介绍函数
    // 代码段中注释,区域代码功能描述
    return a.toString();                    // 语句结束后注释,语句作用描述
}

在使用单行注释时,在“//”符号后面的同一行内就不要输入任何代码,否则都被视为注释文本而忽略掉。

【示例2】还可以使用“/*”和“*/”符号来包含多行注释信息。例如:

/*
多行注释
多行注释
*/

在多行注释中,包含在“/*”和“*/”符号之间的任何文本都视为注释文本而忽略掉。

【拓展】

此外,由于历史上JavaScript兼容HTML代码的注释,所以<!--和-->也被视为单行注释。例如:

x = 1; <!-- x = 2;
--> x = 3;

上面代码中,只有x = 1会执行,其他的部分都被注释掉了。

注意,-->只有在行首,才会被当成单行注释,否则就是一个运算符。例如:

function countdown(n) {
  while (n --> 0) console.log(n);
}
countdown(3)
// 2
// 1
// 0

上面代码中,n --> 0实际上会当作n-- > 0,因此输出2、1、0。

转义序列

在有些计算机硬件和软件里,无法显示或输入Unicode字符全集。为了支持那些使用老旧技术的程序员,JavaScript定义了一种特殊序列,使用6个ASCII字符来代表任意16位Unicode内码。

这些Unicode转义序列均以\u为前缀,其后跟随4个十六进制数,即使用数字以及大写或小写的字母A~F表示。这种Unicode转义写法可以用在JavaScript字符串直接量、正则表达式直接量和标识符中,但关键字除外。

【示例】字符“码”的Unicode转义写法为\u7801,如下两个JavaScript字符串是完全一样的:

document.write("字符编码");
document.write("字符编\u7801");

Unicode转义写法也可以出现在注释中,但由于JavaScript会将注释忽略,它们只是被当成上卞文中的ASCII字符处理,而且并不会被解析为其对应的Unicode字符。