この章では、JavaScriptのステートメントについて説明します。変数宣言、ループ、条件分岐などです。
var
は変数を宣言するために使用されます。変数を作成し、操作できるようにします。等号演算子(=
)は、変数に値を代入するために使用されます。
var
foo
;
foo
=
'abc'
;
var
を使用すると、上記の2つのステートメントを1つにまとめることもできます。
var
foo
=
'abc'
;
さらに、複数のvar
ステートメントを1つにまとめることもできます。
var
x
,
y
=
123
,
z
;
変数の仕組みの詳細については、第16章を参照してください。
複合ステートメント(ループや条件分岐など)には、1つ以上の「本体」が埋め込まれています。たとえば、while
ループは次のようになります。
while
(
«
condition
»
)
«
statement
»
本体の«statement»
には、選択肢があります。単一のステートメントを使用することもできます。
while
(
x
>=
0
)
x
--
;
または、ブロック(単一のステートメントとしてカウントされます)を使用することもできます。
while
(
x
>
0
)
{
x
--
;
}
本体を複数のステートメントで構成する場合は、ブロックを使用する必要があります。複合ステートメント全体を1行で記述できる場合を除き、ブロックを使用することをお勧めします。
このセクションでは、JavaScriptのループステートメントについて説明します。
break ⟦«label»⟧
continue ⟦«label»⟧
ラベルは、識別子の後にコロンを付けたものです。ループの前にラベルを付けると、ネストされたループ内からでも、そのループを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
do-while
ループ:
do
«
statement
»
while
(
«
condition
»
);
statement
を少なくとも1回実行し、その後、condition
が成立する限り実行します。次に例を示します。
var
line
;
do
{
line
=
prompt
(
'Enter a number:'
);
}
while
(
!
/^[0-9]+$/
.
test
(
line
));
for
ループ:
for
(
⟦«
init
»⟧
;
⟦«
condition
»⟧
;
⟦«
post_iteration
»⟧
)
«
statement
»
init
はループの前に1回実行され、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
(;;)
{
...
}
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
最後の注意点が1つあります。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
分岐は、2つのif
ステートメントのどちらに属しているかが明確ではないため、ぶら下がり(dangling)と呼ばれます。
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
を評価し、結果と一致するラベルを持つ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
;
}
}
このセクションでは、JavaScriptでwith
ステートメントがどのように機能するか、そしてなぜその使用が推奨されないのかを説明します。
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'
;
}
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
ステートメントによって発生する問題は3つあります。
構文上の周囲(レキシカルコンテキスト)を見るだけでは、識別子が何を指しているかを判断できません。Brendan Eichによると、with
が非推奨とされたのは、パフォーマンスの考慮ではなく、これが実際の理由でした。
with
はレキシカルスコープに違反するため、プログラム分析(セキュリティなど)が困難または不可能になります。
with
文の中では、名前が変数を参照しているのかプロパティを参照しているのかを静的に判断できません。ミニファイアで名前を変更できるのは変数だけです。with
によってコードが脆弱になる例を次に示します。
function
foo
(
someArray
)
{
var
values
=
...;
// (1)
with
(
someArray
)
{
values
.
someMethod
(...);
// (2)
...
}
}
foo
(
myData
);
// (3)
配列myData
にアクセスできなくても、(3)行目の関数呼び出しが機能しないようにすることができます。
どのようにするのでしょうか? Array.prototype
にプロパティvalues
を追加することで可能です。 例えば、
Array
.
prototype
.
values
=
function
()
{
...
};
これで、(2)行目のコードは、values.someMethod()
ではなく、someArray.values.someMethod()
を呼び出すようになります。その理由は、with
文の中では、values
が(1)行目のローカル変数ではなく、someArray.values
を参照するようになるからです。
これは単なる思考実験ではありません。配列メソッドvalues()
がFirefoxに追加されたことで、コンテンツ管理システムTYPO3に障害が発生しました。Brandon Benvie氏が問題の原因を突き止めました。
debugger
文の構文は次のとおりです。
debugger
;
デバッガがアクティブな場合、この文はブレークポイントとして機能します。アクティブでない場合は、目に見える効果はありません。