## 6.语法
> 原文: [http://exploringjs.com/impatient-js/ch_syntax.html](http://exploringjs.com/impatient-js/ch_syntax.html)
>
> 贡献者:[Hunter-liu](https://github.com/lq920320)
### 6.1. JavaScript 语法概述
#### 6.1.1. 基本语法
注释:
```javascript
// 单行注释
```
```javascript
/*
多行
注释
*/
```
*原始*(原子)值:
```javascript
// Booleans
true
false
// Numbers (JavaScript 的数字只有一种类型)
-123
1.414
// String (JavaScript 的字符没有对应的类型)
'abc'
"abc"
```
*断言*描述了计算结果的预期结果,如果这些期望不正确则抛出异常。例如,以下断言声明计算 7 加 1 的结果必须为 8:
```javascript
assert.equal(7 + 1, 8);
```
`assert.equal()`是一个方法调用(对象是`assert`,方法是`.equal()`),有两个参数:实际结果和预期结果。它是 Node.js 断言 API 的一部分,本书后面的[将对此进行解释](/docs/10.md)。
打印日志到[浏览器的控制台](/docs/7.md#531浏览器控制台)或 Node.js:
```javascript
// 将值标准输出打印(另一种方法调用)
console.log("Hello!");
```
```javascript
// 将错误信息标准输出打印
console.error('Something weng wrong!');
```
运算符:
```javascript
// 布尔运算符
assert.equal(true && false, false); // 与
assert.equal(true || false, true); // 或
// 数学运算符
assert.equal(3 + 4, 7);
assert.equal(5 - 1, 4);
assert.equal(3 * 4, 12);
assert.equal(9 / 3, 3);
// 字符串操作符
assert.equal('a' + 'b', 'ab');
assert.equal('I see ' + 3 + ' monkeys', 'I see 3 monkeys');
// 比较运算符
assert.equal(3 < 4, true);
assert.equal(3 <= 4, true);
assert.equal('abc' === 'abc', true);
assert.equal('abc' !== 'def', true);
```
声明变量:
```javascript
let x; // 声明 x(可变的)
x = 3 * 5; // 给 x 赋值
let y = 3 * 5; // 声明变量并为其赋值
const z = 8; // 声明 y(不可变的)
```
控制流声明:
```javascript
// 条件语句
if (x < 0) { // x 小于 0?
x = -x;
}
```
普通函数声明:
```javascript
// add1() 有 a 和 b 两个参数
function add1(a, b) {
return a + b;
}
// 调用函数 add1()
assert.equal(add1(5, 2), 7);
```
箭头函数表达式(特别用作函数调用和方法调用的参数):
```javascript
const add2 = (a, b) => a + b;
// 调用方法 add2()
assert.equal(add2(5, 2), 7);
const add3 = (a, b) => { return a + b };
```
上面的代码包含以下箭头函数(关于*表达式*和*语句块*等术语将在在[本章后面解释](/docs/8.md#64-语句与表达)):
```javascript
// 主体为一个表达式的箭头函数
(a, b) => a + b
// 主体为一个代码块的箭头函数
(a, b) => { return a + b }
```
对象:
```javascript
// 通过 object 符号({})创建一个对象
const obj = {
first: 'Jane', // 属性
last: 'Doe', // 属性
getFullName() { // 属性(方法)
return this.first + ' ' + this.last;
},
};
// 获取某个属性值
assert.equal(obj.first, 'Jane');
// 设置某个属性的值
obj.first = 'Janey';
// 调用对象的方法
assert.equal(obj.getFullName(), 'Janey Doe');
```
数组(数组也是对象):
```javascript
// 通过 Array 符号([])创建一个数组
const arr = ['a', 'b', 'c'];
// 获取某个数组元素
assert.equal(arr[1], 'b');
// 设置某个数组元素的值
arr[1] = 'β';
```
#### 6.1.2. 模块
每个模块都是一个文件。例如,考虑以下两个包含模块的文件:
```js
file-tools.js
main.js
```
`file-tools.js`中的模块导出其功能`isTextFilePath()`:
```js
export function isTextFilePath(filePath) {
return filePath.endsWith('.txt');
}
```
`main.js`中的模块导入整个模块`path`和函数`isTextFilePath()`:
```js
// 导入整个模块 `path`
import * as path from 'path';
// 导入模块 file-tools.js export的一个函数
import {isTextFilePath} from './file-tools.js';
```
#### 6.1.3. 法定变量和属性名称
变量名称和属性名称的语法类别称为*标识符*。
标识符允许具有以下字符:
- Unicode 字母:`A` - `Z`,`a` - `z`(等)
- `$`,`_`
- Unicode 数字:`0` - `9`(等)
- 变量名不能以数字开头
有些单词在 JavaScript 中有特殊含义,称为*保留字*。例如:`if`,`true`,`const`。
保留字不能用作变量名:
```js
const if = 123;
// SyntaxError: Unexpected token if
```
但它们被允许作为属性的名称:
```
> const obj = { if: 123 };
> obj.if
123
```
#### 6.1.4. 连接类型
用于连接单词的常见类型是:
- 驼峰:`threeConcatenatedWords`
- 下划线(也称*蛇形*):`three_concatenated_words`
- 分隔线(也称*串形*):`three-concatenated-words`
#### 6.1.5. 命名大写
通常,JavaScript 使用驼峰大小写,但常量除外。
小写:
- 函数,变量:`myFunction`
- 方法:`obj.myMethod`
- CSS:
- CSS 实体:`special-class`
- 对应的 JavaScript 变量:`specialClass`
大写:
- 类名:`MyClass`
- 常数:`MY_CONSTANT`
- 常量也常用骆驼写成:`myConstant`
#### 6.1.6. 在哪里加分号?
在语句的最后:
```js
const x = 123;
func();
```
但是,如果该语句以大括号结尾,那就不加分号了:
```js
while (false) {
// ···
} // 无分号
function func() {
// ···
} // 无分号
```
但是,在这样的语句之后添加分号不是语法错误 - 它被解释为空语句:
```js
// 函数声明后跟空语句
function func() {
// ···
};
```
![](https://img.kancloud.cn/ff/a8/ffa8e16628cad59b09c786b836722faa.svg) **测验:基本**
参见[测验应用程序](/docs/11.md#91测验)。
### 6.2. (深入)
本章的所有其余部分都是深入说明(上面的内容)。
### 6.3. 身份标识
#### 6.3.1. 有效标识符(变量名等)
首字符:
- Unicode 字母(包括`é`和`ü`等重音字符和非拉丁字母的字符,如`α`)
- `$`
- `_`
后续字符:
- 合法的首字符
- Unicode 数字(包括东方阿拉伯数字)
- 一些其他 Unicode 标记和标点符号
例如:
```js
const ε = 0.0001;
const строка = '';
let _tmp = 0;
const $foo2 = true;
```
#### 6.3.2. 保留字
保留字不能是变量名,但它们可以是属性名。
所有 JavaScript *关键字*都是保留字:
> `await` `break` `case` `catch` `class` `const` `continue` `debugger` `default` `delete` `do` `else` `export` `extends` `finally` `for` `function` `if` `import` `in` `instanceof` `let` `new` `return` `static` `super` `switch` `this` `throw` `try` `typeof` `var` `void` `while` `with` `yield`
以下标记也是关键字,但目前未在该语言中使用:
> `enum` `implements` `package` `protected` `interface` `private` `public`
以下值是保留字:
> `true` `false` `null`
从技术上讲,这些单词不是保留的,但你也应该避免使用它们,因为它们实际上是关键字:
> `Infinity` `NaN` `undefined` `async`
您也不应将全局变量的名称(`String`,`Math`等)用于您自己的变量和参数。
### 6.4. 语句与表达
在本节中,我们将探讨 JavaScript 如何区分两种句法结构:_ 语句 _ 和 _ 表达式 _。之后,我们会发现这会导致问题,因为相同的语法可能意味着不同的东西,具体取决于它的使用位置。
![](https://img.kancloud.cn/7b/17/7b17729df07100ffa1c582a9b46ac13a.svg) **我们假装只有语句和表达**
为简单起见,我们假装 JavaScript 中只有语句和表达式。
#### 6.4.1. 语句
*语句* 是一段可以执行并执行某种操作的代码。例如,`if`是一段语句:
```js
let myStr;
if (myBool) {
myStr = 'Yes';
} else {
myStr = 'No';
}
```
语句的另一个例子:函数声明。
```js
function twice(x) {
return x + x;
}
```
#### 6.4.2. 表达式
*表达式*是可以*评估*以产生值的一段代码。例如,括号之间的代码是一个表达式:
```js
let myStr = (myBool ? 'Yes' : 'No');
```
括号之间使用的运算符 `_?_:_` 称为*三元运算符*。它是`if`语句的表达式版本。
让我们看一下表达式的更多例子。我们输入表达式,REPL 为我们评估它们:
```
> 'ab' + 'cd'
'abcd'
> Number('123')
123
> true || false
true
```
#### 6.4.3. 什么是允许的?
JavaScript 源代码中的当前位置决定了您可以使用哪种语法结构:
- 函数体必须是一系列语句:
```js
function max(x, y) {
if (x > y) {
return x;
} else {
return y;
}
}
```
- 函数调用或方法调用的参数必须是表达式:
```js
console.log('ab' + 'cd', Number('123'));
```
但是,表达式可以用作语句。然后将它们称为*表达式语句*。相反的情况并非如此:当上下文需要表达式时,你便不能使用语句。
以下代码演示了某个表达式`bar()`可以是表达式还是语句——它取决于上下文:
```js
console.log(bar());
bar();
```
### 6.5. 语法模糊
JavaScript 有几种语法歧义的编程结构:相同的语法被不同地解释,这取决于它是在语句上下文中还是在表达式上下文中使用。本节探讨了这一现象及其引发的陷阱。
#### 6.5.1. 相同的语法:函数声明和函数表达式
*函数声明*是一个声明:
```js
function id(x) {
return x;
}
```
*函数表达式*是一个表达式(`=`右侧的):
```js
const id = function me(x) {
return x;
};
```
#### 6.5.2. 相同的语法:object literal 和 block
在下面的代码中,`{}`是 *对象字面值*:一个创建空对象的表达式。
```js
const obj = {};
```
这是一个空代码块(声明):
```js
{
}
```
#### 6.5.3. 消除歧义
歧义只是语句上下文中的一个问题:如果 JavaScript 解析器遇到模糊语法,它不知道它是简单语句还是表达式语句。例如:
- 如果语句以`function`开头:它是函数声明还是函数表达式?
- 如果语句以`{`开头:它是对象字面值还是代码块?
为了解决歧义,以`function`或`{`开头的语句永远不会被解释为表达式。如果希望表达式语句以这些标记中的任何一个开头,则必须将其包装在括号中:
```js
(function (x) { console.log(x) })('abc');
// Output:
// 'abc'
```
在这段代码中:
1. 我们首先通过函数表达式创建一个函数:
```js
function (x) { console.log(x) }
```
1. 然后我们调用该函数:`('abc')`
#1 只被解释为表达式,因为我们将它包装在括号中。如果我们没有,我们会得到一个语法错误,因为 JavaScript 需要一个函数声明,之后还会警告缺少的函数名称。此外,你不能在函数声明后立即进行函数调用。
在本书的后面,我们将看到更多由语法模糊引起的陷阱的例子:
* [通过对象解构分配](/docs/41.md#3443语法陷阱通过对象解构分配)
* [从箭头函数](/docs/28.md#23333语法陷阱从箭头函数返回一个对象字面值)
### 6.6. 分号
#### 6.6.1. 分号的经验法则
每个语句都以分号结束。
```js
const x = 3;
someFunction('abc');
i++;
```
例外:以块结尾的语句。
```js
function foo() {
// ···
}
if (y > 0) {
// ···
}
```
以下情况有点棘手:
```js
const func = () => {}; // 分号!
```
整个`const`声明(一个语句)以分号结尾,但在其中,有一个箭头函数表达式。那就是:声明本身并不是以花括号结尾;它是嵌入式箭头函数表达式。这就是为什么最后会有一个分号的原因。
#### 6.6.2. 分号:控制语句
控制语句的主体本身就是一个声明。例如,这是`while`循环的语法:
```js
while (condition)
语句
```
正文可以是一行语句:
```js
while (a > 0) a--;
```
但代码块也是声明,因此也是控制声明的合法主体:
```js
while (a > 0) {
a--;
}
```
如果你想让一个循环有一个空的主体,那么你的首选便是一个空语句(它只是一个分号):
```js
while (processNextItem() > 0);
```
你的第二个选择是一个空语句块:
```js
while (processNextItem() > 0) {}
```
### 6.7. 自动分号插入(ASI)
虽然我建议总是写分号,但大多数都是 JavaScript 中的可选项。使这成为可能的机制称为*自动分号插入*(ASI)。在某种程度上,它可以纠正语法错误。
ASI 的工作原理如下。语句解析会直到出现以下情况:
- 分号
- 行终止符后跟非法标记
换句话说,ASI 可以看作在换行符处插入分号。接下来的小节将介绍 ASI 的陷阱。
#### 6.7.1. ASI 意外触发
关于 ASI 的好消息是——如果你不依赖它并且总是写分号——你只需要注意一个陷阱。这是 JavaScript 禁止在一些标记之后的换行符。如果插入换行符,也会插入分号。
最实际相关的标记是`return`。例如,考虑以下代码:
```js
return
{
first: 'jane'
};
```
此代码解析为:
```js
return;
{
first: 'jane';
}
;
```
也就是说,一个空的 return 语句,后跟一个代码块,后跟一个空语句。
为什么 JavaScript 会这样做?它可以防止在`return`之后意外返回一行中的值。
#### 6.7.2. ASI 意外没有触发
在某些情况下,当您认为 ASI 应该触发时,ASI *并没有触发*。对于那些不喜欢分号的人来说,这会使生活更加复杂,因为他们需要意识到这些情况。以下是三个例子。还有更多。
**例 1:**非预期的函数调用。
```js
a = b + c
(d + e).print()
```
解析为:
```js
a = b + c(d + e).print();
```
**例 2:**意外分裂。
```js
a = b
/hi/g.exec(c).map(d)
```
解析为:
```js
a = b / hi / g.exec(c).map(d);
```
**例 3:**非预期的属性访问。
```js
someFunction()
['ul', 'ol'].map(x => x + x)
```
被执行为:
```js
const propKey = ('ul','ol');
assert.equal(propKey, 'ol'); // 因为逗号
someFunction()[propKey].map(x => x + x);
```
### 6.8. 分号:最佳实践
我建议你要经常写分号:
- 我喜欢它提供代码的视觉结构——你清楚地看到一个语句何时结束。
- 要记住的规则较少。
- 大多数 JavaScript 程序员使用分号。
然而,也有许多人不喜欢添加分号的视觉混乱。如果你是其中之一:可能会认为没有它们的代码*亦是*合法的。我建议你使用工具来帮助您避免错误。以下是两个例子:
- 自动代码格式化程序 [Prettier](https://prettier.io) 可以配置为不使用分号。然后它会自动修复问题。例如,如果它遇到以方括号开头的行,则它以分号为前缀。
- 静态检查器 [ESLint](https://eslint.org) 有一套你可以表述你的首选样式的[规则](https://eslint.org/docs/rules/semi)(始终使用分号或尽可能少的分号),并向你对关键问题报警。
### 6.9. 严格模式
从 ECMAScript 5 开始,你可以选择在所谓的*严格模式*中执行 JavaScript。在该模式下,语言稍微清晰:不存在一些怪异的写法并且同时会抛出更多异常。
默认(非严格)模式也称为*草率模式*。
请注意,默认模式在模块和类中默认打开,因此在编写现代 JavaScript(几乎总是位于模块中)时,您并不需要了解它。在本书中,我假设严格模式始终打开。
#### 6.9.1. 开启严格模式
在旧脚本文件和 CommonJS 模块中,通过将以下代码放在第一行中,您可以为完整文件切换严格模式:
```js
'use strict';
```
关于这个“指令”的巧妙之处在于,5 之前的 ECMAScript 版本只是忽略它:它是一个什么都不做的表达式语句。
你还可以仅为单个函数打开严格模式:
```
function functionInStrictMode() {
'use strict';
}
```
#### 6.9.2. 示例:严格模式
让我们看一个示例,其中草率模式做一些严格模式不会做的坏事:更改未知变量(未通过`let`或类似创建)创建一个全局变量。
```js
function sloppyFunc() {
unknownVar1 = 123;
}
sloppyFunc();
// Created global variable `unknownVar1`:
assert.equal(unknownVar1, 123);
```
严格模式则做得更好:
```js
function strictFunc() {
'use strict';
unknownVar2 = 123;
}
assert.throws(
() => strictFunc(),
{
name: 'ReferenceError',
message: 'unknownVar2 is not defined',
});
```
`assert.throws()`要求它的第一个参数,某个函数,在调用时抛出`ReferenceError`。
![](https://img.kancloud.cn/ff/a8/ffa8e16628cad59b09c786b836722faa.svg) **测验:高级**
参见[测验应用程序](/docs/11.md#91测验)。
- I.背景
- 1.关于本书(ES2019 版)
- 2.常见问题:本书
- 3. JavaScript 的历史和演变
- 4.常见问题:JavaScript
- II.第一步
- 5.概览
- 6.语法
- 7.在控制台上打印信息(console.*)
- 8.断言 API
- 9.测验和练习入门
- III.变量和值
- 10.变量和赋值
- 11.值
- 12.运算符
- IV.原始值
- 13.非值undefined和null
- 14.布尔值
- 15.数字
- 16. Math
- 17. Unicode - 简要介绍(高级)
- 18.字符串
- 19.使用模板字面值和标记模板
- 20.符号
- V.控制流和数据流
- 21.控制流语句
- 22.异常处理
- 23.可调用值
- VI.模块化
- 24.模块
- 25.单个对象
- 26.原型链和类
- 七.集合
- 27.同步迭代
- 28.数组(Array)
- 29.类型化数组:处理二进制数据(高级)
- 30.映射(Map)
- 31. WeakMaps(WeakMap)
- 32.集(Set)
- 33. WeakSets(WeakSet)
- 34.解构
- 35.同步生成器(高级)
- 八.异步
- 36. JavaScript 中的异步编程
- 37.异步编程的 Promise
- 38.异步函数
- IX.更多标准库
- 39.正则表达式(RegExp)
- 40.日期(Date)
- 41.创建和解析 JSON(JSON)
- 42.其余章节在哪里?