语法和类型
基础
JavaScript 的大部分语法借鉴自 Java、C 和 C++,但也受到 Awk、Perl 和 Python 的影响。
JavaScript 区分大小写并使用 Unicode 字符集。例如,单词 Früh(德语中意为“早”)可以用作变量名。
const Früh = "foobar";
但是,变量 früh
与 Früh
不相同,因为 JavaScript 区分大小写。
在 JavaScript 中,指令称为 语句,并用分号 (;) 分隔。
如果语句单独写在一行上,则语句后不需要分号。但是,如果希望在一行上写多个语句,则必须用分号分隔。
但是,建议始终在语句后写分号,即使它不是严格需要的。这种做法减少了代码中出现错误的可能性。
JavaScript 脚本的源文本从左到右扫描,并转换为一系列输入元素,这些元素是标记、控制字符、换行符、注释或空白。(空格、制表符和换行符被视为空白。)
注释
注释的语法与 C++ 和许多其他语言相同
// a one line comment
/* this is a longer,
* multi-line comment
*/
您不能嵌套块注释。当您意外地在注释中包含 */
序列时,这通常会发生,这将终止注释。
/* You can't, however, /* nest comments */ SyntaxError */
在这种情况下,您需要分解 */
模式。例如,通过插入反斜杠
/* You can /* nest comments *\/ by escaping slashes */
注释的行为类似于空白,并在脚本执行期间被丢弃。
注意:您可能还会在某些 JavaScript 文件的开头看到第三种注释语法,它看起来像这样:#!/usr/bin/env node
。
这称为shebang 注释语法,是一种特殊的注释,用于指定应执行脚本的特定 JavaScript 引擎的路径。有关更多详细信息,请参阅Shebang 注释。
声明
变量
您将变量用作应用程序中值的符号名称。变量的名称(称为标识符)符合某些规则。
JavaScript 标识符通常以字母、下划线 (_
) 或美元符号 ($
) 开头。后续字符也可以是数字 (0
– 9
)。因为 JavaScript 区分大小写,所以字母包括字符 A
到 Z
(大写)以及 a
到 z
(小写)。
您可以在标识符中使用大多数 Unicode 字母,例如 å
和 ü
。(有关更多详细信息,请参阅词法语法 参考。)您还可以使用Unicode 转义序列 来表示标识符中的字符。
一些合法名称的示例包括 Number_hits
、temp99
、$credit
和 _name
。
声明变量
声明和初始化
在类似 let x = 42
的语句中,let x
部分称为声明,= 42
部分称为初始化程序。声明允许稍后在代码中访问变量,而不会引发ReferenceError
,而初始化程序则为变量赋值。在 var
和 let
声明中,初始化程序是可选的。如果未初始化声明的变量,则将其赋值为undefined
。
let x;
console.log(x); // logs "undefined"
从本质上讲,let x = 42
等效于 let x; x = 42
。
const
声明始终需要初始化程序,因为它们禁止在声明后进行任何类型的赋值,并且隐式地将其初始化为 undefined
很可能是程序员的错误。
const x; // SyntaxError: Missing initializer in const declaration
变量作用域
变量可能属于以下作用域之一
- 全局作用域:在脚本模式下运行的所有代码的默认作用域。
- 模块作用域:在模块模式下运行的代码的作用域。
- 函数作用域:使用函数创建的作用域。
- 块级作用域:使用一对花括号(块)创建的作用域。
当您在任何函数外部声明变量时,它被称为全局变量,因为它对当前文档中的任何其他代码都可用。当您在函数内部声明变量时,它被称为局部变量,因为它仅在该函数内部可用。
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 is 5
变量提升
var
声明的变量会被提升,这意味着你可以在其作用域的任何地方引用该变量,即使还没有到达其声明。你可以将var
声明视为被“提升”到其函数或全局作用域的顶部。但是,如果在声明变量之前访问它,则其值始终为undefined
,因为只有其声明和默认初始化(使用undefined
)会被提升,而其值赋值不会被提升。
console.log(x === undefined); // true
var x = 3;
(function () {
console.log(x); // undefined
var x = "local value";
})();
以上示例将被解释为
var x;
console.log(x === undefined); // true
x = 3;
(function () {
var x;
console.log(x); // undefined
x = "local value";
})();
由于提升,函数中的所有var
语句都应尽可能地放在函数的顶部。这种最佳实践提高了代码的清晰度。
let
和const
是否被提升是一个定义上的争论。在块中引用变量声明之前的变量始终会导致ReferenceError
,因为该变量从块的开始到声明被处理之前都处于“暂时性死区”中。
console.log(x); // ReferenceError
const x = 3;
console.log(y); // ReferenceError
let y = 3;
与仅提升声明而不提升其值的var
声明不同,函数声明会被完整提升——你可以在其作用域的任何地方安全地调用该函数。有关更多讨论,请参阅提升术语表条目。
全局变量
全局变量实际上是全局对象的属性。
在网页中,全局对象是window
,因此你可以使用window.variable
语法读取和设置全局变量。在所有环境中,globalThis
变量(它本身是一个全局变量)可用于读取和设置全局变量。这是为了在各种JavaScript运行时之间提供一致的接口。
因此,你可以从另一个窗口或框架访问在一个窗口或框架中声明的全局变量,方法是指定window
或frame
名称。例如,如果在一个文档中声明了一个名为phoneNumber
的变量,则可以从iframe
中将其引用为parent.phoneNumber
。
常量
你可以使用const
关键字创建一个只读的命名常量。常量标识符的语法与任何变量标识符相同:它必须以字母、下划线或美元符号($
)开头,并且可以包含字母、数字或下划线字符。
const PI = 3.14;
在脚本运行期间,常量不能通过赋值更改其值,也不能重新声明。它必须初始化为一个值。常量的作用域规则与let
块级作用域变量的作用域规则相同。
你不能在相同的作用域中声明与函数或变量同名的常量。例如
// THIS WILL CAUSE AN ERROR
function f() {}
const f = 5;
// THIS WILL CAUSE AN ERROR TOO
function f() {
const g = 5;
var g;
}
但是,const
仅阻止重新赋值,而不阻止修改。分配给常量的对象的属性不受保护,因此以下语句可以正常执行。
const MY_OBJECT = { key: "value" };
MY_OBJECT.key = "otherValue";
此外,数组的内容不受保护,因此以下语句可以正常执行。
const MY_ARRAY = ["HTML", "CSS"];
MY_ARRAY.push("JAVASCRIPT");
console.log(MY_ARRAY); // ['HTML', 'CSS', 'JAVASCRIPT'];
数据结构和类型
数据类型
数据类型转换
JavaScript是一种动态类型语言。这意味着在声明变量时,不必指定其数据类型。这也意味着在脚本执行期间会根据需要自动转换数据类型。
因此,例如,你可以如下定义一个变量
let answer = 42;
稍后,你可以为同一个变量分配一个字符串值,例如
answer = "Thanks for all the fish!";
因为JavaScript是动态类型的,所以此赋值不会导致错误消息。
数字和'+'运算符
在包含数字和字符串值以及+
运算符的表达式中,JavaScript会将数字值转换为字符串。例如,考虑以下语句
x = "The answer is " + 42; // "The answer is 42"
y = 42 + " is the answer"; // "42 is the answer"
z = "37" + 7; // "377"
对于所有其他运算符,JavaScript不会将数字值转换为字符串。例如
"37" - 7; // 30
"37" * 7; // 259
将字符串转换为数字
如果内存中表示数字的值是字符串,则有一些方法可以进行转换。
parseInt
仅返回整数,因此其在小数方面用途有限。
注意:此外,parseInt
的最佳实践是始终包含基数参数。基数参数用于指定要使用的数字系统。
parseInt("101", 2); // 5
另一种从字符串中获取数字的方法是使用+
(一元加号)运算符
"1.1" + "1.1"; // '1.11.1'
(+"1.1") + (+"1.1"); // 2.2
// Note: the parentheses are added for clarity, not required.
字面量
数组字面量
数组字面量是零个或多个表达式的列表,每个表达式表示一个数组元素,并用方括号([]
)括起来。当你使用数组字面量创建数组时,它会使用指定的作为其元素的值进行初始化,并且其length
设置为指定的参数数量。
以下示例创建了包含三个元素且length
为三的coffees
数组
const coffees = ["French Roast", "Colombian", "Kona"];
每次计算数组字面量时,它都会创建一个新的数组对象。例如,在全局作用域中使用字面量定义的数组在脚本加载时创建一次。但是,如果数组字面量在函数内部,则每次调用该函数时都会实例化一个新的数组。
数组字面量中的额外逗号
如果在数组字面量中连续放置两个逗号,则数组会为未指定的元素保留一个空插槽。以下示例创建了fish
数组
const fish = ["Lion", , "Angel"];
当你记录此数组时,你会看到
console.log(fish);
// [ 'Lion', <1 empty item>, 'Angel' ]
请注意,第二个项目是“空的”,这与实际的undefined
值并不完全相同。当使用Array.prototype.map
等遍历数组的方法时,会跳过空插槽。但是,索引访问fish[1]
仍然返回undefined
。
如果在元素列表的末尾包含尾随逗号,则会忽略该逗号。
在以下示例中,数组的length
为三。没有myList[3]
。列表中的所有其他逗号都表示一个新元素。
const myList = ["home", , "school"];
在以下示例中,数组的length
为四,并且myList[0]
和myList[2]
缺失。
const myList = [, "home", , "school"];
在以下示例中,数组的length
为四,并且myList[1]
和myList[3]
缺失。仅忽略最后一个逗号。
const myList = ["home", , "school", ,];
注意:尾随逗号有助于在具有多行数组时保持git差异的整洁,因为将项目追加到末尾只会添加一行,而不会修改上一行。
const myList = [
"home",
"school",
+ "hospital",
];
了解额外逗号的行为对于理解JavaScript作为一种语言非常重要。
但是,在编写自己的代码时,你应该显式地将缺失的元素声明为undefined
,或者至少插入注释以突出显示其缺失。这样做可以提高代码的清晰度和可维护性。
const myList = ["home", /* empty */, "school", /* empty */, ];
布尔字面量
数字字面量
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
。(字符的大小写不会改变其值。因此:0xa
=0xA
=10
和0xf
=0xF
=15
。) - 前导
0b
(或0B
)表示二进制整数字面量。二进制整数字面量只能包含数字0
和1
。 - 整数字面量后的
n
后缀表示BigInt
字面量。BigInt
字面量可以使用上述任何基数。请注意,像0123n
这样的前导零八进制语法是不允许的,但0o123n
是可以的。
一些整数字面量的示例如下
0, 117, 123456789123456789n (decimal, base 10) 015, 0001, 0o777777777777n (octal, base 8) 0x1123, 0x00111, 0x123456789ABCDEFn (hexadecimal, "hex" or base 16) 0b11, 0b0011, 0b11101001010101010101n (binary, base 2)
有关更多信息,请参阅词法语法参考中的数字字面量。
浮点字面量
浮点字面量可以具有以下部分
- 一个无符号十进制整数,
- 一个小数点(
.
), - 一个小数部分(另一个十进制数),
- 一个指数。
指数部分是由一个e
或E
后跟一个整数组成,该整数可以带符号(前面有+
或-
)。浮点数字面量必须至少包含一个数字,以及一个小数点或e
(或E
)。
更简洁地说,语法是
[digits].[digits][(E|e)[(+|-)]digits]
例如
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 : `Sorry, we don't sell ${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
此外,您可以使用数字或字符串字面量作为属性名称,或者在一个对象内部嵌套另一个对象。下面的示例使用了这些选项。
const car = { manyCars: { a: "Saab", b: "Jeep" }, 7: "Mazda" };
console.log(car.manyCars.b); // Jeep
console.log(car[7]); // Mazda
对象属性名称可以是任何字符串,包括空字符串。如果属性名称不是有效的JavaScript 标识符或数字,则必须将其用引号括起来。
不是有效标识符的属性名称不能作为点(.
)属性进行访问。
const unusualPropertyNames = {
'': 'An empty string',
'!': 'Bang!'
}
console.log(unusualPropertyNames.''); // SyntaxError: Unexpected string
console.log(unusualPropertyNames.!); // SyntaxError: Unexpected token !
相反,必须使用方括号表示法([]
)进行访问。
console.log(unusualPropertyNames[""]); // An empty string
console.log(unusualPropertyNames["!"]); // Bang!
增强型对象字面量
对象字面量支持一系列简写语法,包括在构造时设置原型、foo: foo
赋值的简写、定义方法、进行super
调用以及使用表达式计算属性名称。
这些特性也使对象字面量和类声明更加接近,并允许基于对象的程序设计从一些相同的便利中获益。
const obj = {
// __proto__
__proto__: theProtoObj,
// Shorthand for 'handler: handler'
handler,
// Methods
toString() {
// Super calls
return "d " + super.toString();
},
// Computed (dynamic) property names
["prop_" + (() => 42)()]: 42,
};
正则表达式字面量
正则表达式字面量(将在后面详细定义)是一个用斜杠括起来的模式。下面是一个正则表达式字面量的示例。
const re = /ab+c/;
字符串字面量
字符串字面量是由零个或多个字符组成,并用双引号("
)或单引号('
)括起来。字符串必须用相同类型的引号分隔(即,要么都用单引号,要么都用双引号)。
以下是字符串字面量的示例
'foo'
"bar"
'1234'
'one line \n another line'
"Joyo's cat"
除非您特别需要使用String
对象,否则应该使用字符串字面量。有关String
对象的详细信息,请参阅String
。
您可以对字符串字面量值调用任何String
对象的方法。JavaScript会自动将字符串字面量转换为一个临时的String
对象,调用该方法,然后丢弃该临时的String
对象。您还可以将length
属性与字符串字面量一起使用。
// Will print the number of symbols in the string including whitespace.
console.log("Joyo's cat".length); // In this case, 10.
模板字面量也可用。模板字面量用反引号(`
)(重音符)字符而不是双引号或单引号括起来。
模板字面量为构建字符串提供了语法糖。(这类似于Perl、Python等语言中的字符串插值功能。)
// Basic literal string creation
`In JavaScript '\n' is a line-feed.`
// Multiline strings
`In JavaScript, template strings can run
over multiple lines, but double and single
quoted strings cannot.`
// String interpolation
const name = 'Lev', time = 'today';
`Hello ${name}, how are you ${time}?`
标记模板是一种简洁的语法,用于指定模板字面量以及对用于解析它的“标记”函数的调用。标记模板只是调用一个处理字符串和一组相关值的函数的更简洁和语义化的方式。模板标记函数的名称位于模板字面量之前——如下面的示例所示,其中模板标记函数名为print
。print
函数将插值参数并序列化可能出现的任何对象或数组,避免烦人的[object Object]
。
const formatArg = (arg) => {
if (Array.isArray(arg)) {
// Print a bulleted list
return arg.map((part) => `- ${part}`).join("\n");
}
if (arg.toString === Object.prototype.toString) {
// This object will be serialized to "[object Object]".
// Let's print something nicer.
return JSON.stringify(arg);
}
return arg;
};
const print = (segments, ...args) => {
// For any well-formed template literal, there will always be N args and
// (N+1) string segments.
let message = segments[0];
segments.slice(1).forEach((segment, index) => {
message += formatArg(args[index]) + segment;
});
console.log(message);
};
const todos = [
"Learn JavaScript",
"Learn Web APIs",
"Set up my website",
"Profit!",
];
const progress = { javascript: 20, html: 50, css: 10 };
print`I need to do:
${todos}
My current progress is: ${progress}
`;
// I need to do:
// - Learn JavaScript
// - Learn Web APIs
// - Set up my website
// - Profit!
// My current progress is: {"javascript":20,"html":50,"css":10}
由于标记模板字面量只是函数调用的语法糖,因此您可以将其重写为等效的函数调用
print(["I need to do:\n", "\nMy current progress is: ", "\n"], todos, progress);
这可能让人想起console.log
样式的插值
console.log("I need to do:\n%o\nMy current progress is: %o\n", todos, progress);
您可以看到,与传统的“格式化程序”函数相比,标记模板的读取方式更自然,在传统的“格式化程序”函数中,变量和模板本身必须单独声明。
在字符串中使用特殊字符
除了普通字符外,您还可以像以下示例所示在字符串中包含特殊字符。
"one line \n another line";
下表列出了您可以在JavaScript字符串中使用的特殊字符。
字符 | 含义 |
---|---|
\0 |
空字符 |
\b |
退格 |
\f |
换页 |
\n |
换行 |
\r |
回车 |
\t |
制表符 |
\v |
垂直制表符 |
\' |
撇号或单引号 |
\" |
双引号 |
\\ |
反斜杠字符 |
\XXX |
由最多三个八进制数字XXX (介于0 和377 之间)指定的Latin-1编码的字符。例如,\251 是版权符号的八进制序列。 |
\xXX |
由两个十六进制数字XX (介于00 和FF 之间)指定的Latin-1编码的字符。例如,\xA9 是版权符号的十六进制序列。 |
\uXXXX |
由四个十六进制数字XXXX 指定的Unicode字符。例如,\u00A9 是版权符号的Unicode序列。请参阅Unicode转义序列。 |
\u{XXXXX} |
Unicode代码点转义。例如,\u{2F804} 与简单的Unicode转义\uD87E\uDC04 相同。 |
转义字符
对于表中未列出的字符,前面的反斜杠会被忽略,但此用法已过时,应避免使用。
您可以通过在引号前面加上反斜杠来在字符串中插入引号。这称为转义引号。例如
const quote = "He read \"The Cremation of Sam McGee\" by R.W. Service.";
console.log(quote);
这将产生以下结果:
He read "The Cremation of Sam McGee" by R.W. Service.
要在字符串中包含字面反斜杠,必须转义反斜杠字符。例如,要将文件路径c:\temp
分配给字符串,请使用以下方法:
const home = "c:\\temp";
您还可以通过在换行符前加上反斜杠来转义换行符。反斜杠和换行符都会从字符串的值中删除。
const str =
"this string \
is broken \
across multiple \
lines.";
console.log(str); // this string is broken across multiple lines.