第 7 章 JavaScript 語法
目錄
購買書籍
(廣告,請勿封鎖。)

第 7 章 JavaScript 語法

JavaScript 的語法相當簡單。本章將說明需要注意的事項。

語法概觀

本節將快速說明 JavaScript 語法的樣貌。

以下是五種基本值類型:

  • 布林值

    true
    false
  • 數字:

    1023
    7.851
  • 字串:

    'hello'
    "hello"
  • 純粹物件:

    {
        firstName: 'Jane',
        lastName: 'Doe'
    }
  • 陣列:

    [ 'apple', 'banana', 'cherry' ]

以下是幾個基本語法的範例:

// 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;
}

請注意等號 (=) 的兩種不同用法:

  • 單等號 (=) 用於將值指定給變數。
  • 三等號 (===) 用於比較兩個值(請參閱等值運算子)。

註解

有兩種註解

表達式與陳述式

本節將探討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 傳回物件,您必須在物件文字周圍加上括號:

> 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) 加上(請參閱 自動分號插入)。然而,該功能並非總是如預期般運作,這就是為什麼您應該始終包含分號的原因。

結尾為區塊的陳述式後不加分號

以下陳述式如果結尾為區塊,則不會以分號結尾:

  • 迴圈:forwhile(但不是 do-while
  • 分支:ifswitchtry
  • 函式宣告(但不是函式表達式)

以下是 whiledo-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 規定,如果符合下列情況,陳述式也會結束

  • 行終止符(例如換行)後接非法令牌。
  • 遇到閉合大括號。
  • 已到達檔案結尾。

範例:透過非法令牌進行 ASI

下列程式碼包含行終止符後接非法令牌:

if (a < 0) a = 0
console.log(a)

令牌 console0 之後是非法的,會觸發 ASI

if (a < 0) a = 0;
console.log(a);

範例:透過閉合大括號進行 ASI

下列程式碼中,大括號內的陳述式並未以分號結尾:

function add(a,b) { return a+b }

ASI 會建立上述程式碼的語法正確版本

function add(a,b) { return a+b; }

陷阱:ASI 可能會意外中斷陳述式

如果關鍵字 return 之後有行終止符,也會觸發 ASI。例如:

// Don't do this
return
{
    name: 'John'
};

ASI 會將上述程式碼轉換成

return;
{
    name: 'John'
};

這是一個空回傳值,後接一個區塊,其中標籤 name 出現在表達式陳述式 'John' 之前。區塊之後是一個空陳述式。

陷阱:ASI 可能意外未觸發

有時,新行中的陳述式會以令牌開頭,而該令牌允許作為前一個陳述式的延續。此時,即使看起來應該觸發 ASI,但 ASI 也不會觸發。例如:

func()
[ 'ul', 'ol' ].forEach(function (t) { handleTag(t) })

第二行中的方括號會被解譯為 func() 回傳結果的索引。括號內的逗號會被解譯為逗號運算子(在此情況下會回傳 'ol';請參閱 逗號運算子)。因此,JavaScript 會將 前一個程式碼視為:

func()['ol'].forEach(function (t) { handleTag(t) });

法律識別碼

識別碼用於命名事物,並出現在 JavaScript 中的各種語法角色中。例如,變數名稱和未加引號的屬性鍵必須是有效的識別碼。識別碼區分大小寫。

識別碼的第一個字元為下列之一

  • 任何 Unicode 字母,包括拉丁字母(例如 D)、希臘字母(例如 λ)和西里爾字母(例如 Д)
  • 美元符號 ($)
  • 底線 (_)

後續字元為

  • 任何合法的第一個字元
  • Unicode 類別為「十進位數字 (Nd)」的任何 Unicode 數字;這包括歐洲數字(例如 7)和印度數字(例如 ٣)
  • 各種其他 Unicode 標記和標點符號

合法識別碼範例

var ε = 0.0001;
var строка = '';
var _tmp;
var $foo2;

儘管這允許您在 JavaScript 程式碼中使用各種人類語言,但我建議您堅持使用英語,包括識別碼和註解。這可確保您的程式碼能被最多的人理解,這很重要,因為現今有大量的程式碼會在國際間傳播。

下列識別碼保留字,它們是語法的一部分,不能用作變數名稱(包括函式名稱和參數名稱):

arguments

break

case

catch

class

const

continue

debugger

default

delete

do

else

enum

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

下列三個識別碼不是保留字,但您應該將它們視為保留字

Infinity

NaN

undefined

最後,您也應該避開標準全域變數的名稱(請參閱 第 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 () {
        };
    }
}

this 在非方法函式中是未定義的

在寬鬆模式中,非方法函式中 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

在寬鬆模式中,你不會收到警告,而且會建立全域變數 xy。有關詳細資訊,請參閱 實作建構函式的秘訣

在嚴格模式中,設定和刪除不可變屬性會失敗並產生例外

屬性的非法操作會在嚴格模式中擲回例外。例如,嘗試設定唯讀屬性的值會擲回例外,嘗試刪除不可設定屬性也會如此。以下是一個前者的範例:

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)

嚴格模式中禁止的功能

在嚴格模式中禁止另外兩個 JavaScript 功能

  • with 陳述式不再被允許(請參閱with 陳述式)。您會在編譯時(載入程式碼時)收到語法錯誤。
  • 不再有八進制數字:在隨意模式中,開頭為零的整數會被解釋為八進制(8 進位)。例如

    > 010 === 8
    true

    在嚴格模式中,如果您使用此類型的字面值,您會收到語法錯誤:

    > function f() { 'use strict'; return 010 }
    SyntaxError: Octal literals are not allowed in strict mode.



[8] 為了簡化,我假裝宣告是陳述式。

下一頁:8. 值