JavaScript 語法概述

2021-09-15 15:11 更新

基本句法和變量

語句

JavaScript程序的執(zhí)行單位為行(line),也就是一行一行地執(zhí)行。一般情況下,每一行就是一個語句。

語句(statement)是為了完成某種任務(wù)而進行的操作,比如下面就是一行賦值語句:

var a = 1 + 3;

這條語句先用var命令,聲明了變量a,然后將1 + 3的運算結(jié)果賦值給變量a。

1 + 3叫做表達式(expression),指一個為了得到返回值的計算式。語句和表達式的區(qū)別在于,前者主要為了進行某種操作,一般情況下不需要返回值;后者則是為了得到返回值,一定會返回一個值。

凡是JavaScript語言中預(yù)期為值的地方,都可以使用表達式。比如,賦值語句的等號右邊,預(yù)期是一個值,因此可以放置各種表達式。一條語句可以包含多個表達式。

語句以分號結(jié)尾,一個分號就表示一個語句結(jié)束。多個語句可以寫在一行內(nèi)。

var a = 1 + 3 ; var b = "abc";

分號前面可以沒有任何內(nèi)容,JavaScript引擎將其視為空語句。

;;;

上面的代碼就表示3個空語句。(關(guān)于分號的更多介紹,請看后文《結(jié)尾的分號》一節(jié)。)

表達式不需要分號結(jié)尾。一旦在表達式后面添加分號,則JavaScript引擎就將表達式視為語句,這樣會產(chǎn)生一些沒有任何意義的語句。

1 + 3;

"abc";

上面兩行語句有返回值,但是沒有任何意義,因為只是返回一個單純的值,沒有任何其他操作。

變量

變量是對“值”的引用,使用變量等同于引用一個值。每一個變量都有一個變量名。

var a = 1;

上面的代碼先聲明變量a,然后在變量a與數(shù)值1之間建立引用關(guān)系,也稱將數(shù)值1“賦值”給變量a。以后,引用變量a就會得到數(shù)值1。最前面的var,是變量聲明命令。它表示通知解釋引擎,要創(chuàng)建一個變量a。

變量的聲明和賦值,是分開的兩個步驟,上面的代碼將它們合在了一起,實際的步驟是下面這樣。

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ù)值,后來又被重新賦值為一個字符串。第二次賦值的時候,因為變量a已經(jīng)存在,所以不需要使用var命令。如果用了,就等于重新聲明一個變量a,會覆蓋掉前面的同名變量。

變量提升

JavaScript引擎的工作方式是,先解析代碼,獲取所有被聲明的變量,然后再一行一行地運行。這造成的結(jié)果,就是所有的變量的聲明語句,都會被提升到代碼的頭部,這就叫做變量提升(hoisting)。

console.log(a);
var a = 1;

上面代碼首先使用console.log方法,在控制臺(console)顯示變量a的值。這時變量a還沒有聲明和賦值,所以這是一種錯誤的做法,但是實際上不會報錯。因為存在變量提升,真正運行的是下面的代碼:

var a;
console.log(a);
a = 1;

最后的結(jié)果是顯示undefined,表示變量a已聲明,但還未賦值。

請注意,變量提升只對var命令聲明的變量有效,如果一個變量不是用var命令聲明的,就不會發(fā)生變量提升。

console.log(b);
b = 1;

上面的語句將會報錯,提示“ReferenceError: b is not defined”,即變量b未聲明,這是因為b不是用var命令聲明的,JavaScript引擎不會將其提升,而只是視為對頂層對象的b屬性的賦值。

標(biāo)識符

標(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īng)該用作標(biāo)識符:Infinity、NaN、undefined。

注釋

源碼中被JavaScript引擎忽略的部分就叫做注釋,它的作用是對代碼進行解釋。Javascript提供兩種注釋:一種是單行注釋,用//起頭;另一種是多行注釋,放在//之間。

// 這是單行注釋

/*
 這是
 多行
 注釋
*/

本教程后面的代碼部分,會采用這兩種形式說明代碼的運行結(jié)果,以及需要注意的地方。

此外,由于歷史上JavaScript兼容HTML代碼的注釋,所以也被視為單行注釋。

x = 1; <!-- x = 2;
--> x = 3;

上面代碼中,只有x = 1會執(zhí)行,其他的部分都被注釋掉了。

需要注意的是,-->只有在行首,才會被當(dāng)成單行注釋,否則就是一個運算符。

