JavaScript 语法与类型
原创语法与类型
本章讨论 JavaScript 的基本语法、变量声明、数据类型和字面量。
基础
JavaScript 的大部分语法从 Java、C 和 C++ 借鉴而来,但同时也受到 Awk、Perl 和 Python 的影响。
JavaScript 是区分大小写的,并使用 Unicode 字符集。举个例子,可以将单词 Früh(在德语中意思是"早")用作变量名。
const Früh = "foobar";
但是,由于 JavaScript 是大小写敏感的,因此变量 früh 和 Früh 则是两个不同的变量。
在 JavaScript 中,指令被称为语句,并用分号(;)进行分隔。
如果一条语句独占一行的话,那么分号是可以省略的。但如果一行中有多条语句,那么这些语句必须用分号进行分隔。
然而,在一条语句的末尾总是加上分号被认为是最佳实践,即使是在非严格需要的时候。这个习惯可以大大减少代码出问题的可能性。
从左往右扫描 JavaScript 脚本的源文本并将其转换为输入元素(token、控制字符、行终止符、注释和空白字符,空白字符指的是空格、制表符和换行符等)序列。
注释
注释语法和 C++ 以及许多其他语言的注释语法一样:
// 单行注释
/* 这是一个更长的,
* 多行注释
*/
你不能嵌套块注释。这个情况通常是你在注释中意外地包含了 */ 序列,而它会结束这段注释。
/* 然而,你不能,/* 嵌套注释 */ 语法错误 */
在这个例子中,你需要破坏 */ 模式。例如,插入反斜杠:
/* 你可以通过转义正斜杠 /* 嵌套注释 *\/ */
注释的行为类似于空白字符,在脚本执行过程中会被忽略。
声明
JavaScript 有三种变量声明方式。
var-
声明一个变量,可选择将其初始化为一个值。
let-
声明一个块级作用域的局部变量,可选择将其初始化为一个值。
const-
声明一个块级作用域的只读命名常量。
变量
在应用程序中,你将变量用作值的符号名。变量的名字又叫做标识符,其需要遵守一定的规则。
JavaScript 标识符通常以字母、下划线(_)或者美元符号($)开头;后续的字符也可以是数字(0-9)。因为 JavaScript 是区分大小写的,所以字母包含从 A 到 Z 的大写字母和从 a 到 z 的小写字母。
你可以在标识符中使用大部分 Unicode 字母,例如 å 和 ü。你也可以在标识符中使用 Unicode 转义序列表示字符。
合法的标识符示例:Number_hits、temp99、$credit 和 _name。
声明变量
你可以用以下两种方式声明变量:
- 使用关键字
var。例如var x = 42。这个语法可以用来声明局部变量和全局变量,具体取决于执行上下文。 - 使用关键字
const或let。例如let y = 13。这个语法可以用来声明块级作用域的局部变量。
你可以使用解构语法声明用于解包值的变量。例如 const { bar } = foo。这会创建一个名为 bar 的变量,并且将 foo 对象中属性名与之相同的属性的值赋给它。
应该总是在声明变量后使用它们。JavaScript 过去允许给未声明的变量赋值,而这会创建一个**未声明的全局**变量。这在严格模式下是一个错误,应该彻底避免使用它。
声明和初始化
在 let x = 42 这样的语句中,let x 称作声明,= 42称作初始化器。声明允许在后续的代码中访问变量时不会抛出 ReferenceError,而初始化器会给变量赋值。在 var 和 let 声明中,初始化器是可选的。如果声明变量时没有进行初始化,变量会被赋值为 undefined。
let x;
console.log(x); // 输出"undefined"
本质上,let x = 42 等价于 let x; x = 42。
const 声明总是需要初始化器,因为它们禁止在声明后进行任何类型的赋值,以及隐式地将其初始化为 undefined 很可能是程序员犯的错。
const x; // SyntaxError: Missing initializer in const declaration
变量作用域
一个变量可能属于下列作用域之一:
- 全局作用域:在脚本模式中运行的所有代码的默认作用域。
- 模块作用域:在模块模式中运行的代码的作用域。
- 函数作用域:由函数创建的作用域。
此外,用 let 或 const 声明的变量属于另一个作用域:
- 块级作用域:用一对花括号创建的作用域(块)。
当你在函数的外部声明变量时,该变量被称作全局变量,因为当前文档中任何其他代码都能使用它。当你在函数内声明变量时,该变量被称作局部变量,因为它仅在那个函数内可用。
let 和 const 声明也会被限制在声明所在的块语句中。
if (Math.random() > 0.5) {
const y = 5;
}
console.log(y); // ReferenceError: y is not defined
然而,用 var 创建的变量不是块级作用域的,而只是块所在的*函数(或全局作用域)*的。
例如,下列代码会输出 5,因为 x 的作用域是全局上下文(如果代码是函数的一部分的话,就是函数上下文)。x 的作用域不受附近的 if 语句块的限制。
if (true) {
var x = 5;
}
console.log(x); // x 是 5
变量提升
用 var 声明的变量会被提升,意味着你可以在该变量所在的作用域的任意地方引用该变量,即使还没有到达变量声明的地方。你可以看见 var 声明好像被提升到该变量的函数或全局作用域的顶部。然而,如果你在声明变量之前访问该变量,其值总是 undefined,因为只有该变量的声明和默认初始化(为 undefined)被提升,而不是它的值赋值。
console.log(x === undefined); // true
var x = 3;
(function () {
console.log(x); // undefined
var x = "局部值";
})();
上面的例子可以被解释为:
var x;
console.log(x === undefined); // true
x = 3;
(function () {
var x;
console.log(x); // undefined
x = "局部值";
})();
由于存在变量提升,一个函数中所有的 var 语句应尽可能地放在接近函数顶部的地方。这个最佳实践会提升代码的清晰度。
let 和 const 是否被提升是个定义争论。在变量声明之前引用块中的变量,将总是抛出 ReferenceError,因为该变量位于从块的开始到声明所在的"暂时性死区"中。
console.log(x); // ReferenceError
const x = 3;
console.log(y); // ReferenceError
let y = 3;
全局变量
实际上,全局变量是全局对象的属性。
在网页中,全局对象是 window,所以你可以用 window.variable 语法读取和设置全局变量。在所有的环境中,globalThis 变量(其自身也是一个全局变量)可以被用于读取和设置全局变量。这为不同 JavaScript 运行时提供了一个一致的接口。
因此,你可以通过指定 window 或 frame 的名字,在当前 window 或 frame 访问另一个 window 或 frame 中声明的变量。例如,如果在文档中声明了一个叫 phoneNumber 的变量,那么你就可以在 iframe 中使用 parent.phoneNumber 引用它。
常量
你可以用 const 关键字创建一个只读命名常量。常量标识符的语法和任何变量标识符的语法相同:必须以字母、下划线或美元符号($)开头并可以包含字母、数字或下划线。
const PI = 3.14;
常量不可以通过赋值来改变其值或在脚本运行时被重新声明。必须为其初始化一个值。常量的作用域规则和 let 块级作用域变量的一致。
在同一作用域中,不能使用与变量名或函数名相同的名字来声明常量。
然而,const 仅阻止重新赋值,而不阻止修改。被赋值为常量的对象的属性是不受保护的,所以下面的语句执行时不会产生错误。
const MY_OBJECT = { key: "值" };
MY_OBJECT.key = "其他值";
同样的,数组的元素也是不受保护的,所以下面的语句执行时也不会产生错误。
const MY_ARRAY = ["HTML", "CSS"];
MY_ARRAY.push("JAVASCRIPT");
console.log(MY_ARRAY); // ['HTML', 'CSS', 'JAVASCRIPT'];
数据结构和类型
数据类型
最新的 ECMAScript 标准定义了 8 种数据类型:
虽然这些数据类型相对来说比较少,但是它们可以让你在程序中开发有用的功能。函数是这门语言的另一个基本元素。虽然函数从技术上讲是一种对象,但是你可以把对象当作存放值的命名容器,然后将函数当作你的程序能够执行的过程。
数据类型的转换
JavaScript 是一门动态类型语言。这意味着你在声明变量时可以不必指定该变量的数据类型。这也意味着在脚本执行期间会根据需要自动转换数据类型。
因此,你可以按照如下方式来定义变量:
let answer = 42;
然后,你还可以给同一个变量赋予一个字符串值,例如:
answer = "不客气,感谢所有的鱼!";
因为 JavaScript 是动态类型的,这种赋值方式并不会提示出错。
数字和"+"运算符
在使用 + 运算符的表达式中涉及数字和字符串,JavaScript 会把数字转换成字符串。例如,注意下列语句:
x = "答案是 " + 42; // "答案是 42"
y = 42 + " 是答案"; // "42 是答案"
z = "37" + 7; // "377"
在使用其他运算符时,JavaScript 不会把数字转换成字符串。例如:
"37" - 7; // 30
"37" * 7; // 259
字符串转换为数字
有一些方法可以将内存中表示一个数字的字符串转换为数字。
parseInt 只返回整数,因此它在处理小数时用途有限。
将字符串转换为数字的另一种方法是使用 +(一元加)运算符。
"1.1" + "1.1" = "1.11.1"
(+ "1.1") + (+ "1.1") = 2.2
字面量
在 JavaScript 中,字面量可以表示值。这些字面量是脚本中按字面意思给出的固定的值,而不是变量。本节将介绍以下类型的字面量:
数组字面量
数组字面量是由一对方括号([])括起来的包含零个或多个表达式的列表,其中每个表达式代表一个数组元素。当你使用数组字面量创建数组时,该数组将会以指定的值作为其元素进行初始化,而其 length 被设定为指定的参数的个数。
下面的示例创建了含有 3 个元素的 coffees 数组,它的长度是 3。
const coffees = ["French Roast", "Colombian", "Kona"];
每次字面量被求值时,数组字面量都会创建一个新的数组对象。例如,在全局作用域中用字面量定义的数组在脚本加载后被创建。然而,如果数组字面量位于函数内,每次调用函数时会初始化一个新数组。
数组字面量中的多余逗号
如果你在数组字面量中连续放置两个逗号,数组会为未指定的元素留下一个空槽。以下示例创建了一个名为 fish 的数组:
const fish = ["Lion", , "Angel"];
打印这个数组时,你会看见:
console.log(fish);
// [ 'Lion', <1 empty item>, 'Angel' ]
注意,第二项是"empty",与实际的 undefined 值完全不同。当使用数组遍历方法(例如,Array.prototype.map)时,空槽会被跳过。然而,索引访问 fish[1] 仍会返回 undefined。
布尔字面量
布尔类型有两种字面量值:true 和 false。
数字字面量
JavaScript 数字字面量包括多种基数的整数字面量和以 10 为基数的浮点数字面量。
值得一提的是,语言规范要求数字字面量必须是无符号的。但是像 -123.4 这样的代码片段还是没有问题的,会被解释为一元操作符 - 应用于数字字面量 123.4。
整数字面量
整数和 BigInt 字面量可以用十进制(基数 10)、十六进制(基数 16)、八进制(基数 8)和二进制(基数 2)表示。
- 十进制整数字面量由数字序列组成,且没有前缀
0(零)。 - 八进制的整数字面量以
0(或0O、0o)开头,只能包括数字 0-7。 - 十六进制整数字面量以
0x(或0X)开头,可以包含数字(0-9)和字母a-f或A-F。 - 二进制整数字面量以
0b(或0B)开头,只能包含数字0和1。 BigInt由整数字面量和n后缀组成。
浮点数字面量
浮点数字面量可以有以下的组成部分:
- 一个无符号的十进制整数,
- 小数点("."),
- 小数部分(另一个十进制数),
- 指数部分。
指数部分以 e 或 E 开头,后面跟着一个整数,这个整数可以有正负号(即前缀 + 或 -)。浮点数字面量至少有一位数字,而且必须带小数点或者 e(E)。
例如:
3.1415926
.123456789
3.1E+12
.1e-23
对象字面量
对象字面量是由一对花括号({})括起来的包含零个或多个属性名和相关值的列表。
以下是一个对象字面量的例子。car 对象的第一个元素定义了属性 myCar,被赋值为一个新字符串("Saturn");第二个元素,属性 getCar,立即被赋值为函数调用 (carTypes("Honda")) 的结果;第三个元素,属性 special,使用了一个已有的变量(sales)。
const sales = "Toyota";
function carTypes(name) {
return name === "Honda" ? name : `对不起,我们不售卖 ${name}。`;
}
const car = { myCar: "Saturn", getCar: carTypes("Honda"), special: sales };
console.log(car.myCar); // Saturn
console.log(car.getCar); // Honda
console.log(car.special); // Toyota
RegExp 字面量
一个正则表达式字面量是字符被正斜杠围成的表达式。下面是正则表达式字面量的一个示例。
const re = /ab+c/;
字符串字面量
字符串字面量是由一对双引号(")或单引号(')括起来的零个或多个字符。字符串被限定在同种引号之间(也即,必须是成对单引号或成对双引号)。
下面是字符串字面量的示例:
'foo'
"bar"
'1234'
'一行\n另一行'
"Joyo 的猫"
你应该使用字符串字面量,除非你特别需要使用 String 对象。想要了解有关 String 对象的细节,参见 String。
你可以在字符串字面量值上使用 String 对象的所有方法。JavaScript 会自动将字符串字面量转换为一个临时字符串对象,调用该方法,然后废弃掉那个临时的字符串对象。你也可以使用字符串字面量的 length 属性。
// 将打印字符串中的字符个数(包括空格)
console.log("John 的猫".length); // 结果为:7
模板字面量也可用。模板字面量由一对反引号(`)包围,而不是双引号或单引号。
模板字面量为构建字符串提供了语法糖。
// 创建基本的字符串字面量
`在 JavaScript 中,"\n" 是换行符。`
// 多行字符串
`在 JavaScript 中,模板字符串可以
跨越行,但是由双引号和单引号
包裹的字符串不行。`
// 字符串插值
const name = 'Lev', time = 'today';
`你好 ${name},${time} 过得怎么样?`
更多信息
本章重点包括声明和类型的基本语法。想要学习有关 JavaScript 的语言结构更多的信息,也可以参见本指南中的这些文章:
在下一章中,我们将会学习控制流结构与错误处理。
版权声明
本文仅代表作者观点,不代表本站立场。
本文系作者授权本站发表,未经许可,不得转载。
开发学习网





