第一章 基本 JavaScript
目錄
購買書籍
(廣告,請勿封鎖。)

第一章 基本 JavaScript

本章節探討「基本 JavaScript」,這是 JavaScript 的子集,我選用此名稱是因為它盡可能簡潔,同時又能讓您發揮生產力。當您開始學習 JavaScript 時,我建議您先使用它編寫程式一段時間,再繼續學習這門語言的其他部分。如此一來,您不必一次學習所有內容,以免造成混淆。

背景

本節提供 JavaScript 的一些背景資訊,幫助您了解它為何會是現在的樣貌。

JavaScript 與 ECMAScript

ECMAScript 是 JavaScript 的正式名稱。之所以需要新的名稱,是因為 JavaScript 是一個商標(最初由 Sun 擁有,現在由 Oracle 擁有)。目前,Mozilla 是少數幾家獲准正式使用 JavaScript 名稱的公司之一,因為它很早之前就取得了授權。對於一般用途,這些規則適用:

  • JavaScript 指的是程式語言。
  • ECMAScript 是語言規範所使用的名稱。因此,每當提到語言版本時,人們會說 ECMAScript。目前 JavaScript 的版本是 ECMAScript 5;ECMAScript 6 目前正在開發中。

語言的影響和性質

JavaScript 的創造者布蘭登·艾奇別無選擇,只能非常快速地創造這門語言(否則網景通訊公司將採用其他更糟糕的技術)。他借用了多種程式語言:Java(語法、基本值與物件)、Scheme 和 AWK(一級函式)、Self(原型繼承),以及 Perl 和 Python(字串、陣列和正規表示式)。

JavaScript 直到 ECMAScript 3 才具備例外 處理功能,這說明了為何這門語言經常自動轉換值,而且經常在無聲中失敗:它最初無法擲回例外。

一方面,JavaScript 有些怪癖,而且缺少許多功能(區塊作用域變數、模組、支援子類別化等)。另一方面,它有幾個強大的功能,讓您可以解決這些問題。在其他語言中,您會學習語言功能。在 JavaScript 中,您通常會學習模式。

根據它的影響,JavaScript 能夠支援一種程式設計風格,這一點不足為奇,它結合了函式式程式設計(高階函式;內建 mapreduce 等)和物件導向程式設計(物件、繼承)。

語法

此節說明 JavaScript 的基本語法原則。

語法概觀

一些語法的範例:

// Two slashes start single-line comments

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 的語法,您應該知道它有兩個主要的語法類別:陳述式和表達式:

  • 陳述式「執行某些事情」。程式是陳述式序列。以下是宣告(建立)變數 foo 的陳述式範例

    var foo;
  • 表達式產生值。它們是函式引數、指定的一側等。以下是表達式的範例

    3 * 7

陳述式與表達式之間的差異最明顯地表現在 JavaScript 有兩種不同的方式執行 if-then-else—作為陳述式:

var x;
if (y >= 0) {
    x = y;
} else {
    x = -y;
}

或作為表達式

var x = y >= 0 ? y : -y;

您可以將後者作為函式引數(但不能將前者作為函式引數)

myFunction(y >= 0 ? y : -y)

最後,在 JavaScript 預期陳述式的地方,您也可以使用表達式;例如

foo(7, 1);

整行都是陳述式(所謂的表達式陳述式),但函式呼叫 foo(7, 1) 是表達式。

分號

分號在 JavaScript 中是選用的。不過,我建議始終包含分號,因為否則 JavaScript 可能錯誤猜測陳述式的結尾。詳細資訊說明於自動插入分號

分號終止陳述式,但不會終止區塊。您會在區塊後看到分號的情況只有一個:函式表達式是最後以區塊結尾的表達式。如果此類表達式在陳述式中最後出現,則其後會接一個分號:

// Pattern: var _ = ___;
var x = 3 * 7;
var f = function () { };  // function expr. inside var decl.

註解

JavaScript 有兩種註解:單行註解和多行註解。單行註解以 // 開頭,並以行尾終止:

x++; // single-line comment

多行註解/**/ 界定:

/* This is
   a multiline
   comment.
 */

變數和指定

JavaScript 中的變數在使用前宣告:

var foo;  // declare variable `foo`

指派

您可以宣告變數 並同時指派值:

var foo = 6;

您也可以指派值給現有的變數

foo = 4;  // change variable `foo`