function countdown(n) {
  while (n --> 0) console.log(n);
}
countdown(3)
// 2
// 1
// 0

上面代碼中,n --> 0實際上會當(dāng)作n-- > 0,因為輸出2、1、0。

區(qū)塊

JavaScript使用大括號,將多個相關(guān)的語句組合在一起,稱為“區(qū)塊”(block)。

與大多數(shù)編程語言不一樣,JavaScript的區(qū)塊不構(gòu)成單獨的作用域(scope)。也就是說,區(qū)塊中的變量與區(qū)塊外的變量,屬于同一個作用域。

{ 
    var a = 1;
}

a // 1

上面代碼在區(qū)塊內(nèi)部,聲明并賦值了變量a,然后在區(qū)塊外部,變量a依然有效,這說明區(qū)塊不構(gòu)成單獨的作用域,與不使用區(qū)塊的情況沒有任何區(qū)別。所以,單獨使用的區(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)先判斷一個表達式的布爾值,然后根據(jù)布爾值的真?zhèn)危瑘?zhí)行不同的語句。

if (expression) 
  statement

上面是if結(jié)構(gòu)的基本形式。需要注意的是,expression(表達式)必須放在圓括號中,表示對表達式求值。如果結(jié)果為true,就執(zhí)行緊跟在后面的statement(語句);如果結(jié)果為false,則跳過statement。

if (m === 3) 
  m += 1;

上面代碼表示,只有在m等于3時,才會將其值加上1。

這種寫法要求statement只能有一個語句。如果想將多個語句放在statement之中,必須在if的條件判斷之后,加上大括號。

if (m === 3) {
  m += 1; 
}

建議總是在if語句中使用大括號,因為這樣方便插入語句。

(2)if...else結(jié)構(gòu)

if代碼塊后面,還可以跟一個else代碼塊,表示括號中的表示式為false時,所要執(zhí)行的代碼。

if (m === 3) {
    // then
 } else {
   // else
 }

上面代碼判斷變量m是否等于3,如果等于就執(zhí)行if代碼塊,否則執(zhí)行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代碼塊也不會得到執(zhí)行,因為它跟著的是最近的那個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語句部分,都可以使用表達式。

switch(1 + 3) {
    case 2 + 2:
        f();
        break;
    default:
        neverhappens();
}

上面代碼的default部分,是永遠不會執(zhí)行到的。

需要注意的是,switch語句后面的表達式與case語句后面的表示式,在比較運行結(jié)果時,采用的是嚴(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)語句

循環(huán)語句用于重復(fù)執(zhí)行某個操作,它有多種形式。

(1)while循環(huán)

While語句包括一個循環(huán)條件,只要該條件為真,就不斷循環(huán)。

while (expression)    
statement

while語句的循環(huán)條件是一個表達式(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)條件,只要為真就進行后續(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語句表達式的三個部分(initialize,test,increment),可以省略任何一個,也可以全部省略。

for (;;){
    console.log('Hello World');
}

上面代碼省略了for語句表達式的三個部分,結(jié)果就導(dǎo)致了一個無限循環(huán)。

(3)do...while循環(huán)

do...while循環(huán)與while循環(huán)類似,唯一的區(qū)別就是先運行一次循環(huán)體,然后判斷循環(huán)條件。

do 
statement
while(expression);

// 或者

do { 
    statement
} while(expression);

不管條件是否為真,do..while循環(huán)至少運行一次,這是這種結(jié)構(gòu)最大的特點。另外,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ù),則直接進入下一輪循環(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),進入下一次的外層循環(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)簽名,直接進入下一輪外層循環(huán)。如果continue語句后面不使用標(biāo)簽,則只能進入下一輪的內(nèi)層循環(huán)。

數(shù)據(jù)類型

概述

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ù)據(jù)的方法。JavaScript把函數(shù)當(dāng)成一種數(shù)據(jù)類型,可以像其他類型的數(shù)據(jù)一樣,進行賦值和傳遞,這為編程帶來了很大的靈活性,體現(xiàn)了JavaScript作為“函數(shù)式語言”的本質(zhì)。

這里需要明確的是,JavaScript的所有數(shù)據(jù),都可以視為對象。不僅合成類型的數(shù)組和函數(shù)屬于對象的特例,就連原始類型的數(shù)據(jù)(數(shù)值、字符串、布爾值)也可以用對象方式調(diào)用。

除了上面這六個類別,JavaScript還定義了兩個特殊值null和undefined。

