## 语法 **2.1 语法** JavaScript程序是用Unicode字符集编写的。 **2.1.1 区分大小写** JavaScript中的一切(变量、函数名和操作符)都区分大小写。比如变量名test和变量名Test代表的是两个不同的变量。 **2.1.2 标识符** 标识符是指变量、函数、属性的名字,或函数的参数。 **标识符格式规则**: - 第一个字符必须是一个字母、下划线(`_`)或一个美元符号(`$`) - 其他字符可以是字母、下划线、美元符号或数字。 按照惯例,JavaScript标识符采用`驼峰大小写格式`,也就是第一个字母小写,剩下的每个有意义的单词的首字母大写。 ``` myName ``` 注意:不能把关键字、保留字、true、false和null用作标识符。 **2.1.3 注释** 单行注释: ``` // 单行注释 ``` 多行注释(`/**/`): ``` /* * 多行注释,这一行星号非必需 */ ``` 注意:多行注意(`/**/`)不能嵌套。 **2.1.4 严格模式** ECMAScript 5 引入了`严格模式`(strict mode)的概念。严格模式是为JavaScript定义了一种不同的解析与执行模型。 在严格模式下,ECMAScript 3中的一些不确定的行为将得到处理,而且对某些不安全的操作也会抛出错误。 可以在整个脚本中启用严格模式,也可以在函数内的顶部启用: ``` "use strict"; function doSomething(){ "use strict"; // 函数体 } ``` 如果在代码中使用"use strict"开启了严格模式,则下面的情况都会在脚本运行之前抛出SyntaxError异常: - 八进制语法:var n = 023和var s = "\047" - with语句 - 使用delete删除一个变量名(而不是属性名):delete myVariable - 使用eval或arguments作为变量名或函数名 - 使用未来保留字(也许会在ECMAScript 6中使用):implements, interface, let, package, private, protected, public, static,和yield作为变量名或函数名 - 在语句块中使用函数声明:`if(a<b){ function f(){} }` 其他错误 - 对象字面量中使用两个相同的属性名:{a: 1, b: 3, a: 7} - 函数形参中使用两个相同的参数名:function f(a, b, b){} **2.1.5 语句** ECMAScript中的语句以一个分号结尾;如果省略分号,则由解析器确定语句的结尾。 强烈建议在可用可不用分号的地方使用分号,因为加上分号,可以避免很多意想不到的错误,而且也可以放心的使用压缩工具来压缩JavaScript脚本。 **(1) 可选的行尾分号** JavaScript使用分号(;)将语句分隔开。 注意:JavaScript并不是在所有换行处都填补分号,只有在缺少了分号就无法正确解析代码的时候,才会填补分号。也可以说,如果当前语句和随后的非空格字符不能当做一个整体来解析时,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”。 上面的代码解析后是没有问题的,可是没有主动添加分号,有些时候会导致意想不到的情形。比如: ``` var y=x+f (a+b).toString() ``` 解析后: ``` var y=x+f(a+b).toString(); ``` 如果当前语句和下一行语句无法合并解析,JavaScript则在第一行后填补分号,这是通用规则,但有两个例外。 第一个例外是在涉及return、break和continue语句的场景总,如果这三个关键字后紧跟换行,JavaScript则会在换行处填补分号。 例如: ``` return true; ``` JavaScript会解析成: ``` return; true; ``` 第二个例外是在涉及“++”和“--”运算符的时候。 ``` x ++ y ``` 将会解析成 “x;++y”,而不是“x++;y” 适当的添加分号,可避免意想不到的错误。 **2.2 关键字和保留字** ECMA-262描述了一组具有特定用途的**关键字**,这些关键字可用于控制语句的开始或结束,或者用于执行特定操作等,不能用作标识符。 ``` break do instanceof typeof case else new var catch finally return void continue for switch while debugger function this with default if throw delete in try ``` ECMA-262还描述了一组不能用作标识符的**保留字**: ``` abstract enum int short boolean export interface static byte extends long super char final native synchronized class float package throws const goto private transient debugger implements protected volatile double import public ``` 如果使用关键字作标识符,会导致“Identifier Expected”错误。 有些时候,我们不得不用到保留字货关键字的,比如CSS样式中的float,这时就需要这样: ``` style.cssFloat ``` **2.3 变量** ECMAScript的变量是`松散类型`的,所谓`松散类型`就是可以用来保存任何类型的数据。定义变量时要使用`var`操作符,后跟变量名。 ``` var name; ``` 注意:使用var操作符定义的变量将成为定义该变量的作用域中的局部变量,也就是说,如果在函数中使用var定义一个变量,那么这个变量在函数退出后就会被销毁。 ``` function test(){ var name = 'tg'; //局部变量 } test(); console.log(name); // 报错 ``` 如果省略了var操作符,就相当于定义了一个全局变量,在函数外部的任何地方都可以访问到。 ``` function test(){ name = 'tg'; //局部变量 } test(); console.log(name); // "tg" ``` 不推荐省略var操作符。 还有一种隐式全局变量: ``` function test() { var a = b = 0; } test(); console.log(b); // 0 console.log(a); // ReferenceError: a is not defined ``` 在上面的代码中,由于从右至左的操作符优先级,所以表达式“b=0”是先执行的,而此时b未经过声明,所以它会成为全局变量。 注意: - 使用var创建的全局变量不能删除。 - 不适应var创建的隐含全局变量可以使用delete删除(因为它并不是真正的变量,而是全局对象window的属性)。 ``` function test() { var a = b = 0; delete a; delete b; console.log(a); // 0 console.log(b); // ReferenceError: b is not defined } test(); ``` **2.3.1 变量提升** **变量提升**是指所有变量的声明语句,都会被提升到代码的头部。 在函数内也一样,函数中的所有变量声明会在函数执行时被“提升”至函数体顶端。 看个例子: ``` console.log(a); // undefined var a = 1; function test(){ console.log(a); // undefined var a = 2; } test(); ``` 结果是不是有点出乎你的意料。 其实JavaScript的执行环境分为**声明阶段**和**执行阶段**,因此对于上面的代码,JavaScript会这样解释代码: ``` var a; console.log(a); // undefined a = 1; function test(){ var a; console.log(a); // undefined a = 2; } test(); ``` **2.3.2 复制变量值** 如果从一个变量向另一个变量复制基本类型的值,会在变量对象上创建一个新值,然后把该值复制到为新变量分配的位置上。 ``` var num1 = 5; var num2 = num1; num2 += 5; console.log(num1); //5 console.log(num2); // 10 ``` 从上面例子的结果,我们知道num1中的5和num2中的5是完全独立的,num2中的5只是num1中的5的一个副本。 当从一个变量向另一个变量复制引用类型的值时,同样会将存储在变量对象中的值复制一份放到为新变量分配的空间中。不同的是,这个值的副本实际上是一个指针,而这个指针指向存储在堆中的一个对象。复制操作结束后,两个变量实际上将引用同一个对象。因此,改变其中一个变量,就会影响到另一个变量。 ``` var obj1 = new Object(); var obj2 = obj1; obj2.name = 'tg'; console.log(obj1.name); // "tg" ``` 在上面的例子中,我们将obj1赋给了obj2,两者就指向了同一个对象,然后给obj2添加了一个属性name,接着访问obj1中的name,发现值和obj2中的name是一样,因为这两个变量引用的都是同一个对象。 注意:对象和原始值(布尔值、数字、字符串、null和undefined)之间的主要区别在于比较方式。 原始值比较的是值,只要编码值相同,则认为相同: ``` var a = 1; var b = 1; console.log(a === b); // true ``` 而对象比较的是引用(也可以说是引用地址): ``` var obj1 = {}; var obj2 = {}; console.log(obj1 === obj2); // false console.log(obj1 == obj2); // false console.log(obj1 === obj1); // true ``` **2.4 空格、换行符** JavaScript会忽略程序中标识符之间的空格。在大多数情况下忽略换行符。 我们可以使用空格和换行来提高代码的可读性。 除了可以识别普通的空格符(\u0020),JavaScript还可以识别如下表示空格的字符: ``` 水平制表符(\u0009) 垂直制表符(\u000B) 换页符(\u000C) 不中断空表(\u00A0) 字节序标记(\uFEFF) ``` JavaScript会将如下字符识别为行结束符: ``` 换行符(\u000A) 回车符(\u000D) 行分隔符(\u2028) 段分隔符(\u2029) ``` 回车符加换行符在一起被解析为一个单行结束符。 **2.5 Unicode转义序列** JavaScript定义了一种特殊序列,使用6个ASCII字符来代表任意16位Unicode内码。这些Unicode转义序列码均以\u为前缀,其后跟随4个十六进制(使用数字以及大写或小写的字母A~Fa~f表示)。