[TOC]
# 第7章 JavaScript語法
JavaScript的語法相當簡單。本章介紹要注意的事項。
# 語法概述
本節將簡要介紹JavaScript的語法。
以下是五種基本值類型:
**布爾:**
```js
true
false
```
**數字:**
```js
1023
7.851
```
**字符串:**
```js
'hello'
"hello"
```
**簡單對象(對象字面量):**
```js
{
firstName: 'Jane',
lastName: 'Doe'
}
```
**數組:**
```js
[ 'apple', 'banana', 'cherry' ]
```
以下是基本語法的幾個示例:
```js
// Two slashes start single-linecomments
var x; // declaring a variable
x = 3 + y; // assigning a value to the variable `x`
foo(x, y); // calling function `foo` with parameters `x` and `y`
obj.bar(3); // calling method `bar` of object `obj`
// 一個條件語句
if (x === 0) { // Is `x` equal to zero?
x = 123;
}
// Defining function `baz` with parameters `a` and `b`
function baz(a, b) {
return a + b;
}
```
注意兩種不同的用途 等號:
* 單個等號(=)用于將值分配給變量。
* 三等號(===)用于比較兩個值(參見“ 等式運算符”)。
# 注釋
有兩種注釋:
通過`//`注釋該整行。以下是一個例子:
```
var a = 0; // init
```
多行注釋`/* */`可以擴展到任意范圍的文本。它們不能嵌套。這里有兩個例子:
```js
/* temporarily disabled
processNext(queue);
*/
function (a /* int */, b /* str */) {
}
```
# 表達式和語句
本節將介紹JavaScript中的重要句法區別:表達式和語句之間的區別。
## 表達式
一個表達式可以產生一個值,也可以在希望的地方被重新改寫,例如,如在函數調用中的參數或在賦值的右側。
以下每行都包含一個表達式:
```js
myvar
3 + x
myfunc('a', 'b')
```
## 語句([Statements](https://en.wikipedia.org/wiki/Statement_(computer_science)))
概略來說,一個語句執行一個動作。循環和`if`語句是語句的示例。程序基本上是一系列語句的組合。[ 8 ]
JavaScript在任何位置需要一個語句,您都可以編寫一個表達式。這樣的語句被稱為表達式語句。反之則不成立:如果JavaScript在該位置期望的是一個表達式,您不能編寫出一個語句。例如,`if`語句不能成為函數的參數
### 條件語句與條件表達式
如果我們觀察這兩種相似的句法范疇的成員,語句和表達式之間的區別將變得更清楚:
if語句和條件運算符(一個表達式)。
以下是一個`if`語句的示例:
```js
var salutation;
if (male) {
salutation = 'Mr.';
} else {
salutation = 'Mrs.';
}
```
有一個類似的 的表達式,條件運算符。前面的語句相當于以下代碼:
```js
var salutation = (male ? 'Mr.' : 'Mrs.');
```
等號和分號之間的代碼是一個表達式。括號不是必需的,但是如果我把它放在括號里,我發現條件運算符更容易閱讀。
### 使用模糊表達式作為語句
兩種表達式看起來像語句 - 他們的句法范疇是模糊的:
* 對象字面量(表達式)看起來像塊(語句):
```js
{
foo: bar(3, 5)
}
```
前面的結構要么是一個對象字面量(細節:[對象字面量](###)),要么是標簽后跟`foo:`標簽,后跟調用了`bar(3, 5)`的函數。
* 命名函數表達式看起來就像函數聲明(語句):
```js
function foo() { } //函數聲明的典型格式
var f = function foo(){console.log(typeof foo);}; //命名函數表達式
```
前面的結構是一個命名函數表達式或一個函數聲明。前者產生一個函數,后者創建一個變量并為其分配一個函數(兩種函數定義的細節:[函數定義](第15章))。
為了在解析過程中避免歧義,JavaScript不允許將對象字面量和函數表達式用作語句。也就是說,表達式語句不能以下面這兩個符合開始:
* 一個花括號
* 關鍵字 `function`
如果一個表達式從這兩個標記開始,它只能出現在一個表達式上下文中。例如,在表達式周圍放上括號,來遵守這個要求。接下來,我們將需要看看兩個例子。
### 通過eval()執行解析一個對象字面量
`eval`在語句上下文中解析它的參數。如果你想讓`eval`返回對象的話你必須在對象字面量上加上括號:
```js
> eval('{foo:123}')
123
> eval('({foo:123})')
{foo:123}
```
### 立即調用的函數表達式---IIFE
下列代碼是一個立即調用的函數表達式(IIFE),它是一個身體立即執行的函數(您將了解IIFE主要用途 [通過IIFE創建新作用域](第16章)):
```js
> (function () { return 'abc' }())
'abc'
```
如果你省略了括號,你會得到一個語法錯誤,因為JavaScript對于函數聲明,不能使用匿名聲明:
```js
> function () { return 'abc' }()
SyntaxError: function statement requires a name
```
如果你添加一個名字,你也會得到一個語法錯誤,因為函數聲明不能立即被調用:
```js
> function foo() { return 'abc' }()
SyntaxError: Unexpected token )
```
函數聲明中的任何一個都必須是合法的聲明,`()`卻不是。
## 控制流語句和塊
對于控制流語句,主體是一個單獨的語句。這里有兩個例子
```
if (obj !== null) obj.foo();
while (x > 0) x--;
```
然而,任何語句都可以被一個包含零個或多個語句的大括號替換。因此,你也可以寫
```js
if (obj !== null) {
obj.foo();
}
while (x > 0) {
x--;
}
```
我更喜歡后一種形式的控制流語句。對它進行標準化意味著單語句體和多語句體之間沒有區別。因此,您的代碼看起來更一致,在一個語句和多個語句之間進行切換更容易。
# 使用分號的規則
在這個部分, 我們檢查如何在JavaScript中使用分號。基本規則是:
* 通常,語句以分號終止。
* 異常是以塊結尾的語句。
分號在JavaScript中是可選的。缺失分號是通過所謂的自動分號插入添加的(ASI;參見[自動分號插入](第7章))添加缺少的分號。但是,該功能并不總是按預期方式工作,因此您應該始終包含分號。
## 以塊結尾的語句后沒有分號
以下語句如果以塊結尾,則不會以分號結尾:
* 循環:for,while(但不是do-while)
* 分支:if,switch,try
* 函數聲明(但不是函數表達式)
下面是一個例子while對比do-while:
```js
while (a > 0) {
a--;
} // no semicolon
do {
a--;
} while (a > 0);
```
這里是函數聲明與函數表達式的一個例子。后者隨后是分號,因為它出現在`var`聲明中(以分號結尾)::
```js
function foo() {
// ...
} // no semicolon
var foo = function () {
// ...
};
```
| 注意 |
| --- |
| 如果在塊之后添加分號,不會收到語法錯誤,因為它被認為是空的語句(請參閱下一節)。 |
| 提示 |
| --- |
| 這是你需要知道的關于分號的大部分內容。如果您總是添加分號,則可以不閱讀本節的其余部分的。 |
## 空語句
分號本身是空的語句,什么也不做。空語句可以出現在任何語句的任何地方。它們在需要聲明但不需要的情況下很有用。在這種情況下,通常也允許塊。例如,以下兩個語句是等價的:
```js
while (processNextItem() > 0);
while (processNextItem() > 0) {}
```
函數`processNextItem`被假定為返回剩余條目的數量。
下面的程序由三個空語句組成,在語法上也是正確的:
```js
;;;
```
## 自動分號(ASI automatic semicolon insertion )
ECMAScript 提供[自動分號插入(ASI)機制](http://www.ecma-international.org/ecma-262/6.0/index.html#sec-automatic-semicolon-insertion),自動分號插入(ASI)的目標是在一行的末尾讓分號成為可選的。
自動分號插入是 JavaScript 解析器為您插入分號(內部通常以不同的方式處理)。
換句話說,ASI 幫助解析器確定語句何時結束。通常,它以分號結尾。ASI規定,出現下面的情況,語句(statement)也會結束:
* 一行結束符(例如,換行),后面跟著一個非法的標記。
* 遇到關閉的大括號。
* 到達文件的結尾。
### 示例:通過非法標記進行 ASI
以下代碼包含一個行終止符,后跟非法標記:
```js
if (a < 0) a = 0
console.log(a)
```
0之后的標記`console`是非法的,并且觸發ASI:
```js
if (a < 0) a = 0;
console.log(a);
```
### 示例:通過關閉大括號進行 ASI
在下面的代碼中,大括號內的語句沒有被分號終止:
```js
function add(a,b) { return a+b }
```
ASI創建了前面代碼的語法正確的版本:
```js
function add(a,b) { return a+b; }
```
### 陷阱:ASI 可以意外地分解語句
如果關鍵字后面有一個行終止符,也會觸發 ASI 。例如:`return`
```js
// Don't do this
return
{
name: "John"
};
```
ASI將前述轉為:
```
return;
{
name: "John"
};
```
這返回了`undefined`,后面是一個包含了`name`標記的表達式語句為`"John"`的塊。
這個問題通過刪除`return`和對象之間的換行來解決:
~~~
return {
name: "John"
};
~~~
我的建議是研究[自動分號插入的確切方式](http://www.bradoncode.com/blog/2015/08/26/javascript-semi-colon-insertion/),以避免這種情況。
當然,永遠不要在`return`和返回的表達式之間放置換行符。
### 陷阱:ASI可能會意外地沒有被觸發
有時,一條新行中的語句以一個標記作為延續,作為前一個語句的延續。那么ASI不會被觸發,即使它似乎應該被觸發。例如:
```js
func()
[ 'ul', 'ol' ].forEach(function (t) { handleTag(t) })
```
第二行中的方括號被解釋為由`func()`返回的結果的索引。括號內的逗號被解釋為逗號運算符(在本例中返回`'ol'`;請參見[逗號操作符](第9章))。因此,JavaScript將前面的代碼視為:
```js
func()['ol'].forEach(function (t) { handleTag(t) });
```
## 合法標識符
標識符用于命名事物,并出現在JavaScript中的各種語法角色中。例如,變量的名稱和未引用的屬性鍵必須是有效的標識符。標識符是大小寫敏感的。
標識符的開頭字符是以下之一:
* 任何Unicode字母,包括拉丁字母,如D,希臘字母如λ,和西里爾字母,如Д
* 美元符號($)
* 下劃線(_)
后續字符是:
* 任何合法的開頭(第一個)字符
* Unicode類別中的任何Unicode數字“十進制數(Nd)”; 這包括歐洲數字如7和印度數字如3
* 各種其他Unicode標記和標點符號
合法標識符示例:
```js
var ε = 0.0001;
var строка = '';
var _tmp;
var $foo2;
```
盡管這使您能夠在JavaScript代碼中使用多種人類語言,但我還是建議您使用英語,以識別標識符和注釋。這確保了你的代碼可以被最大的人群理解,這一點很重要,因為現在有多少代碼可以在國際上傳播。
以下標識符是保留字 - 它們是語法的一部分,不能用作變量名(包括函數名和參數名):
<table>
<tr>
<td>arguments</td>
<td>break</td>
<td> case </td>
<td>catch </td>
</tr>
<tr>
<td>class</td>
<td> const </td>
<td> continue </td>
<td> debugger </td>
</tr>
<tr>
<td>default</td>
<td> delete</td>
<td> do </td>
<td> else </td>
</tr>
<tr>
<td> enum</td>
<td> export</td>
<td> extends</td>
<td> false</td>
</tr>
<tr>
<td>finally</td>
<td> for</td>
<td> function </td>
<td> if </td>
</tr>
<tr>
<td> implements</td>
<td> import</td>
<td> in</td>
<td> instanceof</td>
</tr>
<tr>
<td>interface</td>
<td> let</td>
<td> new</td>
<td> null </td>
</tr>
<tr>
<td> package</td>
<td> private</td>
<td> protected</td>
<td> public</td>
</tr>
<tr>
<td>interface</td>
<td> let</td>
<td> new</td>
<td> null </td>
</tr>
<tr>
<td> return</td>
<td> static</td>
<td> super</td>
<td> switch</td>
</tr>
<tr>
<td>this</td>
<td> throw</td>
<td> true</td>
<td> try </td>
</tr>
<tr>
<td> typeof</td>
<td> var</td>
<td> void</td>
<td> while</td>
</tr>
</table>
以下三個標識符不是保留字,但您應該將它們視為:
<table>
<tr>
<td> Infinity</td>
<td> NaN</td>
<td> undefined</td>
</tr>
</table>
最后,你也應該遠離標準全局變量的名稱(見[第23章](###))。您可以將它們用于局部變量,而不會破壞任何內容,但是您的代碼仍然會變得混亂。
請注意,您可以使用保留字作為未引用的屬性鍵(從ECMAScript 5開始):
```js
> var obj = { function: 'abc' };
> obj.function
'abc'
```
您可以在Mathias Bynens的博客文章“[有效的JavaScript變量名稱](http://mathiasbynens.be/notes/javascript-identifiers)”中查找標識符的準確規則。
## 數字字面量的方法調用
對于方法調用,區分浮點點和方法調用點是很重要的。因此,您不能編寫`1.toString();`您必須使用下列選項之一:
```js
1..toString()
1 .toString() // 點之前有空格
(1).toString()
1.0.toString()
```
## 嚴格模式
ECMAScript 5具有嚴格的模式,導致更清晰的JavaScript,具有更少的不安全功能,更多的警告和更多的邏輯行為。正常(非限制)模式有時稱為“草率模式”。
### 啟用嚴格模式
您可以通過在js文件中,或者`<script>`標簽里面,首行輸入以下代碼來開啟嚴格模式:
```js
'use strict';
```
請注意,不支持ECMAScript 5的JavaScript引擎將簡單地忽略前面的語句,因為以這種方式寫入字符串(作為表達式語句;參見[語句](###))通常不執行任何操作。
每個功能也可以打開嚴格的模式。為此,請寫下你的函數:
```js
function foo() {
'use strict';
...
}
```
當您使用遺留代碼庫時,這是很方便的,在任何地方切換嚴格模式可能會破壞一些東西
### 嚴格模式:建議與注意事項
一般來說,通過嚴格模式啟用的更改都是更好的。因此,強烈建議您將其用于寫入的新代碼 - 只需在文件開頭打開即可。但是,有兩個注意事項:
* **為現有代碼啟用嚴格模式可能會破壞它**
該代碼可能依賴于不再可用的功能,或者它可能依賴于在草率模式下不同于嚴格模式的行為。不要忘記,您可以選擇將啟用嚴格模式的函數添加到處于草率模式的文件中。
* **小心包裝**
當您連接和/或壓縮minify文件時,必須注意嚴格模式不會被關閉,因為它應該被打開,反之亦然。兩者都可以打破代碼。
以下部分將詳細說明嚴格模式特性。你通常不需要知道他們,因為你會對一些你不應該做的事情有更多的警告。
### 嚴格模式下必須聲明變量
所有變量都必須 以嚴格模式明確聲明。這有助于防止打字錯誤。在草率模式下,分配給未聲明的變量將創建一個全局變量:
```js
function sloppyFunc() {
sloppyVar = 123;
}
sloppyFunc(); // 隱式的創建了全局變量 `sloppyVar`
console.log(sloppyVar); // 123
```
在嚴格模式下,分配給未聲明的變量會引發異常:
```js
function strictFunc() {
'use strict';
strictVar = 123;
}
strictFunc(); // ReferenceError: strictVar is not defined
```
### 嚴格模式下的函數
嚴格模式限制函數相關功能。
#### 函數必須聲明在頂層的范圍
在嚴格模式下,所有函數必須在一個范圍的頂層(全局范圍或函數內直接聲明)中聲明。這意味著您不能在函數塊中放置函數聲明。如果你這樣做,你會得到一個描述性的SyntaxError。例如,V8告訴你:“在嚴格模式代碼中,函數只能在頂層聲明或者立即在另一個函數中調用:”
```js
function strictFunc() {
'use strict';
if (true) {
// SyntaxError:
function nested() {
}
}
}
```
這是一些沒有用的東西,因為函數是在周圍函數的范圍內創建的,而不是塊內的。
如果要解決此限制,可以通過變量聲明和函數表達式在塊內創建一個函數:
```js
function strictFunc() {
'use strict';
if (true) {
// OK:
var nested = function () {
};
}
}
```
#### 更嚴格的函數參數規則
函數參數的規則不允許:
禁止使用兩次相同的參數名稱,因為具有相同的名稱的局部變量作為參數。
#### 參數對象具有較少的屬性
`arguments`對象是在嚴格模式會更簡單:屬性`arguments.callee`和`arguments.caller`已被去除,則不能分配給`arguments`,并且`arguments`不跟蹤參數變化(如果一個參數改變,相應的數組元素不會隨之改變)。[arguments的棄用功能](#第15章)查看更多。
#### 這在非方法函數中是未定義的
在草率模式中,在非方法函數的`this`值是全局對象(在瀏覽器中是`window`;參見[全局對象](#第16章)):
```js
function sloppyFunc() {
console.log(this === window); // true
}
```
在嚴格的模式下,它是`undefined`:
```js
function strictFunc() {
'use strict';
console.log(this === undefined); // true
}
```
這對于構造函數很有用。例如,以下構造函數,`Point`處于嚴格模式:
```js
function Point(x, y) {
'use strict';
this.x = x;
this.y = y;
}
```
由于嚴格的模式,當您意外忘記`new`并將其當作函數調用,您會收到警告:
```js
var pt = Point(3, 1);
TypeError: Cannot set property 'x' of undefined
```
在草率模式下,你沒有得到警告,全局變量`x`和`y`被創建。有關詳細信息,請參閱[實現構造函數的小貼士](#第17章)。
### 嚴格模式中,設置或者刪除不可改變的屬性會拋出異常
對屬性的非法操作會在嚴格模式下拋出異常。例如,試圖設置只讀屬性的值會拋出一個異常,就像試圖刪除一個不可配置的屬性一樣。這是前一個例子:
```js
var str = 'abc';
function sloppyFunc() {
str.length = 7; // no effect, silent failure
console.log(str.length); // 3
}
function strictFunc() {
'use strict';
str.length = 7; // TypeError: 不能設置只讀屬性length
}
```
### 嚴格模式中的不合格標識符不能刪除
在草率模式下,您可以刪除如下所示的全局變量foo:
```
delete foo
```
在嚴格模式下,只要您嘗試刪除不合格的標識符,就會收到語法錯誤。您仍然可以刪除全局變量:
```js
delete window.foo; // browsers
delete global.foo; // Node.js
delete this.foo; // everywhere (in global scope)
```
### 嚴格模式中,`eval()`更加簡潔
在嚴格模式下,`eval()`功能變得不那么古怪:在被執行的字符串中聲明的變量不會被添加到eval()周圍的作用域。有關詳細信息,請參閱[使用eval()執行代碼](第23章)。
### 嚴格模式中禁用的特性
在嚴格模式下禁用的兩個JavaScript特性:
* 該`with`聲明是不允許的(參見[With with Statement](第13章))。您在編譯時會收到語法錯誤(加載代碼時)。
* 不再存在八進制數字:在草率的模式下,一個帶著前導零`0`的整數被解釋為八進制(基數8)。例如:
```js
010 === 8
true
```
在嚴格模式下,如果您使用這種字面值,則會收到一個語法錯誤:
```js
function f() { 'use strict'; return 010 }
SyntaxError: Octal literals are not allowed in strict mode.
```
*[ 8 ] 為了簡單起見,我假裝聲明是語句。*
- 本書簡介
- 前言
- 關于這本書你需要知道些什么
- 如何閱讀本書
- 目錄
- I. JavaScript的快速入門
- 第1章 基礎的JavaScript
- II. 背景知識
- 第2章 為什么選擇JavaScript?
- 第3章 JavaScript的性質
- 第4章 JavaScript是如何創造出來的
- 第5章 標準化:ECMAScript
- 第6章 JavaScript的歷史里程碑
- III. 深入JavaScript
- 第7章 JavaScript語法
- 第8章 值
- 第9章 運算符
- 第10章 布爾值
- 第11章 數字
- 第12章 字符串
- 第13章 語句
- 第14章 異常捕獲
- 第15章 函數
- 第16章 變量:作用域、環境和閉包
- 第17章 對象和繼承
- 第18章 數組
- 第19章 正則表達式
- 第20章 Date
- 第21章 Math
- 第22章 JSON
- 第23章 標準全局變量
- 第24章 編碼和JavaScript
- 第25章 ECMAScript 5中的新功能
- IV. 技巧、工具和類庫
- 第26章 元代碼樣式指南
- 第27章 調試的語言機制
- 第28章 子類化內置構造函數
- 第29章 JSDoc:生成API文檔
- 第30章 類庫
- 第31章 模塊系統和包管理器
- 第32章 其他工具
- 第33章 接下來該做什么
- 著作權