複合指派運算子

有複合指派運算子,例如 +=下列兩個指派是等效的:

x += 1;
x = x + 1;

識別碼和變數名稱

識別碼 是在 JavaScript 中扮演各種語法角色的名稱。例如,變數的名稱是一個識別碼。識別碼會區分大小寫。

大致上,識別碼的第一個字元可以是任何 Unicode 字母、一個美元符號 ($) 或一個底線 (_)。後續的字元還可以是任何 Unicode 數字。因此,下列都是合法的識別碼

arg0
_tmp
$elem
π

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

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 章)。您可以在不破壞任何情況下將它們用於局部變數,但您的程式碼仍然會變得混亂。

JavaScript 有許多我們從程式語言中預期會有的值:布林值、數字、字串、陣列等等。JavaScript 中的所有值都有屬性[1] 每個屬性都有(或名稱)和。您可以將屬性視為記錄的欄位。您可以使用點 (.) 算子來讀取屬性

value.propKey

例如,字串 'abc' 有屬性 length

> var str = 'abc';
> str.length
3

上述內容也可以寫成

> 'abc'.length
3

點算子也用於將值指定給屬性:

> var obj = {};  // empty object
> obj.foo = 123; // create property `foo`, set it to 123
123
> obj.foo
123

您可以使用它來呼叫方法:

> 'hello'.toUpperCase()
'HELLO'

在上述範例中,我們在值 'hello' 上呼叫方法 toUpperCase()

基本值與物件

JavaScript 對值做了一個有點武斷的區分:

  • 基本值是布林值、數字、字串、nullundefined
  • 所有其他值都是物件

兩者之間的主要差異在於它們的比較方式;每個物件都有唯一的識別碼,而且只(嚴格地)等於它自己:

> var obj1 = {};  // an empty object
> var obj2 = {};  // another empty object
> obj1 === obj2
false
> obj1 === obj1
true

相反地,編碼相同值的全部基本值都被視為相同

> var prim1 = 123;
> var prim2 = 123;
> prim1 === prim2
true

接下來的兩個區段會更詳細地說明基本值和物件。

基本值

