JavaScript 的語法相當簡單。本章將說明需要注意的事項。
本節將快速說明 JavaScript 語法的樣貌。
以下是五種基本值類型:
以下是幾個基本語法的範例:
// 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`
// A conditional statement
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
多行註解透過 /* */
可以延伸至任意範圍的文字。無法巢狀使用。以下有兩個範例:
/* temporarily disabled
processNext(queue);
*/
function
(
a
/* int */
,
b
/* str */
)
{
}
本節將探討JavaScript 中一個重要的語法區別:表達式與陳述式的差異。
大致來說,陳述式 會執行一個動作。迴圈和 if
陳述式是陳述式的範例。一個程式基本上就是一系列的陳述式。[8]
不論 JavaScript 預期何種陳述,您也可以撰寫表達式。此類陳述稱為表達式陳述。反之則不然:您無法在 JavaScript 預期表達式的位置撰寫陳述。例如,if
陳述無法成為函式的引數。
如果我們檢視兩個語法類別中相似的成員,if
陳述和條件式運算子(一種表達式),便會更清楚陳述和表達式之間的差異。
以下是一個if
陳述的範例:
var
salutation
;
if
(
male
)
{
salutation
=
'Mr.'
;
}
else
{
salutation
=
'Mrs.'
;
}
有一種類似的表達式,稱為條件式運算子。下列陳述等同於下列程式碼:
var
salutation
=
(
male
?
'Mr.'
:
'Mrs.'
);
等號與分號之間的程式碼是一個表達式。括號不是必要的,但我發現如果將條件式運算子置於括號中,會更容易閱讀。
物件文字(表達式)看起來像區塊(陳述):
{
foo
:
bar
(
3
,
5
)
}
前述建構可能是物件文字(詳細資訊:物件文字)或區塊,後接標籤foo:
,再後接函式呼叫bar(3, 5)
。
命名函式表達式看起來像函式宣告(陳述):
function
foo
()
{
}
前述建構可能是命名函式表達式或函式宣告。前者會產生函式,後者會建立變數並將函式指定給它(兩種函式定義的詳細資訊:定義函式)。
為了避免剖析期間的不明確性,JavaScript 不允許您將物件文字和函式表達式用作陳述。也就是說,表達式陳述不得以下列項目開頭
function
如果表達式以任一記號開頭,它只能出現在表達式內容中。您可以透過執行某些動作來符合該需求,例如在表達式周圍加上括號。接下來,我們將檢視兩個必要的範例。
eval
在陳述內容中剖析其引數。如果您要讓eval
傳回物件,您必須在物件文字周圍加上括號:
> eval('{ foo: 123 }') 123 > eval('({ foo: 123 })') { foo: 123 }
以下程式碼是一個立即呼叫函式表達式 (IIFE),一個主體會立即執行的函式(您將在 透過 IIFE 介紹新的範圍 中瞭解 IIFE 的用途)
> (function () { return 'abc' }()) 'abc'
如果您省略括號,您會得到一個語法錯誤,因為 JavaScript 視為函式宣告,而函式宣告不能是匿名的
> function () { return 'abc' }() SyntaxError: function statement requires a name
如果您加上名稱,您也會得到一個語法錯誤,因為函式宣告不能立即呼叫
> function foo() { return 'abc' }() SyntaxError: Unexpected token )
函式宣告後面的任何內容都必須是一個合法的陳述式,而 ()
不是。
對於 控制流程陳述式,主體是一個單一陳述式。以下是兩個範例:
if
(
obj
!==
null
)
obj
.
foo
();
while
(
x
>
0
)
x
--
;
然而,任何陳述式都可以用一個區塊取代,即包含零個或多個陳述式的花括號。因此,您也可以這樣寫:
if
(
obj
!==
null
)
{
obj
.
foo
();
}
while
(
x
>
0
)
{
x
--
;
}
我比較喜歡後者的控制流程陳述式形式。標準化它表示單一陳述式主體和多重陳述式主體之間沒有差異。因此,您的程式碼看起來更一致,而且在一個陳述式和多個陳述式之間交替會更容易。
在本節中,我們探討分號在 JavaScript 中的用法。基本規則是:
分號在 JavaScript 中是可選的。遺失的分號會透過所謂的自動分號插入 (ASI) 加上(請參閱 自動分號插入)。然而,該功能並非總是如預期般運作,這就是為什麼您應該始終包含分號的原因。
以下陳述式如果結尾為區塊,則不會以分號結尾:
for
、while
(但不是 do-while
)
if
、switch
、try
以下是 while
與 do-while
的範例
while
(
a
>
0
)
{
a
--
;
}
// no semicolon
do
{
a
--
;
}
while
(
a
>
0
);
以下是函式宣告與函式表達式的範例。後者後面加上分號,因為它出現在 var
宣告中(是以分號結尾)
function
foo
()
{
// ...
}
// no semicolon
var
foo
=
function
()
{
// ...
};
如果您在區塊後加上分號,您不會得到語法錯誤,因為它被視為空陳述式(請參閱下一節)。
這就是您需要知道的關於分號的大部分內容。如果您始終加上分號,您可能可以在不閱讀本節剩餘部分的情況下繼續前進。
一個獨立的分號是一個空陳述式,什麼都不做。空陳述式可以在預期陳述式的任何地方出現。它們在需要陳述式但不需要陳述式的狀況下很有用。在這種情況下,通常也允許使用區塊。例如,以下兩個陳述式是等效的:
while
(
processNextItem
()
>
0
);
while
(
processNextItem
()
>
0
)
{}
假設函式 processNextItem
回傳剩餘項目的數量。以下程式由三個空陳述式組成,語法上也是正確的
;;;
自動分號插入 (ASI) 的目標 是讓分號在行尾變成可選的。術語 自動分號插入 讓人聯想到 JavaScript 解析器會自動幫你插入分號(實際上,內部通常會用不同的方式處理)。
換句話說,ASI 可協助解析器判斷陳述式何時結束。通常,陳述式會以分號結尾。ASI 規定,如果符合下列情況,陳述式也會結束
if
(
a
<
0
)
a
=
0
console
.
log
(
a
)
令牌 console
在 0
之後是非法的,會觸發 ASI
if
(
a
<
0
)
a
=
0
;
console
.
log
(
a
);
function
add
(
a
,
b
)
{
return
a
+
b
}
ASI 會建立上述程式碼的語法正確版本
function
add
(
a
,
b
)
{
return
a
+
b
;
}
如果關鍵字 return
之後有行終止符,也會觸發 ASI。例如:
// Don't do this
return
{
name
:
'John'
};
ASI 會將上述程式碼轉換成
return
;
{
name
:
'John'
};
這是一個空回傳值,後接一個區塊,其中標籤 name
出現在表達式陳述式 'John'
之前。區塊之後是一個空陳述式。
有時,新行中的陳述式會以令牌開頭,而該令牌允許作為前一個陳述式的延續。此時,即使看起來應該觸發 ASI,但 ASI 也不會觸發。例如:
func
()
[
'ul'
,
'ol'
].
forEach
(
function
(
t
)
{
handleTag
(
t
)
})
第二行中的方括號會被解譯為 func()
回傳結果的索引。括號內的逗號會被解譯為逗號運算子(在此情況下會回傳 'ol'
;請參閱 逗號運算子)。因此,JavaScript 會將 前一個程式碼視為:
func
()[
'ol'
].
forEach
(
function
(
t
)
{
handleTag
(
t
)
});
識別碼用於命名事物,並出現在 JavaScript 中的各種語法角色中。例如,變數名稱和未加引號的屬性鍵必須是有效的識別碼。識別碼區分大小寫。
識別碼的第一個字元為下列之一
$
)
_
)
後續字元為
合法識別碼範例
var
ε
=
0.0001
;
var
строка
=
''
;
var
_tmp
;
var
$foo2
;
儘管這允許您在 JavaScript 程式碼中使用各種人類語言,但我建議您堅持使用英語,包括識別碼和註解。這可確保您的程式碼能被最多的人理解,這很重要,因為現今有大量的程式碼會在國際間傳播。
下列識別碼是 保留字,它們是語法的一部分,不能用作變數名稱(包括函式名稱和參數名稱):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
下列三個識別碼不是保留字,但您應該將它們視為保留字
|
|
|
最後,您也應該避開標準全域變數的名稱(請參閱 第 23 章)。您可以在不中斷任何情況下將它們用於局部變數,但您的程式碼仍然會令人困惑。
請注意,您 可以 將保留字用作未加引號的屬性鍵(ECMAScript 5 起)
> var obj = { function: 'abc' }; > obj.function 'abc'
您可以在 Mathias Bynens 的部落格文章 “有效的 JavaScript 變數名稱” 中查詢識別碼的精確規則。
對於方法 呼叫,區分浮點小數點和方法呼叫點非常重要。因此,您不能撰寫 1.toString()
;您必須使用下列其中一個替代方案:
1
..
toString
()
1
.
toString
()
// space before dot
(
1
).
toString
()
1.0
.
toString
()
ECMAScript 5 有一個嚴格模式,可以產生更乾淨的 JavaScript,具有較少不安全的特性、更多警告和更合乎邏輯的行為。一般(非嚴格)模式有時稱為「隨意模式」。
您可以開啟嚴格模式,方法是在 JavaScript 檔案或 <script>
元素內最前面輸入下列程式行:
'use strict'
;
請注意,不支援 ECMAScript 5 的 JavaScript 引擎會忽略前述陳述,因為以這種方式撰寫字串(作為表達式陳述;請參閱 陳述)通常不會執行任何動作。
您也可以針對每個函式開啟嚴格模式。為此,請以這種方式撰寫函式
function
foo
()
{
'use strict'
;
...
}
當您使用舊有程式碼庫時,這會很方便,因為在所有地方開啟嚴格模式可能會導致問題。
一般來說,嚴格模式啟用的變更都是為了讓程式更好。因此,強烈建議您將其用於撰寫的新程式碼中,只要在檔案開頭開啟它即可。不過,有兩個注意事項:
下列各節會更詳細地說明嚴格模式特性。您通常不需要知道這些特性,因為您大多會針對不應該執行的動作收到更多警告。
在嚴格模式中,所有變數都必須明確宣告。這有助於防止錯字。在隨意模式中,將值指定給未宣告的變數會建立一個全域變數:
function
sloppyFunc
()
{
sloppyVar
=
123
;
}
sloppyFunc
();
// creates global variable `sloppyVar`
console
.
log
(
sloppyVar
);
// 123
在嚴格模式中,將值指定給未宣告的變數會擲回例外狀況
function
strictFunc
()
{
'use strict'
;
strictVar
=
123
;
}
strictFunc
();
// ReferenceError: strictVar is not defined
嚴格模式限制與函式相關的功能。
在嚴格模式中,所有函式都必須宣告在範圍的最上層(全域範圍或直接在函式內)。這表示你不能將函式宣告置於區塊內。如果你這樣做,你會得到一個描述性的SyntaxError
。例如,V8 會告訴你:「在嚴格模式程式碼中,函式只能宣告在最上層或直接在另一個函式內」:
function
strictFunc
()
{
'use strict'
;
if
(
true
)
{
// SyntaxError:
function
nested
()
{
}
}
}
這本來就沒有用,因為函式是在周圍函式的範圍內建立的,而不是「在」區塊內。
如果你想解決這個限制,你可以透過變數宣告和函式表示式在區塊內建立一個函式
function
strictFunc
()
{
'use strict'
;
if
(
true
)
{
// OK:
var
nested
=
function
()
{
};
}
}
arguments
物件在嚴格模式中較為簡單:arguments.callee
和 arguments.caller
屬性已被移除,你無法指定 arguments
變數,而且 arguments
不會追蹤參數的變更(如果參數變更,對應的陣列元素不會跟著變更)。 arguments 的已棄用功能 說明了詳細資訊。
在寬鬆模式中,非方法函式中 this
的值是全域物件(瀏覽器中的 window
;請參閱 全域物件):
function
sloppyFunc
()
{
console
.
log
(
this
===
window
);
// true
}
在嚴格模式中,它是 undefined
function
strictFunc
()
{
'use strict'
;
console
.
log
(
this
===
undefined
);
// true
}
這對建構函式很有用。例如,以下建構函式 Point
在嚴格模式中:
function
Point
(
x
,
y
)
{
'use strict'
;
this
.
x
=
x
;
this
.
y
=
y
;
}
由於嚴格模式,當你意外忘記 new
並將其呼叫為函式時,你會收到警告
> var pt = Point(3, 1); TypeError: Cannot set property 'x' of undefined
在寬鬆模式中,你不會收到警告,而且會建立全域變數 x
和 y
。有關詳細資訊,請參閱 實作建構函式的秘訣。
屬性的非法操作會在嚴格模式中擲回例外。例如,嘗試設定唯讀屬性的值會擲回例外,嘗試刪除不可設定屬性也會如此。以下是一個前者的範例:
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: Cannot assign to
// read-only property 'length'
}
在隨意模式中,您可以刪除全域變數 foo
,如下所示:
delete
foo
在嚴格模式中,每當您嘗試刪除未限定的識別字時,您會收到語法錯誤。您仍然可以刪除全域變數,如下所示
delete
window
.
foo
;
// browsers
delete
global
.
foo
;
// Node.js
delete
this
.
foo
;
// everywhere (in global scope)
在嚴格模式中,eval()
函數變得不那麼古怪:在已評估字串中宣告的變數不再新增到 eval()
周圍的範圍。有關詳細資訊,請參閱使用 eval() 評估程式碼。
在嚴格模式中禁止另外兩個 JavaScript 功能
with
陳述式不再被允許(請參閱with 陳述式)。您會在編譯時(載入程式碼時)收到語法錯誤。
不再有八進制數字:在隨意模式中,開頭為零的整數會被解釋為八進制(8 進位)。例如
> 010 === 8 true
在嚴格模式中,如果您使用此類型的字面值,您會收到語法錯誤:
> function f() { 'use strict'; return 010 } SyntaxError: Octal literals are not allowed in strict mode.