この章では、JavaScriptのステートメントについて説明します。変数宣言、ループ、条件分岐などです。
varは変数を宣言するために使用されます。変数を作成し、操作できるようにします。等号演算子(=)は、変数に値を代入するために使用されます。
varfoo;foo='abc';
varを使用すると、上記の2つのステートメントを1つにまとめることもできます。
varfoo='abc';
さらに、複数のvarステートメントを1つにまとめることもできます。
varx,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の引数になります。ブロックから抜け出す例を次に示します。
functionfindEvenNumber(arr){loop:{// labelfor(vari=0;i<arr.length;i++){varelem=arr[i];if((elem%2)===0){console.log('Found: '+elem);breakloop;}}console.log('No even number found.');}console.log('DONE');}
while(«condition»)«statement»
conditionが成立する限り、statementを実行します。conditionが常にtrueの場合、無限ループになります。
while(true){...}
次の例では、配列のすべての要素を削除し、コンソールに記録します。
vararr=['a','b','c'];while(arr.length>0){console.log(arr.shift());}
出力は次のとおりです。
a b c
do-whileループ:
do«statement»while(«condition»);
statementを少なくとも1回実行し、その後、conditionが成立する限り実行します。次に例を示します。
varline;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»;}
次の例は、配列を反復処理する従来の方法です(他の可能性については、ベストプラクティス:配列の反復処理を参照してください)。
vararr=['a','b','c'];for(vari=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ループは、継承されたものを含め、すべての(列挙可能な)プロパティを反復処理します。これは、あなたが望むものではないかもしれません。次のコンストラクタを使用して、問題を説明しましょう。
functionPerson(name){this.name=name;}Person.prototype.describe=function(){return'Name: '+this.name;};
Personのインスタンスは、Person.prototypeからプロパティdescribeを継承します。これは、for-inによって認識されます。
varperson=newPerson('Jane');for(varkeyinperson){console.log(key);}
出力は次のとおりです。
name describe
通常、for-inを使用する最良の方法は、hasOwnProperty()を使用して継承されたプロパティをスキップすることです。
for(varkeyinperson){if(person.hasOwnProperty(key)){console.log(key);}}
出力は次のとおりです。
name
最後の注意点が1つあります。personにはプロパティhasOwnPropertyがある場合があり、チェックが機能しなくなる可能性があります。安全のため、汎用メソッド(汎用メソッド:プロトタイプからのメソッドの借用を参照)Object.prototype.hasOwnPropertyを直接参照する必要があります。
for(varkeyinperson){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){return1;}elseif(s1<s2){return-1;}else{return0;}
上記の例では、すべての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する必要がないことを示しています。
functiondivide(dividend,divisor){switch(divisor){case0:throw'Division by zero';default:returndividend/divisor;}}
この例では、default句はありません。したがって、fruitがいずれのcaseラベルとも一致しない場合、何も起こりません。
functionuseFruit(fruit){switch(fruit){case'apple':makeCider();break;case'grape':makeWine();break;// neither apple nor grape: do nothing}}
ここでは、複数のcaseラベルが連続しています。
functioncategorizeColor(color){varresult;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の後の値が任意の式になることを示しています。
functioncompare(x,y){switch(true){casex<y:return-1;casex===y:return0;default:return1;}}
上記のswitchステートメントは、case句を調べて、パラメータtrueと一致するものを探します。case式のいずれかがtrueと評価された場合、対応するcase本文が実行されます。したがって、上記のコードは次のifステートメントと同等です。
functioncompare(x,y){if(x<y){return-1;}elseif(x===y){return0;}else{return1;}}
このセクションでは、JavaScriptでwithステートメントがどのように機能するか、そしてなぜその使用が推奨されないのかを説明します。
withステートメントの構文は次のとおりです。
with(«object»)«statement»
objectのプロパティを、statementのローカル変数に変換します。次に例を示します。
varobj={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);}
代わりに、短い名前の一時変数を使用します。
varb=foo.bar.baz;console.log('Hello '+b.first+' '+b.last);
一時変数bを現在のスコープに公開したくない場合は、IIFEを使用できます(IIFEによる新しいスコープの導入を参照)。
(function(){varb=foo.bar.baz;console.log('Hello '+b.first+' '+b.last);}());
アクセスしたいオブジェクトをIIFEのパラメータにすることもできます。
(function(b){console.log('Hello '+b.first+' '+b.last);}(foo.bar.baz));
withが非推奨とされる理由を理解するために、次の例を見て、関数の引数がどのように動作を完全に変えるかを確認してください。
functionlogMessage(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: worldwithステートメントによって発生する問題は3つあります。
構文上の周囲(レキシカルコンテキスト)を見るだけでは、識別子が何を指しているかを判断できません。Brendan Eichによると、withが非推奨とされたのは、パフォーマンスの考慮ではなく、これが実際の理由でした。
withはレキシカルスコープに違反するため、プログラム分析(セキュリティなど)が困難または不可能になります。
with文の中では、名前が変数を参照しているのかプロパティを参照しているのかを静的に判断できません。ミニファイアで名前を変更できるのは変数だけです。withによってコードが脆弱になる例を次に示します。
functionfoo(someArray){varvalues=...;// (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;
デバッガがアクティブな場合、この文はブレークポイントとして機能します。アクティブでない場合は、目に見える効果はありません。