?
This document uses PHP Chinese website manual Release
基本句法和變量
語句
變量
變量提升
標(biāo)識符
注釋
區(qū)塊
條件語句
循環(huán)語句
數(shù)據(jù)類型
概述
typeof運(yùn)算符
null和undefined
布爾值
結(jié)尾的分號
不使用分號結(jié)尾的語句
分號的自動添加
參考鏈接
JavaScript程序的執(zhí)行單位為行(line),也就是一行一行地執(zhí)行。一般情況下,每一行就是一個語句。
語句(statement)是為了完成某種任務(wù)而進(jìn)行的操作,比如下面就是一行賦值語句:
var a = 1 + 3;
這條語句先用var命令,聲明了變量a,然后將1 + 3
的運(yùn)算結(jié)果賦值給變量a。
1 + 3
叫做表達(dá)式(expression),指一個為了得到返回值的計算式。語句和表達(dá)式的區(qū)別在于,前者主要為了進(jìn)行某種操作,一般情況下不需要返回值;后者則是為了得到返回值,一定會返回一個值。
凡是JavaScript語言中預(yù)期為值的地方,都可以使用表達(dá)式。比如,賦值語句的等號右邊,預(yù)期是一個值,因此可以放置各種表達(dá)式。一條語句可以包含多個表達(dá)式。
語句以分號結(jié)尾,一個分號就表示一個語句結(jié)束。多個語句可以寫在一行內(nèi)。
var a = 1 + 3 ; var b = "abc";
分號前面可以沒有任何內(nèi)容,JavaScript引擎將其視為空語句。
;;;
上面的代碼就表示3個空語句。(關(guān)于分號的更多介紹,請看后文《結(jié)尾的分號》一節(jié)。)
表達(dá)式不需要分號結(jié)尾。一旦在表達(dá)式后面添加分號,則JavaScript引擎就將表達(dá)式視為語句,這樣會產(chǎn)生一些沒有任何意義的語句。
1 + 3; "abc";
上面兩行語句有返回值,但是沒有任何意義,因?yàn)橹皇欠祷匾粋€單純的值,沒有任何其他操作。
變量是對“值”的引用,使用變量等同于引用一個值。每一個變量都有一個變量名。
var a = 1;
上面的代碼先聲明變量a,然后在變量a與數(shù)值1之間建立引用關(guān)系,也稱將數(shù)值1“賦值”給變量a。以后,引用變量a就會得到數(shù)值1。最前面的var,是變量聲明命令。它表示通知解釋引擎,要創(chuàng)建一個變量a。
變量的聲明和賦值,是分開的兩個步驟,上面的代碼將它們合在了一起,實(shí)際的步驟是下面這樣。
var a; a = 1;
如果只是聲明變量而沒有賦值,則該變量的值為undefined。
var a; a // undefined
JavaScript允許省略var,直接對未聲明的變量賦值。也就是說,var a = 1 與 a = 1,這兩條語句的效果相同。但是由于這樣的做法很容易不知不覺地創(chuàng)建全局變量(尤其是在函數(shù)內(nèi)部),所以建議總是使用var命令聲明變量。
嚴(yán)格地說,var a = 1 與 a = 1,這兩條語句的效果不完全一樣,主要體現(xiàn)在delete命令無法刪除前者。不過,絕大多數(shù)情況下,這種差異是可以忽略的。
如果一個變量沒有聲明就直接使用,JavaScript會報錯,告訴你變量未定義。
x // ReferenceError: x is not defined
上面代碼直接使用變量x,系統(tǒng)就報錯,告訴你變量x沒有聲明。
可以在同一條var命令中聲明多個變量。
var a,b;
JavaScirpt是一種動態(tài)類型語言,也就是說,變量的類型沒有限制,可以賦予各種類型的值。
var a = 1; a = "hello";
上面代碼中,變量a起先被賦值為一個數(shù)值,后來又被重新賦值為一個字符串。第二次賦值的時候,因?yàn)樽兞縜已經(jīng)存在,所以不需要使用var命令。如果用了,就等于重新聲明一個變量a,會覆蓋掉前面的同名變量。
JavaScript引擎的工作方式是,先解析代碼,獲取所有被聲明的變量,然后再一行一行地運(yùn)行。這造成的結(jié)果,就是所有的變量的聲明語句,都會被提升到代碼的頭部,這就叫做變量提升(hoisting)。
console.log(a); var a = 1;
上面代碼首先使用console.log方法,在控制臺(console)顯示變量a的值。這時變量a還沒有聲明和賦值,所以這是一種錯誤的做法,但是實(shí)際上不會報錯。因?yàn)榇嬖谧兞刻嵘?,真正運(yùn)行的是下面的代碼:
var a; console.log(a); a = 1;
最后的結(jié)果是顯示undefined,表示變量a已聲明,但還未賦值。
請注意,變量提升只對var命令聲明的變量有效,如果一個變量不是用var命令聲明的,就不會發(fā)生變量提升。
console.log(b); b = 1;
上面的語句將會報錯,提示“ReferenceError: b is not defined”,即變量b未聲明,這是因?yàn)閎不是用var命令聲明的,JavaScript引擎不會將其提升,而只是視為對頂層對象的b屬性的賦值。
標(biāo)識符(identifier)是用來識別具體對象的一個名稱。最常見的標(biāo)識符就是變量名,以及后面要提到的函數(shù)名。JavaScript語言的標(biāo)識符對大小寫敏感,所以a和A是兩個不同的標(biāo)識符。
標(biāo)識符有一套命名規(guī)則,不符合規(guī)則的就是非法標(biāo)識符。JavaScript引擎遇到非法標(biāo)識符,就會報錯。
簡單說,標(biāo)識符命名規(guī)則如下:
第一個字符可以是任意Unicode字母,以及美元符號($)和下劃線(_)。
第二個字符及后面的字符,還可以用數(shù)字。
下面這些都是合法的標(biāo)識符。
arg0 _tmp $elem π
下面這些則是不合法的標(biāo)識符。
1a 23 *** a+b -d
中文是合法的標(biāo)識符,可以用作變量名。
var 臨時變量 = 1;
JavaScript有一些保留字,不能用作標(biāo)識符: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。
另外,還有三個詞雖然不是保留字,但是因?yàn)榫哂刑貏e含義,也不應(yīng)該用作標(biāo)識符:Infinity、NaN、undefined。
源碼中被JavaScript引擎忽略的部分就叫做注釋,它的作用是對代碼進(jìn)行解釋。Javascript提供兩種注釋:一種是單行注釋,用//起頭;另一種是多行注釋,放在/* 和 */之間。
// 這是單行注釋/* 這是 多行 注釋 */
本教程后面的代碼部分,會采用這兩種形式說明代碼的運(yùn)行結(jié)果,以及需要注意的地方。
此外,由于歷史上JavaScript兼容HTML代碼的注釋,所以也被視為單行注釋。
x = 1; <!-- x = 2; --> x = 3;
上面代碼中,只有x = 1
會執(zhí)行,其他的部分都被注釋掉了。
需要注意的是,-->只有在行首,才會被當(dāng)成單行注釋,否則就是一個運(yùn)算符。
function countdown(n) { while (n --> 0) console.log(n); } countdown(3) // 2 // 1 // 0
上面代碼中,n --> 0
實(shí)際上會當(dāng)作n-- > 0
,因?yàn)檩敵?、1、0。
JavaScript使用大括號,將多個相關(guān)的語句組合在一起,稱為“區(qū)塊”(block)。
與大多數(shù)編程語言不一樣,JavaScript的區(qū)塊不構(gòu)成單獨(dú)的作用域(scope)。也就是說,區(qū)塊中的變量與區(qū)塊外的變量,屬于同一個作用域。
{ var a = 1; } a // 1
上面代碼在區(qū)塊內(nèi)部,聲明并賦值了變量a,然后在區(qū)塊外部,變量a依然有效,這說明區(qū)塊不構(gòu)成單獨(dú)的作用域,與不使用區(qū)塊的情況沒有任何區(qū)別。所以,單獨(dú)使用的區(qū)塊在JavaScript中意義不大,很少出現(xiàn)。區(qū)塊往往用來構(gòu)成其他更復(fù)雜的語法結(jié)構(gòu),比如for、if、while、functions等。
JavaScript提供if結(jié)構(gòu)和switch結(jié)構(gòu),完成條件判斷。
(1)if 結(jié)構(gòu)
if結(jié)構(gòu)先判斷一個表達(dá)式的布爾值,然后根據(jù)布爾值的真?zhèn)?,?zhí)行不同的語句。
if (expression) statement
上面是if結(jié)構(gòu)的基本形式。需要注意的是,expression(表達(dá)式)必須放在圓括號中,表示對表達(dá)式求值。如果結(jié)果為true,就執(zhí)行緊跟在后面的statement(語句);如果結(jié)果為false,則跳過statement。
if (m === 3) m += 1;
上面代碼表示,只有在m等于3時,才會將其值加上1。
這種寫法要求statement只能有一個語句。如果想將多個語句放在statement之中,必須在if的條件判斷之后,加上大括號。
if (m === 3) { m += 1; }
建議總是在if語句中使用大括號,因?yàn)檫@樣方便插入語句。
(2)if...else結(jié)構(gòu)
if代碼塊后面,還可以跟一個else代碼塊,表示括號中的表示式為false時,所要執(zhí)行的代碼。
if (m === 3) { // then } else { // else }
上面代碼判斷變量m是否等于3,如果等于就執(zhí)行if代碼塊,否則執(zhí)行else代碼塊。
對同一個變量進(jìn)行多次判斷時,多個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代碼塊也不會得到執(zhí)行,因?yàn)樗氖亲罱哪莻€if語句,相當(dāng)于下面這樣。
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結(jié)構(gòu)
多個if...else連在一起使用的時候,可以轉(zhuǎn)為使用更方便的switch結(jié)構(gòu)。
switch (fruit) { case "banana": // ... break; case "apple": // ... break; default: // ... }
上面代碼根據(jù)變量fruit的值,選擇執(zhí)行相應(yīng)的case。如果所有case都不符合,則執(zhí)行最后的default部分。需要注意的是,每個case代碼塊內(nèi)部的break語句不能少,否則會接下去執(zhí)行下一個case代碼塊,而不是跳出switch結(jié)構(gòu)。
switch語句部分和case語句部分,都可以使用表達(dá)式。
switch(1 + 3) { case 2 + 2: f(); break; default: neverhappens(); }
上面代碼的default部分,是永遠(yuǎn)不會執(zhí)行到的。
需要注意的是,switch語句后面的表達(dá)式與case語句后面的表示式,在比較運(yùn)行結(jié)果時,采用的是嚴(yán)格相等運(yùn)算符(===),而不是相等運(yùn)算符(==),這意味著比較時不會發(fā)生類型轉(zhuǎn)換。
switch結(jié)構(gòu)不利于代碼重用,往往可以用對象形式重寫。
var o = { banana: function (){ return }, apple: function (){ return }, default: function (){ return } }; if (o[fruit]){ o[fruit](); } else { o['default'](); }
循環(huán)語句用于重復(fù)執(zhí)行某個操作,它有多種形式。
(1)while循環(huán)
While語句包括一個循環(huán)條件,只要該條件為真,就不斷循環(huán)。
while (expression) statement
while語句的循環(huán)條件是一個表達(dá)式(express),必須放在圓括號中。語句(statement)部分默認(rèn)只能寫一條語句,如果需要包括多條語句,必須添加大括號。
while (expression){ statement }
下面是while語句的一個例子。
var i = 0; while (i<100){ console.log('i當(dāng)前為:' + i); i++; }
上面的代碼將循環(huán)100次,直到i等于100為止。
(2)for循環(huán)
for語句是循環(huán)命令的另一種形式。
for(initialize; test; increment) statement // 或者 for(initialize; test; increment){ statement }
它分成三步:
初始化(initialize):確定循環(huán)的初始值,只在循環(huán)開始時執(zhí)行一次;
測試(test):檢查循環(huán)條件,只要為真就進(jìn)行后續(xù)操作;
遞增(increment):完成后續(xù)操作,然后返回上一步,再一次檢查循環(huán)條件。
下面是一個循環(huán)打印數(shù)組每個元素的例子。
for (var i=0; i < arr.length; i++) { console.log(arr[i]); }
所有for循環(huán),都可以改寫成while循環(huán)。
var i = 0; while (i < arr.length) { console.log(arr[i]); i++; }
for語句表達(dá)式的三個部分(initialize,test,increment),可以省略任何一個,也可以全部省略。
for (;;){ console.log('Hello World');}
上面代碼省略了for語句表達(dá)式的三個部分,結(jié)果就導(dǎo)致了一個無限循環(huán)。
(3)do...while循環(huán)
do...while循環(huán)與while循環(huán)類似,唯一的區(qū)別就是先運(yùn)行一次循環(huán)體,然后判斷循環(huán)條件。
do statement while(expression); // 或者 do { statement } while(expression);
不管條件是否為真,do..while循環(huán)至少運(yùn)行一次,這是這種結(jié)構(gòu)最大的特點(diǎn)。另外,while語句后面的分號不能省略。
(4)break語句和continue語句
break語句和continue語句都具有跳轉(zhuǎn)作用,可以讓代碼不按既有的順序執(zhí)行。
break語句用于跳出代碼塊或循環(huán)。
var i = 0; while (i<100){ console.log('i當(dāng)前為:' + i); i++; if (i === 10) break; }
上面代碼只會執(zhí)行10次循環(huán),一旦i等于10,就會跳出循環(huán)。
continue語句用于立即終止本次循環(huán),返回循環(huán)結(jié)構(gòu)的頭部,開始下一次循環(huán)。
var i = 0; while (i<100){ i++; if (i%2===0) continue; console.log('i當(dāng)前為:' + i); }
上面代碼只有在i為奇數(shù)時,才會輸出i的值。如果i為偶數(shù),則直接進(jìn)入下一輪循環(huán)。
如果存在多重循環(huán),不帶參數(shù)的break語句和continue語句都只針對最內(nèi)層循環(huán)。
(5)標(biāo)簽(label)
JavaScript語言允許,語句的前面有標(biāo)簽(label)。標(biāo)簽通常與break語句和continue語句配合使用,跳出特定的循環(huán)。
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
上面代碼為一個雙重循環(huán)區(qū)塊,加上了top標(biāo)簽(注意,top不用加引號)。當(dāng)滿足一定條件時,使用break語句加上標(biāo)簽名,直接跳出雙層循環(huán)。如果break語句后面不使用標(biāo)簽,則只能跳出內(nèi)層循環(huán),進(jìn)入下一次的外層循環(huán)。
continue語句也可以與標(biāo)簽配合使用。
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語句加上標(biāo)簽名,直接進(jìn)入下一輪外層循環(huán)。如果continue語句后面不使用標(biāo)簽,則只能進(jìn)入下一輪的內(nèi)層循環(huán)。
JavaScript語言的每一個值,都屬于某一種數(shù)據(jù)類型。JavaScript的數(shù)據(jù)類型,共有六個類別和兩個特殊值。
六個類別的數(shù)據(jù)類型又可以分成兩組:原始類型(primitive type)和合成類型(complex type)。
原始類型包括三種數(shù)據(jù)類型。
數(shù)值(number)
字符串(string)
布爾值(boolean)
“數(shù)值”就是整數(shù)和小數(shù)(比如1和3.14),“字符串”就是由多個字符組成的文本(比如"Hello World"),“布爾值”則是true(真)和false(假)兩個特定值。
合成類型也包括三種數(shù)據(jù)類型。
對象(object)
數(shù)組(array)
函數(shù)(function)
對象和數(shù)組是兩種不同的數(shù)據(jù)組合方式,而函數(shù)其實(shí)是處理數(shù)據(jù)的方法。JavaScript把函數(shù)當(dāng)成一種數(shù)據(jù)類型,可以像其他類型的數(shù)據(jù)一樣,進(jìn)行賦值和傳遞,這為編程帶來了很大的靈活性,體現(xiàn)了JavaScript作為“函數(shù)式語言”的本質(zhì)。
這里需要明確的是,JavaScript的所有數(shù)據(jù),都可以視為對象。不僅合成類型的數(shù)組和函數(shù)屬于對象的特例,就連原始類型的數(shù)據(jù)(數(shù)值、字符串、布爾值)也可以用對象方式調(diào)用。
除了上面這六個類別,JavaScript還定義了兩個特殊值null和undefined。
本書將分別詳細(xì)介紹這六個類別和兩個特殊值。其中,兩個特殊值和布爾類型比較簡單,將在本節(jié)介紹,其他類型將各自有單獨(dú)的一節(jié)。
JavaScript有三種方法,可以確定一個值到底是什么類型。
typeof運(yùn)算符
instanceof運(yùn)算符
Object.prototype.toString方法
instanceof運(yùn)算符和Object.prototype.toString方法,將在后文相關(guān)章節(jié)介紹。這里著重介紹typeof 運(yùn)算符。
typeof運(yùn)算符可以返回一個值的數(shù)據(jù)類型,可能有以下結(jié)果。
(1)原始類型
數(shù)值、字符串、布爾值分別返回number、string、boolean。
typeof 123 // "number" typeof "123" // "string" typeof false // "boolean"
(2)函數(shù)
函數(shù)返回function。
// 定義一個空函數(shù) function f(){} typeof f // "function"
(3)undefined
undefined返回undefined。
typeof undefined // "undefined"
利用這一點(diǎn),typeof可以用來檢查一個沒有聲明的變量,而不報錯。
v // ReferenceError: v is not defined typeof v // "undefined"
實(shí)際編程中,這個特點(diǎn)通常用在判斷語句。
// 錯誤的寫法 if (v){ // ... } // ReferenceError: v is not defined // 正確的寫法 if (typeof v === "undefined"){ // ... }
(4)其他
除此以外,都返回object。
typeof window // "object" typeof {} // "object" typeof [] // "object" typeof null // "object"
從上面代碼可以看到,空數(shù)組([])的類型也是object,這表示在JavaScript內(nèi)部,數(shù)組本質(zhì)上只是一種特殊的對象。另外,null的類型也是object,這是由于歷史原因造成的,為了兼容以前的代碼,后來就沒法修改了,并不是說null就屬于對象,本質(zhì)上null是一個類似于undefined的特殊值。
既然typeof對數(shù)組(array)和對象(object)的顯示結(jié)果都是object,那么怎么區(qū)分它們呢?instanceof運(yùn)算符可以做到。
var o = {}; var a = []; o instanceof Array // false a instanceof Array // true
instanceof運(yùn)算符的詳細(xì)解釋,請見《面向?qū)ο缶幊獭芬徽隆?/p>
(1)相似性
首先,null與undefined都可以表示“無”,含義非常相似。將一個變量賦值為undefined或null,老實(shí)說,幾乎沒區(qū)別。
var a = undefined; // 或者 var a = null;
上面代碼中,a變量分別被賦值為undefined和null,這兩種寫法幾乎等價。
在if語句中,都會被自動轉(zhuǎn)為false,相等運(yùn)算符甚至直接報告兩者相等。
if (!undefined) console.log('undefined is false'); // undefined is false if (!null) console.log('null is false'); // null is false undefined == null // true
上面代碼說明,兩者的行為是何等相似!Google公司開發(fā)的JavaScript語言的替代品Dart語言,就明確規(guī)定只有null,沒有undefined!
既然含義與用法都差不多,為什么要同時設(shè)置兩個這樣的值,這不是無端增加復(fù)雜度,令初學(xué)者困擾嗎?這與歷史原因有關(guān)。
(2)歷史原因
1995年JavaScript誕生時,最初像Java一樣,只設(shè)置了null作為表示"無"的值。根據(jù)C語言的傳統(tǒng),null被設(shè)計成可以自動轉(zhuǎn)為0。
Number(null) // 0 5 + null // 5
但是,JavaScript的設(shè)計者Brendan Eich,覺得這樣做還不夠,有兩個原因。
首先,null像在Java里一樣,被當(dāng)成一個對象。但是,JavaScript的數(shù)據(jù)類型分成原始類型和合成類型兩大類,Brendan Eich覺得表示"無"的值最好不是對象。
其次,JavaScript的最初版本沒有包括錯誤處理機(jī)制,發(fā)生數(shù)據(jù)類型不匹配時,往往是自動轉(zhuǎn)換類型或者默默地失敗。Brendan Eich覺得,如果null自動轉(zhuǎn)為0,很不容易發(fā)現(xiàn)錯誤。
因此,Brendan Eich又設(shè)計了一個undefined。他是這樣區(qū)分的:null是一個表示"無"的對象,轉(zhuǎn)為數(shù)值時為0;undefined是一個表示"無"的原始值,轉(zhuǎn)為數(shù)值時為NaN。
Number(undefined) // NaN 5 + undefined // NaN
但是,這樣的區(qū)分在實(shí)踐中很快就被證明不可行。目前,null和undefined基本是同義的,只有一些細(xì)微的差別。
(3)用法和含義
對于null和undefined,可以大致上像下面這樣理解。
null表示"沒有對象",即該處不應(yīng)該有值。典型用法是:
作為函數(shù)的參數(shù),表示該函數(shù)的參數(shù)不是對象。
作為對象原型鏈的終點(diǎn)。
undefined表示"缺少值",就是此處應(yīng)該有一個值,但是還未定義。典型用法是:
變量被聲明了,但沒有賦值時,就等于undefined。
調(diào)用函數(shù)時,應(yīng)該提供的參數(shù)沒有提供,該參數(shù)等于undefined。
對象沒有賦值的屬性,該屬性的值為undefined。
函數(shù)沒有返回值時,默認(rèn)返回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的數(shù)據(jù)類型就是對象,而是JavaScript早期部署中的一個約定俗成,其實(shí)不完全正確,后來再想改已經(jīng)太晚了,會破壞現(xiàn)存代碼,所以一直保留至今。
(5)注意點(diǎn)
JavaScript的標(biāo)識名區(qū)分大小寫,所以undefined和null不同于Undefined和Null(或者其他僅僅大小寫不同的詞形),后者只是普通的變量名。
布爾值代表“真”和“假”兩個狀態(tài)?!罢妗庇藐P(guān)鍵字true表示,“假”用關(guān)鍵字false表示。布爾值只有這兩個值。
下列運(yùn)算符會返回布爾值:
兩元邏輯運(yùn)算符: && (And),|| (Or)
前置邏輯運(yùn)算符: ! (Not)
相等運(yùn)算符:===,!==,==,!=
比較運(yùn)算符:>,>=,<,<=
如果JavaScript預(yù)期某個位置應(yīng)該是布爾值,會將該位置上現(xiàn)有的值自動轉(zhuǎn)為布爾值。轉(zhuǎn)換規(guī)則是除了下面六個值被轉(zhuǎn)為false,其他值都視為true。
undefined
null
false
0
NaN
""(空字符串)
布爾值往往用于程序流程的控制,請看一個例子。
if (""){ console.log(true);} // 沒有任何輸出
上面代碼的if命令后面的判斷條件,預(yù)期應(yīng)該是一個布爾值,所以JavaScript自動將空字符串,轉(zhuǎn)為布爾值false,導(dǎo)致程序不會進(jìn)入代碼塊,所以沒有任何輸出。
需要特別注意的是,空數(shù)組([])和空對象({})對應(yīng)的布爾值,都是true。
if ([]){ console.log(true);} // true if ({}){ console.log(true);} // true
更多關(guān)于數(shù)據(jù)類型轉(zhuǎn)換的介紹,參見《數(shù)據(jù)類型轉(zhuǎn)換》一節(jié)。
分號表示一條語句的結(jié)尾。但是,有一些語法結(jié)構(gòu)不需要在語句的結(jié)尾添加分號,主要是以下三種情況。
(1)for和while循環(huán)
for(;;){} // 沒有分號 while(true){} // 沒有分號
需要注意的是do...while循環(huán)是有分號的。
do { a--; } while(a > 0); // 分號不能省略
(2)分支語句:if, switch, try
if (true) {} // 沒有分號 switch () {} // 沒有分號 try {} catch {} // 沒有分號
(3)函數(shù)的聲明語句
function f() {} // 沒有分號
但是函數(shù)表達(dá)式仍然要使用分號。
var f = function f() {};
以上三種情況,如果使用了分號,并不會出錯。因?yàn)?,解釋引擎會把這個分號解釋為空語句。
除了本來就不寫分號的情況,JavaScript引擎還有一個特點(diǎn),就是在應(yīng)該寫分號卻沒寫的情況下,它會自動添加(Automatic Semicolon Insertion,簡稱ASI)。
var a = b + c // 等同于 var a = b + c;
但是,這種自動添加不是絕對的。如果下一行的開始可以與本行的結(jié)尾連在一起解釋,就不會自動添加分號。
var a = 3 // 等同于 var a = 3; "abc" .length // 等同于 "abc".length
上面代碼舉了兩個例子,每行的尾部都沒有分號,JavaScript并不會自動添加分號,因?yàn)槊啃械慕Y(jié)尾與下一行的開頭可以放在一起解釋。下面這個例子也不會自動添加分號。
3 * (2 * (4 + (3 - 5))) + (10 * (27 / 6)) // 等同于 3 * (2 * (4 + (3 - 5))) + (10 * (27 / 6))
這些例子還是比較容易看出來的,但是下面的例子就不那么容易發(fā)現(xiàn)了。它們都不會自動添加分號。
var a = b + c (d+e).toString(); /* 結(jié)果報錯,因?yàn)閮尚羞B在一起, 解釋為c(d+e), 即對函數(shù) c 的調(diào)用 */ a = b /hi/g.exec(c).map(d); /* 解釋為 a = b / hi / g.exec(c).map(d), 即把正則表達(dá)式的斜杠當(dāng)作除法運(yùn)算符 */ var a = "b" [ "red", "green" ].forEach(function(c) { console.log(c) }) /* 結(jié)果報錯,因?yàn)閮尚羞B在一起, 解釋為"b"["red", "green"], 即把字符串當(dāng)作一個數(shù)組,按索引取值 */ var a = 0; var f = function(x) { return x } (a++) /* f等于0,因?yàn)?a++)被 * 視為匿名函數(shù)的調(diào)用 */ return a + b; return (a + b) obj.foo(arg1, arg2)
一般來說,在沒有分號結(jié)尾的情況下,如果下一行起首的是(、 [ 、+、-、/這五個字符中的一個,分號不會被自動添加。只有下一行的開始與本行的結(jié)尾,無法放在一起解釋,JavaScript引擎才會自動添加分號。
if (a < 0) a = 0 console.log(a) // 等同于下面的代碼, // 因?yàn)?console沒有意義 if (a < 0) a = 0; console.log(a)
另外,如果一行的起首是“自增”(++)或“自減”(--)運(yùn)算符,則它們的前面會自動添加分號。
a = b = c = 1 a ++ b -- c console.log(a, b, c) // 1 2 0
之所以會得到“1 2 0”的結(jié)果,原因是自增和自減運(yùn)算符前,自動被加上了分號。上面的代碼實(shí)際上等同于下面的形式:
a = b = c = 1; a; ++b; --c;
如果continue、break、return和throw這四個語句后面,直接跟換行符,則會自動添加分號。這意味著,如果return語句返回的是一個對象的字面量,起首的大括號一定要寫在同一行,否則得不到預(yù)期結(jié)果。
return { first: "Jane" }; // 解釋成 return; { first: "Jane" };
由于解釋引擎自動添加分號的行為難以預(yù)測,因此編寫代碼的時候不應(yīng)該省略行尾的分號。
省略結(jié)尾的分號,還有一個問題。有些JavaScript代碼壓縮器不會自動添加分號,因此遇到?jīng)]有分號的結(jié)尾,就會讓代碼保持原狀,而不是壓縮成一行。
Axel Rauschmayer,
Axel Rauschmayer,
Axel Rauschmayer,
Axel Rauschmayer,
Rod Vagg,