## 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() {
// ···
};
```
 **測驗:基本**
參見[測驗應用程序](/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 如何區分兩種句法結構:_ 語句 _ 和 _ 表達式 _。之后,我們會發現這會導致問題,因為相同的語法可能意味著不同的東西,具體取決于它的使用位置。
 **我們假裝只有語句和表達**
為簡單起見,我們假裝 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`。
 **測驗:高級**
參見[測驗應用程序](/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.其余章節在哪里?