以下所有都是基本值(或簡稱基本類型):

  • 布林值:truefalse(請參閱 布林值
  • 數字:17361.351(請參閱 數字
  • 字串:'abc'"abc"(請參閱 字串
  • 兩個「非值」:undefinednull(請參閱 undefined 和 null

基本型別具有下列 特性

以值進行比較

比較「內容」

> 3 === 3
true
> 'abc' === 'abc'
true
始終不可變

無法 變更、新增或移除 屬性:

> var str = 'abc';

> str.length = 1; // try to change property `length`
> str.length      // ⇒ no effect
3

> str.foo = 3; // try to create property `foo`
> str.foo      // ⇒ no effect, unknown property
undefined

(讀取未知屬性時,總是會傳回 undefined。)

物件

所有非基本型別的 值都是 物件。最常見的物件類型為:

物件具有下列 特性

以參考進行比較

比較身分;每個值都有自己的身分

> ({} === {})  // two different empty objects
false

> var obj1 = {};
> var obj2 = obj1;
> obj1 === obj2
true
預設可變

通常可以自由變更、新增和移除 屬性(請參閱 單一物件

> var obj = {};
> obj.foo = 123; // add property `foo`
> obj.foo
123

undefined 和 null

大多數程式語言都有表示遺失資訊的值。 JavaScript 有兩個這樣的「非值」,undefinednull

  • undefined 表示「沒有值」。未初始化的變數為 undefined

    > var foo;
    > foo
    undefined

    遺失的參數為 undefined

    > function f(x) { return x }
    > f()
    undefined

    如果您讀取不存在的屬性,您會取得 undefined

    > var obj = {}; // empty object
    > obj.foo
    undefined
  • null 表示「沒有物件」。當預期有物件時(參數、物件鏈中的最後一個等等),它會用作非值。

警告

undefinednull 沒有任何屬性,甚至連標準方法(例如 toString())都沒有。

檢查 undefined 或 null

函數通常允許您透過 undefinednull 指出遺失的值。您可以透過明確檢查執行相同的動作:

if (x === undefined || x === null) {
    ...
}

您也可以利用 undefinednull 都被視為 false 的事實

if (!x) {
    ...
}

警告

false0NaN'' 也被視為 false(請參閱 Truthy and Falsy)。

使用 typeof 和 instanceof 分類值

有兩個用於 分類值的運算子:typeof 主要用於原始值,而 instanceof 則用於物件。

typeof 的外觀如下

typeof value

它會傳回一個描述 value「類型」的字串。以下是一些範例

> typeof true
'boolean'
> typeof 'abc'
'string'
> typeof {} // empty object literal
'object'
> typeof [] // empty array literal
'object'

下表列出 typeof 的所有結果

運算元 結果

undefined

'undefined'

null

'object'

布林值

'boolean'

數字值

'number'

字串值

'string'

函數

'function'

所有其他正常值

'object'

(引擎建立的值)

JavaScript 引擎被允許建立 typeof 會傳回任意字串(不同於此表中列出的所有結果)的值。

typeof null 傳回 'object' 是個錯誤 ,無法修復,因為它會中斷現有的程式碼。這並不表示 null 是個物件。

instanceof 看起來像這樣

value instanceof Constr

如果 value 是由建構函式 Constr 建立的物件,它會傳回 true(請參閱 建構函式:物件工廠)。以下是一些範例

> var b = new Bar();  // object created by constructor Bar
> b instanceof Bar
true

> {} instanceof Object
true
> [] instanceof Array
true
> [] instanceof Object  // Array is a subconstructor of Object
true

> undefined instanceof Object
false
> null instanceof Object
false

布林值

原始布林值類型包含 truefalse 值。下列運算子會產生布林值:

  • 二元邏輯運算子:&&(與)、||(或)
  • 前置邏輯運算子:!(非)
  • 比較運算子

    • 等號運算子:===!====!=
    • 排序運算子(適用於字串和數字):>>=<<=

真值和假值

只要 JavaScript 預期布林值(例如 if 陳述式的條件),就可以使用任何值。它會被解釋為 truefalse。下列值會被解釋為 false

  • undefinednull
  • 布林值:false
  • 數字:0NaN
  • 字串:''

所有其他值(包括所有物件!)都被視為 true。被解釋為 false 的值稱為假值,而被解釋為 true 的值稱為真值。以函式方式呼叫的 Boolean() 會將其參數轉換為布林值。您可以使用它來測試值是如何被解釋的

> Boolean(undefined)
false
> Boolean(0)
false
> Boolean(3)
true
> Boolean({}) // empty object
true
> Boolean([]) // empty array
true

二元邏輯運算子

JavaScript 中的二元邏輯運算子是短路運算。也就是說,如果第一個運算元足以決定結果,則不會評估第二個運算元。例如,在下列表達式中,函式 foo() 永遠不會被呼叫:

false && foo()
true  || foo()

此外,二元邏輯運算子會傳回其運算元之一,而該運算元可能是布林值,也可能不是布林值。會使用真值檢查來決定哪一個運算元

且 (&&)

如果 第一個運算元為假,則傳回它。否則,傳回第二個運算元:

> NaN && 'abc'
NaN
> 123 && 'abc'
'abc'
或 (||)

如果 第一個運算元為真,則傳回它。否則,傳回第二個運算元:

> 'abc' || 123
'abc'
> '' || 123
123

相等運算子

JavaScript 有 兩種相等性:

  • 一般或「寬鬆」的(不)相等: ==!=
  • 嚴格的(不)相等: ===!==

一般相等性將(過多)的值視為相等(詳細說明請參閱 一般(寬鬆)相等(==, !=)),這可能會隱藏錯誤。因此,建議總是使用嚴格相等

數字

JavaScript 中的所有數字都是浮點數:

> 1 === 1.0
true

特殊數字包括下列:

NaN(「非數字」)

錯誤值

> Number('xyz')  // 'xyz' can’t be converted to a number
NaN
Infinity

大多數也是錯誤值:

> 3 / 0
Infinity
> Math.pow(2, 1024)  // number too large
Infinity

Infinity 大於任何其他數字(NaN 除外)。類似地,-Infinity 小於任何其他數字(NaN 除外)。這使得這些數字可用作預設值(例如,當您在尋找最小值或最大值時)。

運算子

JavaScript 具有下列算術運算子(請參閱 算術運算子)

  • 加法: number1 + number2
  • 減法: number1 - number2
  • 乘法: number1 * number2
  • 除法: number1 / number2
  • 餘數: number1 % number2
  • 遞增:++variablevariable++
  • 遞減:--variablevariable--
  • 取負:-value
  • 轉換為數字:+value

全域物件 Math(請參閱 Math)提供更多算術運算,透過函式。

JavaScript 也有用於位元運算的運算子(例如,位元 And;請參閱 位元運算子)。

字串

字串可以直接透過字串文字建立。這些文字以單引號或雙引號為界。反斜線 (\) 會跳脫字元並產生一些控制字元。以下是一些範例:

'abc'
"abc"

'Did she say "Hello"?'
"Did she say \"Hello\"?"

'That\'s nice!'
"That's nice!"

'Line 1\nLine 2'  // newline
'Backlash: \\'

單一字元可透過 方括弧 存取:

> var str = 'abc';
> str[1]
'b'

屬性 length 會計算字串中的字元數:

> 'abc'.length
3

與所有基本型別一樣,字串是不可變的;如果您想變更現有的字串,您需要建立一個新的字串。

字串運算子

字串會透過加號 (+) 運算子串接 ,如果其中一個運算元是字串,它會將另一個運算元轉換為字串:

> var messageCount = 3;
> 'You have ' + messageCount + ' messages'
'You have 3 messages'

若要分多個 步驟 串接字串,請使用 += 運算子:

> var str = '';
> str += 'Multiple ';
> str += 'pieces ';
> str += 'are concatenated.';
> str
'Multiple pieces are concatenated.'

字串方法

字串有許多有用的 方法(請參閱 字串原型方法)。以下是一些範例

> 'abc'.slice(1)  // copy a substring
'bc'
> 'abc'.slice(1, 2)
'b'

> '\t xyz  '.trim()  // trim whitespace
'xyz'

> 'mjölnir'.toUpperCase()
'MJÖLNIR'

> 'abc'.indexOf('b')  // find a string
1
> 'abc'.indexOf('x')
-1

陳述式

JavaScript 中的條件式和迴圈會在以下各節中介紹。

條件式

if 陳述式 有一個 then 子句和 一個選用的 else 子句,這些子句會根據布林條件執行:

if (myvar === 0) {
    // then
}

if (myvar === 0) {
    // then
} else {
    // else
}

if (myvar === 0) {
    // then
} else if (myvar === 1) {
    // else-if
} else if (myvar === 2) {
    // else-if
} else {
    // else
}

我建議總是使用大括號(它們表示零個或多個陳述式的區塊)。但是,如果子句只是一個單一陳述式,您不必這麼做(控制流程陳述式 forwhile 也是如此):

if (x < 0) return -x;

以下是 switch 陳述式。 fruit 的值會決定執行哪個 case

switch (fruit) {
    case 'banana':
        // ...
        break;
    case 'apple':
        // ...
        break;
    default:  // all other cases
        // ...
}

case 之後的「運算元」可以是任何表達式;它會透過 ===switch 的參數進行比較。

迴圈

for 迴圈有 下列格式:

for (⟦«init»⟧; ⟦«condition»⟧; ⟦«post_iteration»⟧)
    «statement»

init 會在迴圈開始時執行。 condition 會在每次迴圈反覆之前檢查;如果它變成 false,則迴圈會終止。 post_iteration 會在每次迴圈反覆之後執行。

這個範例會在主控台上列印陣列 arr 的所有元素

for (var i=0; i < arr.length; i++) {
    console.log(arr[i]);
}

while 迴圈會持續 反覆執行其主體,只要其條件成立:

// Same as for loop above:
var i = 0;
while (i < arr.length) {
    console.log(arr[i]);
    i++;
}

do-while 迴圈會持續迴圈其主體,只要其條件成立。由於條件會在主體之後,因此主體至少會執行一次:

do {
    // ...
} while (condition);

在所有迴圈中:

  • break 會離開迴圈。
  • continue 會開始新的迴圈反覆運算。

函式

定義函式的方法之一是透過函式宣告

function add(param1, param2) {
    return param1 + param2;
}

前述程式碼定義一個函式,add,它有兩個參數,param1param2,並傳回兩個參數的總和。以下是呼叫該函式的方式

> add(6, 1)
7
> add('a', 'b')
'ab'

定義 add() 的另一種方式是將函式運算式指定給變數 add

var add = function (param1, param2) {
    return param1 + param2;
};

函式運算式會產生一個值,因此可以用來直接將函式傳遞為其他函式的引數

someOtherFunction(function (p1, p2) { ... });

函式宣告會提升

函式宣告提升—完全移到目前範圍的開頭。這允許您參照稍後宣告的函式:

function foo() {
    bar();  // OK, bar is hoisted
    function bar() {
        ...
    }
}

請注意,雖然 var 宣告也會提升(請參閱 變數會提升),但它們執行的指定並不會提升

function foo() {
    bar();  // Not OK, bar is still undefined
    var bar = function () {
        // ...
    };
}

特殊變數 arguments

您可以在 JavaScript 中使用任意數量的引數呼叫任何函式;語言永遠不會抱怨。但是,它會透過特殊變數 arguments 提供所有參數。arguments 看起來像陣列,但沒有任何陣列方法:

> function f() { return arguments }
> var args = f('a', 'b', 'c');
> args.length
3
> args[0]  // read element at index 0
'a'

太多或太少引數

讓我們使用下列函式來探討 JavaScript 中如何處理太多或太少的參數(函式 toArray() 顯示在 將引數轉換為陣列

function f(x, y) {
    console.log(x, y);
    return toArray(arguments);
}

額外的參數將會被忽略(除了 arguments

> f('a', 'b', 'c')
a b
[ 'a', 'b', 'c' ]

遺失的參數會取得值 undefined

> f('a')
a undefined
[ 'a' ]
> f()
undefined undefined
[]

選用參數

以下是指定參數預設值的常見模式:

function pair(x, y) {
    x = x || 0;  // (1)
    y = y || 0;
    return [ x, y ];
}

在第 (1) 行中,|| 算子會傳回 x(如果它是真值(非 nullundefined 等))。否則,它會傳回第二個運算元

> pair()
[ 0, 0 ]
> pair(3)
[ 3, 0 ]
> pair(3, 5)
[ 3, 5 ]

強制執行元數

如果您想強制執行元數(特定數量的參數),您可以檢查arguments.length

function pair(x, y) {
    if (arguments.length !== 2) {
        throw new Error('Need exactly 2 arguments');
    }
    ...
}

將參數轉換為陣列

arguments不是陣列,它只是類陣列(請參閱 類陣列物件和一般方法)。它有一個屬性 length,您可以透過方括號中的索引來存取其元素。不過,您無法移除元素或呼叫任何陣列方法。因此,您有時需要將 arguments 轉換為陣列,以下函式就是這樣做的(它在 類陣列物件和一般方法 中說明)

function toArray(arrayLikeObject) {
    return Array.prototype.slice.call(arrayLikeObject);
}

例外處理

處理例外最常見的方式(請參閱 第 14 章)如下

function getPerson(id) {
    if (id < 0) {
        throw new Error('ID must not be negative: '+id);
    }
    return { id: id }; // normally: retrieved from database
}

function getPersons(ids) {
    var result = [];
    ids.forEach(function (id) {
        try {
            var person = getPerson(id);
            result.push(person);
        } catch (exception) {
            console.log(exception);
        }
    });
    return result;
}

try 子句圍繞著關鍵程式碼,而如果在 try 子句內引發例外,則會執行 catch 子句。使用前述程式碼:

> getPersons([2, -5, 137])
[Error: ID must not be negative: -5]
[ { id: 2 }, { id: 137 } ]

嚴格模式

嚴格模式(請參閱 嚴格模式)啟用更多警告並使 JavaScript 成為更簡潔的語言(非嚴格模式有時稱為「寬鬆模式」)。若要開啟它,請先在 JavaScript 檔案或 <script> 標籤中輸入下列程式碼:

'use strict';

您也可以針對每個函式啟用嚴格模式:

function functionInStrictMode() {
    'use strict';
}

變數範圍和閉包

在 JavaScript 中,您在使用變數之前透過 var 宣告變數:

> var x;
> x
undefined
> y
ReferenceError: y is not defined

您可以使用單一 var 陳述式宣告和初始化多個變數

var x = 1, y = 2, z = 3;

但我建議每個變數使用一個陳述式(原因說明語法 中)。因此,我會將前一個陳述式改寫為

var x = 1;
var y = 2;
var z = 3;

由於提升(請參閱 變數會提升),通常最好在函式的開頭宣告變數。

變數是函式範圍

變數的範圍總是完整的函式(相對於目前的區塊)。例如:

function foo() {
    var x = -512;
    if (x < 0) {  // (1)
        var tmp = -x;
        ...
    }
    console.log(tmp);  // 512
}

我們可以看到變數 tmp 沒有限制在從第 (1) 行開始的區塊;它存在於函式的最後。

變數會提升

每個變數宣告都會 提升宣告會移到函式的開頭,但它所做的指定會保留。例如,考慮以下函式中第 (1) 行的變數宣告:

function foo() {
    console.log(tmp); // undefined
    if (false) {
        var tmp = 3;  // (1)
    }
}

在內部,前一個函式會這樣執行

function foo() {
    var tmp;  // hoisted declaration
    console.log(tmp);
    if (false) {
        tmp = 3;  // assignment stays put
    }
}

閉包

每個函式 會保持連線到圍繞它的函式的變數,即使它離開建立它的範圍。例如:

function createIncrementor(start) {
    return function () {  // (1)
        start++;
        return start;
    }
}

從第 (1) 行開始的函式離開建立它的內容,但會保持連線到 start 的即時版本

> var inc = createIncrementor(5);
> inc()
6
> inc()
7
> inc()
8

閉包 是函式加上連線到其周圍範圍的變數。因此,createIncrementor() 回傳的內容是閉包。

IIFE 模式:引入新的範圍

有時候您想要引入新的 變數範圍—例如,防止變數變成全域變數。在 JavaScript 中,您不能使用區塊來執行此動作;您必須使用函式。但有一個模式可以用區塊狀的方式使用函式。它稱為 IIFE立即呼叫函式表示式,讀音為「iffy」)

(function () {  // open IIFE
    var tmp = ...;  // not a global variable
}());  // close IIFE

請務必照著顯示的內容輸入前一個範例(註解除外)。IIFE 是函式表示式,在您定義它後會立即呼叫。在函式內,會存在新的範圍,防止 tmp 變成全域變數。請參閱 透過 IIFE 引入新的範圍,以取得 IIFE 的詳細資料。

IIFE 使用案例:透過閉包無意間共用

閉包會保持它們 與外部變數的連線,有時這不是您想要的:

var result = [];
for (var i=0; i < 5; i++) {
    result.push(function () { return i });  // (1)
}
console.log(result[1]()); // 5 (not 1)
console.log(result[3]()); // 5 (not 3)

第 (1) 行回傳的值總是 i 的目前值,而不是函式建立時的數值。迴圈結束後,i 的值為 5,這就是陣列中所有函式回傳該值的緣故。如果您希望第 (1) 行的函式接收 i 的目前值的快照,您可以使用 IIFE

for (var i=0; i < 5; i++) {
    (function () {
        var i2 = i; // copy current i
        result.push(function () { return i2 });
    }());
}

物件和建構函式

本節涵蓋 JavaScript 的兩個基本 物件導向機制:單一物件和 建構函式(是物件的工廠,類似於其他語言中的類別)。

單一物件

如同所有值,物件具有屬性。您實際上可以將物件視為一組屬性,其中每個屬性都是 (金鑰、值) 配對。金鑰為字串,值為任何 JavaScript 值。

在 JavaScript 中,您可以透過 物件文字 直接建立純粹物件

'use strict';
var jane = {
    name: 'Jane',

    describe: function () {
        return 'Person named '+this.name;
    }
};

前述物件具有屬性 namedescribe。您可以讀取(「取得」)和寫入(「設定」)屬性

> jane.name  // get
'Jane'
> jane.name = 'John';  // set
> jane.newProperty = 'abc';  // property created automatically

函式值屬性,例如 describe,稱為 方法。它們使用 this 參照用於呼叫它們的物件:

> jane.describe()  // call method
'Person named John'
> jane.name = 'Jane';
> jane.describe()
'Person named Jane'

in 算子檢查 屬性是否存在:

> 'newProperty' in jane
true
> 'foo' in jane
false

如果您讀取不存在的屬性,會取得值 undefined。因此,前兩個檢查也可以這樣執行:[2]

> jane.newProperty !== undefined
true
> jane.foo !== undefined
false

delete 算子 移除屬性:

> delete jane.newProperty
true
> 'newProperty' in jane
false

任意屬性金鑰

屬性金鑰可以是任何字串。到目前為止,我們已在物件文字和點運算子之後看到屬性金鑰。不過,您只能在它們是識別碼時以這種方式使用它們(請參閱 識別碼和變數名稱)。如果您想使用其他字串作為金鑰,您必須在物件文字中加上引號,並使用方括號取得和設定屬性:

> var obj = { 'not an identifier': 123 };
> obj['not an identifier']
123
> obj['not an identifier'] = 456;

方括號也允許您 計算屬性的金鑰:

> var obj = { hello: 'world' };
> var x = 'hello';

> obj[x]
'world'
> obj['hel'+'lo']
'world'

萃取方法

如果您萃取方法,它會失去與物件的連結。函式本身不再是方法 ,而且 this 的值為 undefined(在嚴格模式中)。

舉例來說,我們回到先前的物件 jane

'use strict';
var jane = {
    name: 'Jane',

    describe: function () {
        return 'Person named '+this.name;
    }
};

我們想要從 jane 中擷取方法 describe,把它放入變數 func 中,再呼叫它。不過,這行不通

> var func = jane.describe;
> func()
TypeError: Cannot read property 'name' of undefined

解決方案是使用所有函式都具備的方法 bind()。它會建立一個新的函式,其 this 永遠具有給定的值:

> var func2 = jane.describe.bind(jane);
> func2()
'Person named Jane'

方法中的函式

每個函式都有 它自己的特殊變數 this。如果你在方法中巢狀一個函式,這會造成不便,因為你無法從函式中存取方法的 this。以下是一個範例,我們在其中呼叫 forEach,並使用一個函式來反覆運算陣列:

var jane = {
    name: 'Jane',
    friends: [ 'Tarzan', 'Cheeta' ],
    logHiToFriends: function () {
        'use strict';
        this.friends.forEach(function (friend) {
            // `this` is undefined here
            console.log(this.name+' says hi to '+friend);
        });
    }
}

呼叫 logHiToFriends 會產生錯誤

> jane.logHiToFriends()
TypeError: Cannot read property 'name' of undefined

我們來看兩個修正此問題的方法。首先,我們可以將 this 儲存在不同的變數中

logHiToFriends: function () {
    'use strict';
    var that = this;
    this.friends.forEach(function (friend) {
        console.log(that.name+' says hi to '+friend);
    });
}

或者,forEach 有第二個參數,允許你提供 this 的值

logHiToFriends: function () {
    'use strict';
    this.friends.forEach(function (friend) {
        console.log(this.name+' says hi to '+friend);
    }, this);
}

函式運算式經常在 JavaScript 中用作函式呼叫中的引數。當你從其中一個函式運算式參照 this 時,務必小心。

建構函式:物件的工廠

到目前為止,你可能會認為 JavaScript 物件 是從字串到值的對應,這個觀念是 JavaScript 物件文字所暗示的,它看起來像其他語言的對應/字典文字。不過,JavaScript 物件也支援一個真正物件導向的功能:繼承。本節不會完全說明 JavaScript 繼承如何運作,但會向你展示一個簡單的模式,讓你入門。如果你想進一步了解,請參閱 第 17 章

除了是「真正的」函式和方法之外,函式在 JavaScript 中還扮演另一個角色:它們會變成 建構函式,也就是物件的工廠,如果透過 new 營運子呼叫它們的話。因此,建構函式大致類似於其他語言中的類別。根據慣例,建構函式的名稱以大寫字母開頭。例如:

// Set up instance data
function Point(x, y) {
    this.x = x;
    this.y = y;
}
// Methods
Point.prototype.dist = function () {
    return Math.sqrt(this.x*this.x + this.y*this.y);
};

我們可以看到建構函式有兩個部分。首先,函式 Point 設定執行個體資料。其次,屬性 Point.prototype 包含一個具有方法的物件。前者資料是特定於每個執行個體,而後者資料則在所有執行個體之間共用。

要使用 Point,我們透過 new 算子來呼叫它:

> var p = new Point(3, 5);
> p.x
3
> p.dist()
5.830951894845301

pPoint 的執行個體

> p instanceof Point
true

陣列

陣列 是元素的序列,可透過從 0 開始的整數索引來存取。

陣列文字

陣列文字很方便用於 建立陣列:

> var arr = [ 'a', 'b', 'c' ];

前一個陣列有三個元素:字串 'a''b''c'。您可以透過整數索引來存取它們

> arr[0]
'a'
> arr[0] = 'x';
> arr
[ 'x', 'b', 'c' ]

length 屬性指出陣列有多少個元素。 您可以使用它來附加元素和移除元素:

> var arr = ['a', 'b'];
> arr.length
2

> arr[arr.length] = 'c';
> arr
[ 'a', 'b', 'c' ]
> arr.length
3

> arr.length = 1;
> arr
[ 'a' ]

in 算子也適用於 陣列:

> var arr = [ 'a', 'b', 'c' ];
> 1 in arr // is there an element at index 1?
true
> 5 in arr // is there an element at index 5?
false

請注意,陣列是物件,因此 可以有物件屬性:

> var arr = [];
> arr.foo = 123;
> arr.foo
123

陣列方法

陣列有許多 方法(請參閱 陣列原型方法)。以下是一些範例

> var arr = [ 'a', 'b', 'c' ];

> arr.slice(1, 2)  // copy elements
[ 'b' ]
> arr.slice(1)
[ 'b', 'c' ]

> arr.push('x')  // append an element
4
> arr
[ 'a', 'b', 'c', 'x' ]

> arr.pop()  // remove last element
'x'
> arr
[ 'a', 'b', 'c' ]

> arr.shift()  // remove first element
'a'
> arr
[ 'b', 'c' ]

> arr.unshift('x')  // prepend an element
3
> arr
[ 'x', 'b', 'c' ]

> arr.indexOf('b')  // find the index of an element
1
> arr.indexOf('y')
-1

> arr.join('-')  // all elements in a single string
'x-b-c'
> arr.join('')
'xbc'
> arr.join()
'x,b,c'

反覆運算陣列

有幾個陣列 方法可用於反覆運算元素(請參閱 反覆運算 (非破壞性))。最重要的兩個是 forEachmap

forEach 反覆運算陣列,並將目前的元素和其索引傳遞給函式

[ 'a', 'b', 'c' ].forEach(
    function (elem, index) {  // (1)
        console.log(index + '. ' + elem);
    });

前一個程式碼產生下列輸出

0. a
1. b
2. c

請注意,第 (1) 行的函式可以忽略引數。例如,它只能有參數 elem

map 透過將 函式套用至現有陣列的每個元素來建立新的陣列:

> [1,2,3].map(function (x) { return x*x })
[ 1, 4, 9 ]

正規表示式

JavaScript 具有內建的正規表示法支援(第 19 章 介紹教學課程,並更詳細地說明其運作方式)。它們由斜線分隔:

/^abc$/
/[A-Za-z0-9]+/

方法 test():是否有符合項?

> /^a+b+$/.test('aaab')
true
> /^a+b+$/.test('aaa')
false

方法 exec():符合項和擷取群組

> /a(b+)a/.exec('_abbba_aba_')
[ 'abbba', 'bbb' ]

傳回的陣列包含索引 0 處的完整符合項、索引 1 處的第一個群組擷取,依此類推。有一種方式(在RegExp.prototype.exec:擷取群組中討論)可重複呼叫此方法以取得所有符合項。

方法 replace():搜尋和取代

> '<a> <bbb>'.replace(/<(.*?)>/g, '[$1]')
'[a] [bbb]'

replace 的第一個參數必須是帶有 /g 旗標的正規表示法;否則,只會取代第一次出現的符合項。還有一種方式(如在String.prototype.replace:搜尋和取代中所討論)可使用函式來計算取代。

數學

Math(請參閱第 21 章)是一個具有算術函式的物件。以下是一些範例:

> Math.abs(-2)
2

> Math.pow(3, 2)  // 3 to the power of 2
9

> Math.max(2, -1, 5)
5

> Math.round(1.9)
2

> Math.PI  // pre-defined constant for π
3.141592653589793

> Math.cos(Math.PI)  // compute the cosine for 180°
-1

標準函式庫的其他功能

JavaScript 的標準函式庫相對簡潔,但還有更多您可以使用的功能:

Date第 20 章
日期的建構函式,其主要功能是剖析和建立日期字串,以及存取日期的組成部分(年、時等)。
JSON第 22 章
一個具有用於剖析和產生 JSON 資料的函式的物件。
console.* 方法(請參閱主控台 API
這些特定於瀏覽器的函式並非語言本身的一部分,但其中一些函式也在 Node.js 上運作。


[1] 兩個「非值」undefinednull 沒有屬性。

[2] 注意:此檢查會將存在的屬性報告為不存在,但值為 undefined

下一頁:II. 背景