本書將分別詳細介紹這六個類別和兩個特殊值。其中,兩個特殊值和布爾類型比較簡單,將在本節(jié)介紹,其他類型將各自有單獨的一節(jié)。

typeof運算符

JavaScript有三種方法,可以確定一個值到底是什么類型。

  • typeof運算符
  • instanceof運算符
  • Object.prototype.toString方法

instanceof運算符和Object.prototype.toString方法,將在后文相關(guān)章節(jié)介紹。這里著重介紹typeof 運算符。

typeof運算符可以返回一個值的數(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"

利用這一點,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"

從上面代碼可以看到,空數(shù)組([])的類型也是object,這表示在JavaScript內(nèi)部,數(shù)組本質(zhì)上只是一種特殊的對象。另外,null的類型也是object,這是由于歷史原因造成的,為了兼容以前的代碼,后來就沒法修改了,并不是說null就屬于對象,本質(zhì)上null是一個類似于undefined的特殊值。

既然typeof對數(shù)組(array)和對象(object)的顯示結(jié)果都是object,那么怎么區(qū)分它們呢?instanceof運算符可以做到。

var o = {};
var a = [];

o instanceof Array // false
a instanceof Array // true

instanceof運算符的詳細解釋,請見《面向?qū)ο缶幊獭芬徽隆?/p>

null和undefined

(1)相似性

首先,null與undefined都可以表示“無”,含義非常相似。將一個變量賦值為undefined或null,老實說,幾乎沒區(qū)別。

var a = undefined;

// 或者

var a = null;

上面代碼中,a變量分別被賦值為undefined和null,這兩種寫法幾乎等價。

在if語句中,都會被自動轉(zhuǎn)為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公司開發(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的最初版本沒有包括錯誤處理機制,發(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ū)分在實踐中很快就被證明不可行。目前,null和undefined基本是同義的,只有一些細微的差別。

(3)用法和含義

對于null和undefined,可以大致上像下面這樣理解。

null表示"沒有對象",即該處不應(yīng)該有值。典型用法是:

  • 作為函數(shù)的參數(shù),表示該函數(shù)的參數(shù)不是對象。

  • 作為對象原型鏈的終點。

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早期部署中的一個約定俗成,其實不完全正確,后來再想改已經(jīng)太晚了,會破壞現(xiàn)存代碼,所以一直保留至今。

(5)注意點

JavaScript的標(biāo)識名區(qū)分大小寫,所以undefined和null不同于Undefined和Null(或者其他僅僅大小寫不同的詞形),后者只是普通的變量名。

布爾值

布爾值代表“真”和“假”兩個狀態(tài)。“真”用關(guān)鍵字true表示,“假”用關(guān)鍵字false表示。布爾值只有這兩個值。

下列運算符會返回布爾值:

  • 兩元邏輯運算符: && (And),|| (Or)
  • 前置邏輯運算符: ! (Not)
  • 相等運算符:===,!==,==,!=
  • 比較運算符:>,>=,<,<=

如果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)致程序不會進入代碼塊,所以沒有任何輸出。

需要特別注意的是,空數(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é)尾的語句

分號表示一條語句的結(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ù)表達式仍然要使用分號。

var f = function f() {};

以上三種情況,如果使用了分號,并不會出錯。因為,解釋引擎會把這個分號解釋為空語句。

分號的自動添加

除了本來就不寫分號的情況,JavaScript引擎還有一個特點,就是在應(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并不會自動添加分號,因為每行的結(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é)果報錯,因為兩行連在一起,
   解釋為c(d+e),
   即對函數(shù) c 的調(diào)用 */

a = b
/hi/g.exec(c).map(d);
/* 解釋為 a = b / hi / g.exec(c).map(d),
   即把正則表達式的斜杠當(dāng)作除法運算符 */ 

var a = "b"
[ "red", "green" ].forEach(function(c) { console.log(c) })
/* 結(jié)果報錯,因為兩行連在一起,
 解釋為"b"["red", "green"],
 即把字符串當(dāng)作一個數(shù)組,按索引取值 */ 

var a = 0;
var f = function(x) { return x }
(a++)
/* f等于0,因為(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)

// 等同于下面的代碼,
// 因為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”的結(jié)果,原因是自增和自減運算符前,自動被加上了分號。上面的代碼實際上等同于下面的形式:

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é)尾,就會讓代碼保持原狀,而不是壓縮成一行。

參考鏈接

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號