本章節探討「基本 JavaScript」,這是 JavaScript 的子集,我選用此名稱是因為它盡可能簡潔,同時又能讓您發揮生產力。當您開始學習 JavaScript 時,我建議您先使用它編寫程式一段時間,再繼續學習這門語言的其他部分。如此一來,您不必一次學習所有內容,以免造成混淆。
本節提供 JavaScript 的一些背景資訊,幫助您了解它為何會是現在的樣貌。
ECMAScript 是 JavaScript 的正式名稱。之所以需要新的名稱,是因為 JavaScript 是一個商標(最初由 Sun 擁有,現在由 Oracle 擁有)。目前,Mozilla 是少數幾家獲准正式使用 JavaScript 名稱的公司之一,因為它很早之前就取得了授權。對於一般用途,這些規則適用:
JavaScript 的創造者布蘭登·艾奇別無選擇,只能非常快速地創造這門語言(否則網景通訊公司將採用其他更糟糕的技術)。他借用了多種程式語言:Java(語法、基本值與物件)、Scheme 和 AWK(一級函式)、Self(原型繼承),以及 Perl 和 Python(字串、陣列和正規表示式)。
JavaScript 直到 ECMAScript 3 才具備例外 處理功能,這說明了為何這門語言經常自動轉換值,而且經常在無聲中失敗:它最初無法擲回例外。
一方面,JavaScript 有些怪癖,而且缺少許多功能(區塊作用域變數、模組、支援子類別化等)。另一方面,它有幾個強大的功能,讓您可以解決這些問題。在其他語言中,您會學習語言功能。在 JavaScript 中,您通常會學習模式。
根據它的影響,JavaScript 能夠支援一種程式設計風格,這一點不足為奇,它結合了函式式程式設計(高階函式;內建 map
、reduce
等)和物件導向程式設計(物件、繼承)。
一些語法的範例:
// 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`
識別碼 是在 JavaScript 中扮演各種語法角色的名稱。例如,變數的名稱是一個識別碼。識別碼會區分大小寫。
大致上,識別碼的第一個字元可以是任何 Unicode 字母、一個美元符號 ($
) 或一個底線 (_
)。後續的字元還可以是任何 Unicode 數字。因此,下列都是合法的識別碼
arg0
_tmp
$elem
π
下列識別碼是 保留字,它們是語法的一部分,不能用作變數名稱(包括函式名稱和參數名稱):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
下列三個識別碼不是保留字,但您應該將它們視為保留字
|
|
|
最後,您也應該遠離標準全域變數的名稱(請參閱 第 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 對值做了一個有點武斷的區分:
null
和 undefined
。
兩者之間的主要差異在於它們的比較方式;每個物件都有唯一的識別碼,而且只(嚴格地)等於它自己:
> 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
接下來的兩個區段會更詳細地說明基本值和物件。
以下所有都是基本值(或簡稱基本類型):
true
、false
(請參閱 布林值)
1736
、1.351
(請參閱 數字)
'abc'
、"abc"
(請參閱 字串)
undefined
、null
(請參閱 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
。)
所有非基本型別的 值都是 物件。最常見的物件類型為:
一般物件,可透過 物件文字 建立(請參閱 單一物件)
{
firstName
:
'Jane'
,
lastName
:
'Doe'
}
陣列,可透過 陣列文字 建立(請參閱 陣列)
[
'apple'
,
'banana'
,
'cherry'
]
上述陣列有三個元素,可透過數字索引存取。例如,'apple'
的索引為 0。
正規表示式,可透過 正規表示式文字 建立(請參閱 正規表示式)
/^a+b+$/
物件具有下列 特性:
大多數程式語言都有表示遺失資訊的值。 JavaScript 有兩個這樣的「非值」,undefined
和 null
:
undefined
表示「沒有值」。未初始化的變數為 undefined
> var foo; > foo undefined
遺失的參數為 undefined
> function f(x) { return x } > f() undefined
如果您讀取不存在的屬性,您會取得 undefined
> var obj = {}; // empty object > obj.foo undefined
null
表示「沒有物件」。當預期有物件時(參數、物件鏈中的最後一個等等),它會用作非值。
undefined
和 null
沒有任何屬性,甚至連標準方法(例如 toString()
)都沒有。
函數通常允許您透過 undefined
或 null
指出遺失的值。您可以透過明確檢查執行相同的動作:
if
(
x
===
undefined
||
x
===
null
)
{
...
}
您也可以利用 undefined
和 null
都被視為 false
的事實
if
(
!
x
)
{
...
}
false
、0
、NaN
和 ''
也被視為 false
(請參閱 Truthy and Falsy)。
有兩個用於 分類值的運算子:typeof
主要用於原始值,而 instanceof
則用於物件。
typeof
的外觀如下
typeof
value
它會傳回一個描述 value
「類型」的字串。以下是一些範例
> typeof true 'boolean' > typeof 'abc' 'string' > typeof {} // empty object literal 'object' > typeof [] // empty array literal 'object'
下表列出 typeof
的所有結果
運算元 | 結果 |
|
|
|
|
布林值 |
|
數字值 |
|
字串值 |
|
函數 |
|
所有其他正常值 |
|
(引擎建立的值) | JavaScript 引擎被允許建立 |
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
原始布林值類型包含 true
和 false
值。下列運算子會產生布林值:
&&
(與)、||
(或)
!
(非)
比較運算子
===
、!==
、==
、!=
>
、>=
、<
、<=
只要 JavaScript 預期布林值(例如 if
陳述式的條件),就可以使用任何值。它會被解釋為 true
或 false
。下列值會被解釋為 false
undefined
、null
false
0
、NaN
''
所有其他值(包括所有物件!)都被視為 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
++variable
、variable++
--variable
、variable--
-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.'
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
}
我建議總是使用大括號(它們表示零個或多個陳述式的區塊)。但是,如果子句只是一個單一陳述式,您不必這麼做(控制流程陳述式 for
和 while
也是如此):
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
,它有兩個參數,param1
和 param2
,並傳回兩個參數的總和。以下是呼叫該函式的方式
> 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
()
{
// ...
};
}
您可以在 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
(如果它是真值(非 null
、undefined
等))。否則,它會傳回第二個運算元
> 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()
回傳的內容是閉包。
有時候您想要引入新的 變數範圍—例如,防止變數變成全域變數。在 JavaScript 中,您不能使用區塊來執行此動作;您必須使用函式。但有一個模式可以用區塊狀的方式使用函式。它稱為 IIFE(立即呼叫函式表示式,讀音為「iffy」)
(
function
()
{
// open IIFE
var
tmp
=
...;
// not a global variable
}());
// close IIFE
請務必照著顯示的內容輸入前一個範例(註解除外)。IIFE 是函式表示式,在您定義它後會立即呼叫。在函式內,會存在新的範圍,防止 tmp
變成全域變數。請參閱 透過 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
;
}
};
前述物件具有屬性 name
和 describe
。您可以讀取(「取得」)和寫入(「設定」)屬性
> 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
p
是 Point
的執行個體
> p instanceof Point true
陣列文字很方便用於 建立陣列:
> 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'
有幾個陣列 方法可用於反覆運算元素(請參閱 反覆運算 (非破壞性))。最重要的兩個是 forEach
和 map
。
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
]
+
/
> /^a+b+$/.test('aaab') true > /^a+b+$/.test('aaa') false
> /a(b+)a/.exec('_abbba_aba_') [ 'abbba', 'bbb' ]
傳回的陣列包含索引 0 處的完整符合項、索引 1 處的第一個群組擷取,依此類推。有一種方式(在RegExp.prototype.exec:擷取群組中討論)可重複呼叫此方法以取得所有符合項。
> '<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 的標準函式庫相對簡潔,但還有更多您可以使用的功能: