本章涵蓋 JavaScript 的陳述式:變數宣告、迴圈、條件式和其他內容。
var
用於宣告變數,這會建立變數,讓您可以使用它。 等號運算子 (=
) 用於指派值給它:
var
foo
;
foo
=
'abc'
;
var
也讓您可以將前兩個陳述式合併成一個陳述式
var
foo
=
'abc'
;
最後,您也可以將多個 var
陳述式合併成一個
var
x
,
y
=
123
,
z
;
在 第 16 章 中進一步了解變數的工作原理。
複合陳述式(例如迴圈和條件式)有一個或多個嵌入的「本體」—例如 while
迴圈:
while
(
«
condition
»
)
«
statement
»
對於本體 «陳述式»
,您可以選擇。您可以使用單一陳述式
while
(
x
>=
0
)
x
--
;
或您可以使用區塊(計為單一陳述式):
while
(
x
>
0
)
{
x
--
;
}
本節探討 JavaScript 的迴圈陳述式。
break ⟦«標籤»⟧
continue ⟦«標籤»⟧
標籤是識別碼,後接冒號。在迴圈前面,標籤讓您可以中斷或繼續該迴圈,即使從巢狀在迴圈內的迴圈中也可以。在區塊前面,您可以中斷該區塊。在兩種情況下,標籤的名稱都會變成 break
或 continue
的引數。以下是中斷區塊的範例:
function
findEvenNumber
(
arr
)
{
loop
:
{
// label
for
(
var
i
=
0
;
i
<
arr
.
length
;
i
++
)
{
var
elem
=
arr
[
i
];
if
((
elem
%
2
)
===
0
)
{
console
.
log
(
'Found: '
+
elem
);
break
loop
;
}
}
console
.
log
(
'No even number found.'
);
}
console
.
log
(
'DONE'
);
}
while
(
«
condition
»
)
«
statement
»
只要 condition
成立,就執行 statement
。如果 condition
永遠為 true
,就會得到一個無限迴圈
while
(
true
)
{
...
}
在以下範例中,我們移除陣列中的所有元素,並將它們記錄到主控台
var
arr
=
[
'a'
,
'b'
,
'c'
];
while
(
arr
.
length
>
0
)
{
console
.
log
(
arr
.
shift
());
}
以下是輸出
a b c
A do-while
迴圈:
do
«
statement
»
while
(
«
condition
»
);
至少執行 statement
一次,然後只要 condition
成立,就繼續執行。例如
var
line
;
do
{
line
=
prompt
(
'Enter a number:'
);
}
while
(
!
/^[0-9]+$/
.
test
(
line
));
在 for
迴圈中:
for
(
⟦«
init
»⟧
;
⟦«
condition
»⟧
;
⟦«
post_iteration
»⟧
)
«
statement
»
init
在迴圈之前執行一次,只要 condition
為 true
,迴圈就會繼續執行。您可以在 init
中使用 var
來宣告變數,但這些變數的範圍永遠是完整的周圍函式。在迴圈的每次反覆運算之後,都會執行 post_iteration
。考量所有這些,前面的迴圈等於以下 while
迴圈
«
init
»
;
while
(
«
condition
»
)
{
«
statement
»
«
post_iteration
»
;
}
以下範例是反覆運算陣列的傳統方式(其他可能性說明於 最佳實務:反覆運算陣列)
var
arr
=
[
'a'
,
'b'
,
'c'
];
for
(
var
i
=
0
;
i
<
arr
.
length
;
i
++
)
{
console
.
log
(
arr
[
i
]);
}
如果您省略頭部的所有部分,for
迴圈就會變成無限迴圈
for
(;;)
{
...
}
A for-in
迴圈:
for
(
«
variable
»
in
«
object
»
)
«
statement
»
反覆運算 object
的所有屬性金鑰,包括繼承的屬性。不過,會忽略標記為不可列舉的屬性(請參閱 屬性屬性和屬性描述詞)。以下規則適用於 for-in
迴圈
var
來宣告變數,但這些變數的範圍總是完整的周圍函式。
不要使用 for-in
來 迭代陣列。首先,它會迭代索引,而不是值:
> var arr = [ 'a', 'b', 'c' ]; > for (var key in arr) { console.log(key); } 0 1 2
其次,它也會迭代所有(非索引)屬性鍵。以下範例說明當您將屬性 foo
加入陣列時會發生什麼事
> var arr = [ 'a', 'b', 'c' ]; > arr.foo = true; > for (var key in arr) { console.log(key); } 0 1 2 foo
因此,您最好使用一般的 for
迴圈或陣列方法 forEach()
(請參閱 最佳實務:迭代陣列)。
for-in
迴圈會迭代 所有(可列舉的)屬性,包括繼承的屬性。這可能不是您想要的。讓我們使用以下建構函式來說明這個問題:
function
Person
(
name
)
{
this
.
name
=
name
;
}
Person
.
prototype
.
describe
=
function
()
{
return
'Name: '
+
this
.
name
;
};
Person
的執行個體會從 Person.prototype
繼承屬性 describe
,而 for-in
會看到這個屬性
var
person
=
new
Person
(
'Jane'
);
for
(
var
key
in
person
)
{
console
.
log
(
key
);
}
以下是輸出
name describe
一般來說,使用 for-in
的最佳方式是透過 hasOwnProperty()
來略過繼承的屬性
for
(
var
key
in
person
)
{
if
(
person
.
hasOwnProperty
(
key
))
{
console
.
log
(
key
);
}
}
以下是輸出
name
最後還有一個注意事項: person
可能有一個屬性 hasOwnProperty
,這會讓檢查無法運作。為了安全起見,您必須直接參照一般方法(請參閱 一般方法:從原型借用方法) Object.prototype.hasOwnProperty
for
(
var
key
in
person
)
{
if
(
Object
.
prototype
.
hasOwnProperty
.
call
(
person
,
key
))
{
console
.
log
(
key
);
}
}
還有其他更方便的方法可以迭代屬性鍵,這些方法會在 最佳實務:迭代自己的屬性 中說明。
這個迴圈只存在於 Firefox。 不要使用它。
本節涵蓋 JavaScript 的條件式陳述。
在 if-then-else
敘述中:
if
(
«
condition
»
)
«
then_branch
»
⟦
else
«
else_branch
»⟧
then_branch
和 else_branch
可以是單一敘述或敘述區塊(請參閱 迴圈和條件式的主體)。
你可以 串接多個 if
敘述:
if
(
s1
>
s2
)
{
return
1
;
}
else
if
(
s1
<
s2
)
{
return
-
1
;
}
else
{
return
0
;
}
請注意,在前面的範例中,所有 else
分支都是單一敘述(if
敘述)。只允許 else
分支使用區塊的程式語言需要某種 else-if
分支來串接。
下列範例中 else
分支稱為 懸宕,因為不明確它屬於哪一個 if
敘述:
if
(
«
cond1
»
)
if
(
«
cond2
»
)
«
stmt1
»
else
«
stmt2
»
以下是簡單的規則:使用大括號。 前面的程式片段等同於下列程式碼(其中 else
所屬的對象很明顯):
if
(
«
cond1
»
)
{
if
(
«
cond2
»
)
{
«
stmt1
»
}
else
{
«
stmt2
»
}
}
一個 switch
敘述:
switch
(
«
expression
»
)
{
case
«
label1_1
»
:
case
«
label1_2
»
:
...
«
statements1
»
⟦
break
;
⟧
case
«
label2_1
»
:
case
«
label2_2
»
:
...
«
statements2
»
⟦
break
;
⟧
...
⟦
default
:
«
statements_default
»
⟦
break
;
⟧⟧
}
評估 expression
,然後跳到標籤與 switch
參數結果相符的 case
子句。 如果沒有標籤相符,switch
會跳到 default
子句(如果存在)或不會執行任何動作。
case
之後的「運算元」可以是任何運算式;它會透過 ===
與 switch
的參數進行比較。
如果你沒有使用終止敘述完成子句,執行會繼續進行到下一個子句。最常使用的終止敘述是 break
。 但是 return
和 throw
也可以使用,即使它們通常會離開不只 switch
敘述。
下列範例說明如果你使用 throw
或 return
,就不需要 break
function
divide
(
dividend
,
divisor
)
{
switch
(
divisor
)
{
case
0
:
throw
'Division by zero'
;
default
:
return
dividend
/
divisor
;
}
}
在此範例中,沒有 default
子句。因此,如果 fruit
與任何 case
標籤都不相符,就不會發生任何事
function
useFruit
(
fruit
)
{
switch
(
fruit
)
{
case
'apple'
:
makeCider
();
break
;
case
'grape'
:
makeWine
();
break
;
// neither apple nor grape: do nothing
}
}
在此範例中,有多個 case
標籤連續出現
function
categorizeColor
(
color
)
{
var
result
;
switch
(
color
)
{
case
'red'
:
case
'yellow'
:
case
'blue'
:
result
=
'Primary color: '
+
color
;
break
;
case
'orange'
:
case
'green'
:
case
'violet'
:
result
=
'Secondary color: '
+
color
;
break
;
case
'black'
:
case
'white'
:
result
=
'Not a color'
;
break
;
default
:
throw
'Illegal argument: '
+
color
;
}
console
.
log
(
result
);
}
此範例說明 case
之後的數值可以是任意運算式
function
compare
(
x
,
y
)
{
switch
(
true
)
{
case
x
<
y
:
return
-
1
;
case
x
===
y
:
return
0
;
default
:
return
1
;
}
}
前述的 switch
陳述式會透過遍歷 case
子句,尋找與其參數 true
相符的項目。如果其中一個 case
表達式評估為 true
,就會執行對應的 case
主體。因此,前述程式碼等同於下列 if
陳述式
function
compare
(
x
,
y
)
{
if
(
x
<
y
)
{
return
-
1
;
}
else
if
(
x
===
y
)
{
return
0
;
}
else
{
return
1
;
}
}
本節說明 with
陳述式在 JavaScript 中的運作方式,以及為何不建議使用它。
with
陳述式的語法如下
with
(
«
object
»
)
«
statement
»
它會將 object
的屬性轉換為 statement
的區域變數。例如
var
obj
=
{
first
:
'John'
};
with
(
obj
)
{
console
.
log
(
'Hello '
+
first
);
// Hello John
}
其預期用途是避免在多次存取物件時產生重複。以下是包含重複的程式碼範例
foo
.
bar
.
baz
.
bla
=
123
;
foo
.
bar
.
baz
.
yadda
=
'abc'
;
with
會讓它變短
with
(
foo
.
bar
.
baz
)
{
bla
=
123
;
yadda
=
'abc'
;
}
一般不建議使用 the with
陳述式(下一節說明原因)。例如,在嚴格模式中禁止使用:
> function foo() { 'use strict'; with ({}); } SyntaxError: strict mode code may not contain 'with' statements
避免使用 類似這樣的程式碼:
// Don't do this:
with
(
foo
.
bar
.
baz
)
{
console
.
log
(
'Hello '
+
first
+
' '
+
last
);
}
請改用具有簡短名稱的暫時變數
var
b
=
foo
.
bar
.
baz
;
console
.
log
(
'Hello '
+
b
.
first
+
' '
+
b
.
last
);
如果您不希望將暫時變數 b
公開到目前的範圍,您可以使用 IIFE(請參閱 透過 IIFE 引入新的範圍)
(
function
()
{
var
b
=
foo
.
bar
.
baz
;
console
.
log
(
'Hello '
+
b
.
first
+
' '
+
b
.
last
);
}());
您也可以選擇將您想要存取的物件設為 IIFE 的參數
(
function
(
b
)
{
console
.
log
(
'Hello '
+
b
.
first
+
' '
+
b
.
last
);
}(
foo
.
bar
.
baz
));
若要了解為何 with
已棄用,請查看下列範例,並注意函式的引數如何完全改變其運作方式:
function
logMessage
(
msg
,
opts
)
{
with
(
opts
)
{
console
.
log
(
'msg: '
+
msg
);
// (1)
}
}
如果 opts
具有屬性 msg
,則第 (1) 行的陳述式不再存取參數 msg
。它存取屬性
> logMessage('hello', {}) // parameter msg msg: hello > logMessage('hello', { msg: 'world' }) // property opts.msg msg: world
with
陳述式會造成三個問題
你無法透過查看識別項的語法環境(其詞彙內容)來判斷它所指為何。根據 Brendan Eich 的說法,這是 with
被棄用的真正原因,而不是效能考量
with
違反詞彙範圍,使得程式分析(例如,為了安全性)難以執行,甚至無法執行。
with
陳述式內,你無法靜態地判斷名稱是指變數還是屬性。只有變數才能由縮小程式重新命名。
以下是一個 with
使程式碼脆弱的範例
function
foo
(
someArray
)
{
var
values
=
...;
// (1)
with
(
someArray
)
{
values
.
someMethod
(...);
// (2)
...
}
}
foo
(
myData
);
// (3)
即使你無法存取陣列 myData
,你也可以防止第 (3) 行的函式呼叫運作。
如何?透過新增一個屬性 values
到 Array.prototype
。例如
Array
.
prototype
.
values
=
function
()
{
...
};
現在第 (2) 行的程式碼呼叫 someArray.values.someMethod()
而不是 values.someMethod()
。原因是,在 with
陳述式內,values
現在是指 someArray.values
,而不是第 (1) 行的區域變數。
這不只是個思想實驗:陣列方法 values()
已新增到 Firefox,並中斷了 TYPO3 內容管理系統。 Brandon Benvie 找出問題所在。