[TOC]
## 基本句法和變量
### 語句
JavaScript程序的執行單位為行(line),也就是一行一行地執行。一般情況下,每一行就是一個語句。
語句(statement)是為了完成某種任務而進行的操作,比如下面就是一行賦值語句:
~~~
var a = 1 + 3;
~~~
這條語句先用var命令,聲明了變量a,然后將`1 + 3`的運算結果賦值給變量a。
`1 + 3`叫做表達式(expression),指一個為了得到返回值的計算式。語句和表達式的區別在于,前者主要為了進行某種操作,一般情況下不需要返回值;后者則是為了得到返回值,一定會返回一個值。
凡是JavaScript語言中預期為值的地方,都可以使用表達式。比如,賦值語句的等號右邊,預期是一個值,因此可以放置各種表達式。一條語句可以包含多個表達式。
語句以分號結尾,一個分號就表示一個語句結束。多個語句可以寫在一行內。
~~~
var a = 1 + 3 ; var b = "abc";
~~~
分號前面可以沒有任何內容,JavaScript引擎將其視為空語句。
~~~
;;;
~~~
上面的代碼就表示3個空語句。(關于分號的更多介紹,請看后文《結尾的分號》一節。)
表達式不需要分號結尾。一旦在表達式后面添加分號,則JavaScript引擎就將表達式視為語句,這樣會產生一些沒有任何意義的語句。
~~~
1 + 3;
"abc";
~~~
上面兩行語句有返回值,但是沒有任何意義,因為只是返回一個單純的值,沒有任何其他操作。
### 變量
變量是對“值”的引用,使用變量等同于引用一個值。每一個變量都有一個變量名。
~~~
var a = 1;
~~~
上面的代碼先聲明變量a,然后在變量a與數值1之間建立引用關系,也稱將數值1“賦值”給變量a。以后,引用變量a就會得到數值1。最前面的var,是變量聲明命令。它表示通知解釋引擎,要創建一個變量a。
變量的聲明和賦值,是分開的兩個步驟,上面的代碼將它們合在了一起,實際的步驟是下面這樣。
~~~
var a;
a = 1;
~~~
如果只是聲明變量而沒有賦值,則該變量的值為undefined。
~~~
var a;
a
// undefined
~~~
JavaScript允許省略var,直接對未聲明的變量賦值。也就是說,var a = 1 與 a = 1,這兩條語句的效果相同。但是由于這樣的做法很容易不知不覺地創建全局變量(尤其是在函數內部),所以建議總是使用var命令聲明變量。
> 嚴格地說,var a = 1 與 a = 1,這兩條語句的效果不完全一樣,主要體現在delete命令無法刪除前者。不過,絕大多數情況下,這種差異是可以忽略的。
如果一個變量沒有聲明就直接使用,JavaScript會報錯,告訴你變量未定義。
~~~
x
// ReferenceError: x is not defined
~~~
上面代碼直接使用變量x,系統就報錯,告訴你變量x沒有聲明。
可以在同一條var命令中聲明多個變量。
~~~
var a,b;
~~~
JavaScirpt是一種動態類型語言,也就是說,變量的類型沒有限制,可以賦予各種類型的值。
~~~
var a = 1;
a = "hello";
~~~
上面代碼中,變量a起先被賦值為一個數值,后來又被重新賦值為一個字符串。第二次賦值的時候,因為變量a已經存在,所以不需要使用var命令。如果用了,就等于重新聲明一個變量a,會覆蓋掉前面的同名變量。
### 變量提升
JavaScript引擎的工作方式是,先解析代碼,獲取所有被聲明的變量,然后再一行一行地運行。這造成的結果,就是所有的變量的聲明語句,都會被提升到代碼的頭部,這就叫做變量提升(hoisting)。
~~~
console.log(a);
var a = 1;
~~~
上面代碼首先使用console.log方法,在控制臺(console)顯示變量a的值。這時變量a還沒有聲明和賦值,所以這是一種錯誤的做法,但是實際上不會報錯。因為存在變量提升,真正運行的是下面的代碼:
~~~
var a;
console.log(a);
a = 1;
~~~
最后的結果是顯示undefined,表示變量a已聲明,但還未賦值。
請注意,變量提升只對var命令聲明的變量有效,如果一個變量不是用var命令聲明的,就不會發生變量提升。
~~~
console.log(b);
b = 1;
~~~
上面的語句將會報錯,提示“ReferenceError: b is not defined”,即變量b未聲明,這是因為b不是用var命令聲明的,JavaScript引擎不會將其提升,而只是視為對頂層對象的b屬性的賦值。
### 標識符
標識符(identifier)是用來識別具體對象的一個名稱。最常見的標識符就是變量名,以及后面要提到的函數名。JavaScript語言的標識符對大小寫敏感,所以a和A是兩個不同的標識符。
標識符有一套命名規則,不符合規則的就是非法標識符。JavaScript引擎遇到非法標識符,就會報錯。
簡單說,標識符命名規則如下:
* 第一個字符可以是任意Unicode字母,以及美元符號($)和下劃線(_)。
* 第二個字符及后面的字符,還可以用數字。
下面這些都是合法的標識符。
~~~
arg0
_tmp
$elem
π
~~~
下面這些則是不合法的標識符。
~~~
1a
23
***
a+b
-d
~~~
中文是合法的標識符,可以用作變量名。
~~~
var 臨時變量 = 1;
~~~
> JavaScript有一些保留字,不能用作標識符:arguments、break、case、catch、class、const、continue、debugger、default、delete、do、else、enum、eval、export、extends、false、finally、for、function、if、implements、import、in、instanceof、interface、let、new、null、package、private、protected、public、return、static、super、switch、this、throw、true、try、typeof、var、void、while、with、yield。
另外,還有三個詞雖然不是保留字,但是因為具有特別含義,也不應該用作標識符:Infinity、NaN、undefined。
### 注釋
源碼中被JavaScript引擎忽略的部分就叫做注釋,它的作用是對代碼進行解釋。Javascript提供兩種注釋:一種是單行注釋,用//起頭;另一種是多行注釋,放在/* 和 */之間。
~~~
// 這是單行注釋
/*
這是
多行
注釋
*/
~~~
本教程后面的代碼部分,會采用這兩種形式說明代碼的運行結果,以及需要注意的地方。
此外,由于歷史上JavaScript兼容HTML代碼的注釋,所以也被視為單行注釋。
~~~
x = 1; <!-- x = 2;
--> x = 3;
~~~
上面代碼中,只有`x = 1`會執行,其他的部分都被注釋掉了。
需要注意的是,-->只有在行首,才會被當成單行注釋,否則就是一個運算符。
~~~
function countdown(n) {
while (n --> 0) console.log(n);
}
countdown(3)
// 2
// 1
// 0
~~~
上面代碼中,`n --> 0`實際上會當作`n-- > 0`,因為輸出2、1、0。
### 區塊
JavaScript使用大括號,將多個相關的語句組合在一起,稱為“區塊”(block)。
與大多數編程語言不一樣,JavaScript的區塊不構成單獨的作用域(scope)。也就是說,區塊中的變量與區塊外的變量,屬于同一個作用域。
~~~
{
var a = 1;
}
a // 1
~~~
上面代碼在區塊內部,聲明并賦值了變量a,然后在區塊外部,變量a依然有效,這說明區塊不構成單獨的作用域,與不使用區塊的情況沒有任何區別。所以,單獨使用的區塊在JavaScript中意義不大,很少出現。區塊往往用來構成其他更復雜的語法結構,比如for、if、while、functions等。
### 條件語句
JavaScript提供if結構和switch結構,完成條件判斷。
(1)if 結構
if結構先判斷一個表達式的布爾值,然后根據布爾值的真偽,執行不同的語句。
~~~
if (expression)
statement
~~~
上面是if結構的基本形式。需要注意的是,expression(表達式)必須放在圓括號中,表示對表達式求值。如果結果為true,就執行緊跟在后面的statement(語句);如果結果為false,則跳過statement。
~~~
if (m === 3)
m += 1;
~~~
上面代碼表示,只有在m等于3時,才會將其值加上1。
這種寫法要求statement只能有一個語句。如果想將多個語句放在statement之中,必須在if的條件判斷之后,加上大括號。
~~~
if (m === 3) {
m += 1;
}
~~~
建議總是在if語句中使用大括號,因為這樣方便插入語句。
(2)if...else結構
if代碼塊后面,還可以跟一個else代碼塊,表示括號中的表示式為false時,所要執行的代碼。
~~~
if (m === 3) {
// then
} else {
// else
}
~~~
上面代碼判斷變量m是否等于3,如果等于就執行if代碼塊,否則執行else代碼塊。
對同一個變量進行多次判斷時,多個if...else語句可以連寫在一起。
~~~
if (m === 0) {
// ...
} else if (m === 1) {
// ...
} else if (m === 2) {
// ...
} else {
// ...
}
~~~
else代碼塊總是跟隨離自己最近的那個if語句。
~~~
var m = 1;
var n = 2;
if (m !== 1)
if (n === 2) console.log('hello');
else console.log('world');
~~~
上面代碼不會有任何輸出,else代碼塊也不會得到執行,因為它跟著的是最近的那個if語句,相當于下面這樣。
~~~
if (m !== 1) {
if (n === 2) {
console.log('hello');
} else {
console.log('world');
}
}
~~~
如果想讓else代碼塊跟隨最上面的那個if語句,就要改變大括號的位置。
~~~
if (m !== 1) {
if (n === 2) {
console.log('hello');
}
} else {
console.log('world');
}
// world
~~~
(3)switch結構
多個if...else連在一起使用的時候,可以轉為使用更方便的switch結構。
~~~
switch (fruit) {
case "banana":
// ...
break;
case "apple":
// ...
break;
default:
// ...
}
~~~
上面代碼根據變量fruit的值,選擇執行相應的case。如果所有case都不符合,則執行最后的default部分。需要注意的是,每個case代碼塊內部的break語句不能少,否則會接下去執行下一個case代碼塊,而不是跳出switch結構。
switch語句部分和case語句部分,都可以使用表達式。
~~~
switch(1 + 3) {
case 2 + 2:
f();
break;
default:
neverhappens();
}
~~~
上面代碼的default部分,是永遠不會執行到的。
需要注意的是,switch語句后面的表達式與case語句后面的表示式,在比較運行結果時,采用的是嚴格相等運算符(===),而不是相等運算符(==),這意味著比較時不會發生類型轉換。
switch結構不利于代碼重用,往往可以用對象形式重寫。
~~~
var o = {
banana: function (){ return },
apple: function (){ return },
default: function (){ return }
};
if (o[fruit]){
o[fruit]();
} else {
o['default']();
}
~~~
### 循環語句
循環語句用于重復執行某個操作,它有多種形式。
(1)while循環
While語句包括一個循環條件,只要該條件為真,就不斷循環。
~~~
while (expression)
statement
~~~
while語句的循環條件是一個表達式(express),必須放在圓括號中。語句(statement)部分默認只能寫一條語句,如果需要包括多條語句,必須添加大括號。
~~~
while (expression){
statement
}
~~~
下面是while語句的一個例子。
~~~
var i = 0;
while (i<100){
console.log('i當前為:' + i);
i++;
}
~~~
上面的代碼將循環100次,直到i等于100為止。
(2)for循環
for語句是循環命令的另一種形式。
~~~
for(initialize; test; increment)
statement
// 或者
for(initialize; test; increment){
statement
}
~~~
它分成三步:
* 初始化(initialize):確定循環的初始值,只在循環開始時執行一次;
* 測試(test):檢查循環條件,只要為真就進行后續操作;
* 遞增(increment):完成后續操作,然后返回上一步,再一次檢查循環條件。
下面是一個循環打印數組每個元素的例子。
~~~
for (var i=0; i < arr.length; i++) {
console.log(arr[i]);
}
~~~
所有for循環,都可以改寫成while循環。
~~~
var i = 0;
while (i < arr.length) {
console.log(arr[i]);
i++;
}
~~~
for語句表達式的三個部分(initialize,test,increment),可以省略任何一個,也可以全部省略。
~~~
for (;;){
console.log('Hello World');
}
~~~
上面代碼省略了for語句表達式的三個部分,結果就導致了一個無限循環。
(3)do...while循環
do...while循環與while循環類似,唯一的區別就是先運行一次循環體,然后判斷循環條件。
~~~
do
statement
while(expression);
// 或者
do {
statement
} while(expression);
~~~
不管條件是否為真,do..while循環至少運行一次,這是這種結構最大的特點。另外,while語句后面的分號不能省略。
(4)break語句和continue語句
break語句和continue語句都具有跳轉作用,可以讓代碼不按既有的順序執行。
break語句用于跳出代碼塊或循環。
~~~
var i = 0;
while (i<100){
console.log('i當前為:' + i);
i++;
if (i === 10) break;
}
~~~
上面代碼只會執行10次循環,一旦i等于10,就會跳出循環。
continue語句用于立即終止本次循環,返回循環結構的頭部,開始下一次循環。
~~~
var i = 0;
while (i<100){
i++;
if (i%2===0) continue;
console.log('i當前為:' + i);
}
~~~
上面代碼只有在i為奇數時,才會輸出i的值。如果i為偶數,則直接進入下一輪循環。
如果存在多重循環,不帶參數的break語句和continue語句都只針對最內層循環。
(5)標簽(label)
JavaScript語言允許,語句的前面有標簽(label)。標簽通常與break語句和continue語句配合使用,跳出特定的循環。
~~~
top:
for (var i=0;i<3;i++){
for (var j=0;j<3;j++){
if (i===1 && j===1) break top;
console.log("i="+i+",j="+j);
}
}
// i=0,j=0
// i=0,j=1
// i=0,j=2
// i=1,j=0
~~~
上面代碼為一個雙重循環區塊,加上了top標簽(注意,top不用加引號)。當滿足一定條件時,使用break語句加上標簽名,直接跳出雙層循環。如果break語句后面不使用標簽,則只能跳出內層循環,進入下一次的外層循環。
continue語句也可以與標簽配合使用。
~~~
top:
for (var i=0;i<3;i++){
for (var j=0;j<3;j++){
if (i===1 && j===1) continue top;
console.log("i="+i+",j="+j);
}
}
// i=0,j=0
// i=0,j=1
// i=0,j=2
// i=1,j=0
// i=2,j=0
// i=2,j=1
// i=2,j=2
~~~
上面代碼在滿足一定條件時,使用continue語句加上標簽名,直接進入下一輪外層循環。如果continue語句后面不使用標簽,則只能進入下一輪的內層循環。
## 數據類型
### 概述
JavaScript語言的每一個值,都屬于某一種數據類型。JavaScript的數據類型,共有六個類別和兩個特殊值。
六個類別的數據類型又可以分成兩組:原始類型(primitive type)和合成類型(complex type)。
原始類型包括三種數據類型。
* 數值(number)
* 字符串(string)
* 布爾值(boolean)
“數值”就是整數和小數(比如1和3.14),“字符串”就是由多個字符組成的文本(比如"Hello World"),“布爾值”則是true(真)和false(假)兩個特定值。
合成類型也包括三種數據類型。
* 對象(object)
* 數組(array)
* 函數(function)
對象和數組是兩種不同的數據組合方式,而函數其實是處理數據的方法。JavaScript把函數當成一種數據類型,可以像其他類型的數據一樣,進行賦值和傳遞,這為編程帶來了很大的靈活性,體現了JavaScript作為“函數式語言”的本質。
這里需要明確的是,JavaScript的所有數據,都可以視為對象。不僅合成類型的數組和函數屬于對象的特例,就連原始類型的數據(數值、字符串、布爾值)也可以用對象方式調用。
除了上面這六個類別,JavaScript還定義了兩個特殊值null和undefined。
本書將分別詳細介紹這六個類別和兩個特殊值。其中,兩個特殊值和布爾類型比較簡單,將在本節介紹,其他類型將各自有單獨的一節。
### typeof運算符
JavaScript有三種方法,可以確定一個值到底是什么類型。
* typeof運算符
* instanceof運算符
* Object.prototype.toString方法
instanceof運算符和Object.prototype.toString方法,將在后文相關章節介紹。這里著重介紹typeof 運算符。
typeof運算符可以返回一個值的數據類型,可能有以下結果。
(1)原始類型
數值、字符串、布爾值分別返回number、string、boolean。
~~~
typeof 123 // "number"
typeof "123" // "string"
typeof false // "boolean"
~~~
(2)函數
函數返回function。
~~~
// 定義一個空函數
function f(){}
typeof f
// "function"
~~~
(3)undefined
undefined返回undefined。
~~~
typeof undefined
// "undefined"
~~~
利用這一點,typeof可以用來檢查一個沒有聲明的變量,而不報錯。
~~~
v
// ReferenceError: v is not defined
typeof v
// "undefined"
~~~
實際編程中,這個特點通常用在判斷語句。
~~~
// 錯誤的寫法
if (v){
// ...
}
// ReferenceError: v is not defined
// 正確的寫法
if (typeof v === "undefined"){
// ...
}
~~~
(4)其他
除此以外,都返回object。
~~~
typeof window // "object"
typeof {} // "object"
typeof [] // "object"
typeof null // "object"
~~~
從上面代碼可以看到,空數組([])的類型也是object,這表示在JavaScript內部,數組本質上只是一種特殊的對象。另外,null的類型也是object,這是由于歷史原因造成的,為了兼容以前的代碼,后來就沒法修改了,并不是說null就屬于對象,本質上null是一個類似于undefined的特殊值。
既然typeof對數組(array)和對象(object)的顯示結果都是object,那么怎么區分它們呢?instanceof運算符可以做到。
~~~
var o = {};
var a = [];
o instanceof Array // false
a instanceof Array // true
~~~
instanceof運算符的詳細解釋,請見《面向對象編程》一章。
### null和undefined
(1)相似性
首先,null與undefined都可以表示“無”,含義非常相似。將一個變量賦值為undefined或null,老實說,幾乎沒區別。
~~~
var a = undefined;
// 或者
var a = null;
~~~
上面代碼中,a變量分別被賦值為undefined和null,這兩種寫法幾乎等價。
在if語句中,都會被自動轉為false,相等運算符甚至直接報告兩者相等。
~~~
if (!undefined)
console.log('undefined is false');
// undefined is false
if (!null)
console.log('null is false');
// null is false
undefined == null
// true
~~~
上面代碼說明,兩者的行為是何等相似!Google公司開發的JavaScript語言的替代品Dart語言,就明確規定只有null,沒有undefined!
既然含義與用法都差不多,為什么要同時設置兩個這樣的值,這不是無端增加復雜度,令初學者困擾嗎?這與歷史原因有關。
(2)歷史原因
1995年JavaScript誕生時,最初像Java一樣,只設置了null作為表示"無"的值。根據C語言的傳統,null被設計成可以自動轉為0。
~~~
Number(null)
// 0
5 + null
// 5
~~~
但是,JavaScript的設計者Brendan Eich,覺得這樣做還不夠,有兩個原因。
首先,null像在Java里一樣,被當成一個對象。但是,JavaScript的數據類型分成原始類型和合成類型兩大類,Brendan Eich覺得表示"無"的值最好不是對象。
其次,JavaScript的最初版本沒有包括錯誤處理機制,發生數據類型不匹配時,往往是自動轉換類型或者默默地失敗。Brendan Eich覺得,如果null自動轉為0,很不容易發現錯誤。
因此,Brendan Eich又設計了一個undefined。他是這樣區分的:null是一個表示"無"的對象,轉為數值時為0;undefined是一個表示"無"的原始值,轉為數值時為NaN。
~~~
Number(undefined)
// NaN
5 + undefined
// NaN
~~~
但是,這樣的區分在實踐中很快就被證明不可行。目前,null和undefined基本是同義的,只有一些細微的差別。
(3)用法和含義
對于null和undefined,可以大致上像下面這樣理解。
null表示"沒有對象",即該處不應該有值。典型用法是:
* 作為函數的參數,表示該函數的參數不是對象。
* 作為對象原型鏈的終點。
undefined表示"缺少值",就是此處應該有一個值,但是還未定義。典型用法是:
* 變量被聲明了,但沒有賦值時,就等于undefined。
* 調用函數時,應該提供的參數沒有提供,該參數等于undefined。
* 對象沒有賦值的屬性,該屬性的值為undefined。
* 函數沒有返回值時,默認返回undefined。
~~~
var i;
i // undefined
function f(x){console.log(x)}
f() // undefined
var o = new Object();
o.p // undefined
var x = f();
x // undefined
~~~
(4)null的特殊之處
null的特殊之處在于,JavaScript把它包含在對象類型(object)之中。
~~~
typeof null // "object"
~~~
上面代碼表示,查詢null的類型,JavaScript返回object(對象)。
這并不是說null的數據類型就是對象,而是JavaScript早期部署中的一個約定俗成,其實不完全正確,后來再想改已經太晚了,會破壞現存代碼,所以一直保留至今。
(5)注意點
JavaScript的標識名區分大小寫,所以undefined和null不同于Undefined和Null(或者其他僅僅大小寫不同的詞形),后者只是普通的變量名。
### 布爾值
布爾值代表“真”和“假”兩個狀態。“真”用關鍵字true表示,“假”用關鍵字false表示。布爾值只有這兩個值。
下列運算符會返回布爾值:
* 兩元邏輯運算符: && (And),|| (Or)
* 前置邏輯運算符: ! (Not)
* 相等運算符:===,!==,==,!=
* 比較運算符:>,>=,<,<=
如果JavaScript預期某個位置應該是布爾值,會將該位置上現有的值自動轉為布爾值。轉換規則是除了下面六個值被轉為false,其他值都視為true。
* undefined
* null
* false
* 0
* NaN
* ""(空字符串)
布爾值往往用于程序流程的控制,請看一個例子。
~~~
if (""){ console.log(true);}
// 沒有任何輸出
~~~
上面代碼的if命令后面的判斷條件,預期應該是一個布爾值,所以JavaScript自動將空字符串,轉為布爾值false,導致程序不會進入代碼塊,所以沒有任何輸出。
需要特別注意的是,空數組([])和空對象({})對應的布爾值,都是true。
~~~
if ([]){ console.log(true);}
// true
if ({}){ console.log(true);}
// true
~~~
更多關于數據類型轉換的介紹,參見《數據類型轉換》一節。
## 結尾的分號
### 不使用分號結尾的語句
分號表示一條語句的結尾。但是,有一些語法結構不需要在語句的結尾添加分號,主要是以下三種情況。
(1)for和while循環
~~~
for(;;){} // 沒有分號
while(true){} // 沒有分號
~~~
需要注意的是do...while循環是有分號的。
~~~
do {
a--;
} while(a > 0); // 分號不能省略
~~~
(2)分支語句:if, switch, try
~~~
if (true) {} // 沒有分號
switch () {} // 沒有分號
try {} catch {} // 沒有分號
~~~
(3)函數的聲明語句
~~~
function f() {} // 沒有分號
~~~
但是函數表達式仍然要使用分號。
~~~
var f = function f() {};
~~~
以上三種情況,如果使用了分號,并不會出錯。因為,解釋引擎會把這個分號解釋為空語句。
### 分號的自動添加
除了本來就不寫分號的情況,JavaScript引擎還有一個特點,就是在應該寫分號卻沒寫的情況下,它會自動添加(Automatic Semicolon Insertion,簡稱ASI)。
~~~
var a = b + c
// 等同于
var a = b + c;
~~~
但是,這種自動添加不是絕對的。如果下一行的開始可以與本行的結尾連在一起解釋,就不會自動添加分號。
~~~
var
a
=
3
// 等同于
var a = 3;
"abc"
.length
// 等同于
"abc".length
~~~
上面代碼舉了兩個例子,每行的尾部都沒有分號,JavaScript并不會自動添加分號,因為每行的結尾與下一行的開頭可以放在一起解釋。下面這個例子也不會自動添加分號。
~~~
3 * (2 * (4 + (3 - 5)))
+
(10 * (27 / 6))
// 等同于
3 * (2 * (4 + (3 - 5))) + (10 * (27 / 6))
~~~
這些例子還是比較容易看出來的,但是下面的例子就不那么容易發現了。它們都不會自動添加分號。
~~~
var a = b + c
(d+e).toString();
/* 結果報錯,因為兩行連在一起,
解釋為c(d+e),
即對函數 c 的調用 */
a = b
/hi/g.exec(c).map(d);
/* 解釋為 a = b / hi / g.exec(c).map(d),
即把正則表達式的斜杠當作除法運算符 */
var a = "b"
[ "red", "green" ].forEach(function(c) { console.log(c) })
/* 結果報錯,因為兩行連在一起,
解釋為"b"["red", "green"],
即把字符串當作一個數組,按索引取值 */
var a = 0;
var f = function(x) { return x }
(a++)
/* f等于0,因為(a++)被
* 視為匿名函數的調用 */
return a +
b;
return (a
+ b)
obj.foo(arg1,
arg2)
~~~
一般來說,在沒有分號結尾的情況下,如果下一行起首的是(、 [ 、+、-、/這五個字符中的一個,分號不會被自動添加。只有下一行的開始與本行的結尾,無法放在一起解釋,JavaScript引擎才會自動添加分號。
~~~
if (a < 0) a = 0
console.log(a)
// 等同于下面的代碼,
// 因為0console沒有意義
if (a < 0) a = 0;
console.log(a)
~~~
另外,如果一行的起首是“自增”(++)或“自減”(--)運算符,則它們的前面會自動添加分號。
~~~
a = b = c = 1
a
++
b
--
c
console.log(a, b, c)
// 1 2 0
~~~
之所以會得到“1 2 0”的結果,原因是自增和自減運算符前,自動被加上了分號。上面的代碼實際上等同于下面的形式:
~~~
a = b = c = 1;
a;
++b;
--c;
~~~
如果continue、break、return和throw這四個語句后面,直接跟換行符,則會自動添加分號。這意味著,如果return語句返回的是一個對象的字面量,起首的大括號一定要寫在同一行,否則得不到預期結果。
~~~
return
{ first: "Jane" };
// 解釋成
return;
{ first: "Jane" };
~~~
由于解釋引擎自動添加分號的行為難以預測,因此編寫代碼的時候不應該省略行尾的分號。
省略結尾的分號,還有一個問題。有些JavaScript代碼壓縮器不會自動添加分號,因此遇到沒有分號的結尾,就會讓代碼保持原狀,而不是壓縮成一行。
## 參考鏈接
* Axel Rauschmayer,?[A quick overview of JavaScript](http://www.2ality.com/2011/10/javascript-overview.html)
* Axel Rauschmayer,?[Improving the JavaScript typeof operator](http://www.2ality.com/2011/11/improving-typeof.html)
* Axel Rauschmayer,?[Automatic semicolon insertion in JavaScript](http://www.2ality.com/2011/05/semicolon-insertion.html)
* Axel Rauschmayer,?[Categorizing values in JavaScript](http://www.2ality.com/2013/01/categorizing-values.html)
* Rod Vagg,?[JavaScript and Semicolons](http://dailyjs.com/2012/04/19/semicolons/)
- 第一章 導論
- 1.1 前言
- 1.2 為什么學習JavaScript?
- 1.3 JavaScript的歷史
- 第二章 基本語法
- 2.1 語法概述
- 2.2 數值
- 2.3 字符串
- 2.4 對象
- 2.5 數組
- 2.6 函數
- 2.7 運算符
- 2.8 數據類型轉換
- 2.9 錯誤處理機制
- 2.10 JavaScript 編程風格
- 第三章 標準庫
- 3.1 Object對象
- 3.2 Array 對象
- 3.3 包裝對象和Boolean對象
- 3.4 Number對象
- 3.5 String對象
- 3.6 Math對象
- 3.7 Date對象
- 3.8 RegExp對象
- 3.9 JSON對象
- 3.10 ArrayBuffer:類型化數組
- 第四章 面向對象編程
- 4.1 概述
- 4.2 封裝
- 4.3 繼承
- 4.4 模塊化編程
- 第五章 DOM
- 5.1 Node節點
- 5.2 document節點
- 5.3 Element對象
- 5.4 Text節點和DocumentFragment節點
- 5.5 Event對象
- 5.6 CSS操作
- 5.7 Mutation Observer
- 第六章 瀏覽器對象
- 6.1 瀏覽器的JavaScript引擎
- 6.2 定時器
- 6.3 window對象
- 6.4 history對象
- 6.5 Ajax
- 6.6 同域限制和window.postMessage方法
- 6.7 Web Storage:瀏覽器端數據儲存機制
- 6.8 IndexedDB:瀏覽器端數據庫
- 6.9 Web Notifications API
- 6.10 Performance API
- 6.11 移動設備API
- 第七章 HTML網頁的API
- 7.1 HTML網頁元素
- 7.2 Canvas API
- 7.3 SVG 圖像
- 7.4 表單
- 7.5 文件和二進制數據的操作
- 7.6 Web Worker
- 7.7 SSE:服務器發送事件
- 7.8 Page Visibility API
- 7.9 Fullscreen API:全屏操作
- 7.10 Web Speech
- 7.11 requestAnimationFrame
- 7.12 WebSocket
- 7.13 WebRTC
- 7.14 Web Components
- 第八章 開發工具
- 8.1 console對象
- 8.2 PhantomJS
- 8.3 Bower:客戶端庫管理工具
- 8.4 Grunt:任務自動管理工具
- 8.5 Gulp:任務自動管理工具
- 8.6 Browserify:瀏覽器加載Node.js模塊
- 8.7 RequireJS和AMD規范
- 8.8 Source Map
- 8.9 JavaScript 程序測試
- 第九章 JavaScript高級語法
- 9.1 Promise對象
- 9.2 有限狀態機
- 9.3 MVC框架與Backbone.js
- 9.4 嚴格模式
- 9.5 ECMAScript 6 介紹
- 附錄
- 10.1 JavaScript API列表
- 草稿一:函數庫
- 11.1 Underscore.js
- 11.2 Modernizr
- 11.3 Datejs
- 11.4 D3.js
- 11.5 設計模式
- 11.6 排序算法
- 草稿二:jQuery
- 12.1 jQuery概述
- 12.2 jQuery工具方法
- 12.3 jQuery插件開發
- 12.4 jQuery.Deferred對象
- 12.5 如何做到 jQuery-free?
- 草稿三:Node.js
- 13.1 Node.js 概述
- 13.2 CommonJS規范
- 13.3 package.json文件
- 13.4 npm模塊管理器
- 13.5 fs 模塊
- 13.6 Path模塊
- 13.7 process對象
- 13.8 Buffer對象
- 13.9 Events模塊
- 13.10 stream接口
- 13.11 Child Process模塊
- 13.12 Http模塊
- 13.13 assert 模塊
- 13.14 Cluster模塊
- 13.15 os模塊
- 13.16 Net模塊和DNS模塊
- 13.17 Express框架
- 13.18 Koa 框架