JavaScript はC言語や Perl と同じく、手続き型のプログラミング言語です。JavaScript もC言語や Perl と同様に、プログラムの実行を制御する「文」 (命令文ともいう)、データを格納する「変数」、決められた処理を行う「関数」、という構造に分かれます。C言語と同様に、文の終わりにはセミコロン ( ; ) を付けます。コメントは // から行末まで、もしくは /* から */ の間に記述します。
変数と関数は名前をつけて区別します。名前には英数字とアンダースコア _ が使えます。英大文字と英小文字は区別されるので、FOO と Foo と foo は異なる名前と判断されます。関数は JavaScript にあらかじめ用意されている「組み込み関数」のほかに、私達ユーザーが定義することもできます。もちろん「再帰定義」も可能です。
それでは、JavaScript に用意されている基本的なデータ型について簡単に説明しましょう。JavaScript の数値は浮動小数点数 (floating-point number) で表されます。これはC言語の倍精度浮動小数点数 (double) と同じで、範囲は絶対値で約 1e-307 から 1e+308 までです。ES2020 から多倍長整数 BigInt がサポートされました。数値の末尾に n を付けると BigInt になります。
簡単な例を示します。
> a = 100 100 > b = 1.234 1.234 > a 100 > b 1.234 > c = 10 + 20 30 > d = 10 * 20 200 > e = 10 - 20 -10 > f = 10 / 20 0.5 > c 30 > d 200 > e -10 > f 0.5 > g = 123456789n 123456789n > g * g 15241578750190521n > g * g* g 1881676371789154860897069n
BigInt と他の数値を混ぜて計算することはできまん。エラーになります。
JavaScript はC言語と違い、変数のデータ型を宣言 [*1} する必要はありません。変数に値をセットすることを「代入」といいます。代入には = を使います。これはC言語や Perl と同じです。コンソールで変数名を入力するとその値が表示されます。主な算術演算子を下表に示します。
操作 | 結果 |
---|---|
-x | x を負にする |
x + y | x と y の和 |
x - y | x と y の差 |
x * y | x と y の積 |
x / y | x 割る y の商 |
x % y | x 割る y の剰余 |
文字列 (string) はシングルクオート ' で囲むか、ダブルクオート " で囲んで表します。
> a = "hello, world" "hello, world" > a "hello, world"
変数 a に文字列 "hello, world" を代入しています。文字列には演算子 + を適用することができます。
> "abc" + "def" "abcdef"
演算子 + は文字列を連結した新しい文字列を作ります。
文字列には「エスケープシーケンス」を含めることができます。これは、画面に表示することができない文字を表すのに用いられる方法です。よく使われる記号に改行を表す \n とタブを表す \t があります。
> "abc\ndef" "abc def" > "abc\tdef" "abc def"
この例はタブが 8 文字に設定されている場合です。
このほかに、JavaScript には文字列を操作する便利な関数 (メソッド) があり、正規表現も利用することができます。ES2015 で導入された「テンプレート文字列 (Template literal)」を使うと変数展開を行うこともできます。
テンプレート文字列はバッククォート ( ` ) で囲まれた文字列で、${...} で変数や式を展開することができます。
> x = "foo" 'foo' > `hello, ${x}` 'hello, foo' > a = 123 123 > b = 456 456 > `a + b = ${a + b}` 'a + b = 579'
詳細はあとで説明します。
JavaScript は Perl と同様に、数が必要な演算に文字列が与えられると、文字列を数値に変換して処理を行います。数に変換できない場合は非数を表すデータ NaN (Not-a-Number) を返します。逆に、文字列が必要な演算で数が与えられると、数を文字列に変換して処理を行います。簡単な例を示しましょう。
> 1 - "2" -1 > 1 + "2" "12" > 1 + "x" "1x" > 1 - "x" NaN
最初の例は文字列 "2" を数値に変換して 1 - 2 を計算します。2 番目の例は数値 1 を文字列 "1" に変換して、"1" と "2" を連結します。最後の例は "x" を数値に変換できないので NaN (非数) を返します。
「配列 (array) 」は複数のデータを格納するデータ構造です。配列に格納されたデータを「要素」といいます。特に、要素を一列に並べたものを「1 次元配列」もしくは「ベクタ (vector) 」と呼びます。配列の要素は 0 以上の整数で指定します。これを「添字 (subscripts) 」といいます。添字はC言語と同じく 0 から始まります。配列は角カッコ '[' と ']' で囲み、要素をカンマ ( , ) で区切って表します。[ ] は要素が一つもない空の配列になります。簡単な例を示します。
> ary1 = [10, 20, 30, 40, 50] [ 10, 20, 30, 40, 50 ] > ary1[0] 10 > ary1[4] 50 > ary1[4] = 100 100 > ary1[4] 100 > ary1 [ 10, 20, 30, 40, 100 ]
C言語は配列の大きさを宣言する必要がありますが、JavaScript の配列は大きさを宣言する必要はありません。配列の大きさは JavaScript が自動的に調整してくれます。大きさを自由に変えることができる配列を「可変長配列」といいます。他のスクリプト言語、たとえば Perl, Python, Ruby でも可変長配列をサポートしています。
配列の要素には、いろいろなデータ型が混在していてもかまいません。また、要素に式を書くこともできます。
> ary2 = ["a", 0, "b", 1, "c", 2] [ "a", 0, "b", 1, "c", 2 ] > ary2[0] "a" > ary2[5] 2 > ary3 = [1 + 2, 3 * 4 - 5] [ 3, 7 ] > ary3[0] 3 > ary3[1] 7
ary2 の配列は、0, 2, 4 番目の要素が文字列で、1, 3, 5 番目の要素が整数になっています。要素に式を書くと、その式の評価結果が要素になります。
配列は入れ子にすることができます。つまり、配列の要素に配列を入れてもかまいません。これで多次元配列を表すことができます。
> ary4 = [[1,2,3],[4,5,6],[7,8,9]] [ [ 1, 2, 3 ], [ 4, 5, 6 ], [ 7, 8, 9 ] ] > ary4[0][0] 1 > ary4[1][0] 4 > ary4[2][2] 9
ary4 のように、配列の中に配列を入れることで 2 次元配列を表すことができます。ary4 の 0 番目の要素は配列 [1, 2, 3] で、その配列の 0 番目の要素は 1 です。この要素は角カッコを 2 つ使って ary4[0][0] とアクセスすることができます。最初の ary4[0] で 0 番目の配列を取り出し、その配列の 0 番目の要素を次の [0] で取り出します。
JavaScript の場合、配列は Array というオブジェクトとして定義されているので、オブジェクトを生成する演算子 new を使っても配列を生成することができます。JavaScript のオブジェクト指向については後で詳しく説明しますが、配列は new を使って次のように生成することができます。
2 の場合、配列の要素は undefined という値に初期化されます。undefined は未定義を表す JavaScript の特別な値です。このほかに、JavaScript には何もないことを表す null という特別な値もあります。
配列は [ ] を使ってアクセスするだけではありません。JavaScript には便利な関数 (メソッド) が用意されています。主な操作を下表に示します。
操作 | 意味 |
---|---|
ary.length | 配列の大きさ |
ary.reverse() | 配列を逆順に並べ替える |
ary.push(x1, x2, ...) | 最後尾に要素を追加する |
ary.pop() | 最後尾から要素を削除する |
ary.unshift(x1, x2, ...) | 先頭に要素を追加する |
ary.shift() | 先頭から要素を削除する |
ary は配列を表します。配列の大きさは ary.length で求めることができます。length は関数ではなく値を格納する変数です。これを「属性 (プロパティ)」といいといいます。配列の操作はメソッド (method) として定義されています。メソッドは次の形式で呼び出します。
object.method(args, ...)
object はデータ (Object) のことで、その後ろにドット ( . ) を付けて、メソッド名と引数を続けて書きます。メソッドはオブジェクトを操作する関数のことで、JavaScript では多くの操作がメソッドとして定義されています。オブジェクト指向機能はあとで詳しく説明します。
簡単な例を示します。
> a = [] [] > a.push(1, 2, 3) 3 > a [ 1, 2, 3 ] > a.pop() 3 > a [ 1, 2 ] > a.pop() 2 > a [ 1 ] > a.pop() 1 > a [] > a.unshift(1, 2, 3) 3 > a [ 3, 2, 1 ] > a.shift() 3 > a [ 2, 1 ] > a.shift() 2 > a [ 1 ] > a.shift() 1 > a [] > a.length 0 > a.length 0 > a = [1,2,3,4,5] [ 1, 2, 3, 4, 5 ] > a.length 5 > a.reverse() [ 5, 4, 3, 2, 1 ] > a [ 5, 4, 3, 2, 1 ]
一般に、キーと値を関連付けて格納するデータ構造を「連想配列」といいます。連想配列は多くのプログラミング言語でサポートされていて、ハッシュ (hash), 辞書 (dictonary), マップ (map) などと呼ばれています。JavaScript の場合、連想配列はオブジェクト (Object) というデータ型で代用していましたが、ES2015 では新たなコレクション (collection) として「マップ (Map)」が導入されました。
最初に Object を使う方法について簡単に説明します。ここでは便宜的に Object をハッシュと呼ぶことにします。一般に、ハッシュのキーには文字列が用いられますが、JavaScript は配列もキーに指定することができます。ハッシュは中カッコ '{' と '}' で囲み、要素をカンマで区切って表します。要素は「キー: 値」で指定します。Object の場合、キーがプロパティになります。{ } は要素が一つもない空のハッシュになります。
簡単な例を示します。
> d = {foo: 10, bar: 20} { foo: 10, bar: 20 } > d["foo"] 10 > d["bar"] 20 > d.foo 10 > d.bar 20 > d["foo"] = 100 100 > d["foo"] 100 > d.foo = 1000 1000 > d.foo 1000 > d["baz"] = 200 200 > d["baz"] 200
ハッシュのアクセスは配列と同様に角カッコ [ ] を使うか、"ハッシュ + ドット ( . ) + キー" で行います。最初にハッシュを生成して変数 d にセットします。d["foo"] または d.foo でキー foo の値を取り出したり、そこに値を代入すれば、d["foo"] の値を書き換えることができます。値を取り出すとき、キーが見つからない場合は undefined を返します。また、新しいキー baz を追加する場合は、d["baz"] や d.baz に値を代入すると、ハッシュに "baz" とその値が追加されます。
次はマップについて説明します。マップの場合、キーと値は任意のデータ型を使うことができます。
new Map([[k1, v1], [k2, v2], ...]) => Map_object
new Map() は空のマップを生成します。引数に配列が渡された場合、その要素 (キー k と値 v を組にした配列) がマップに追加されます。マップの要素数はプロパティ size に格納されています。
主なメソッドを下表に示します。
メソッド | 機能 |
---|---|
set(key, value) | マップに key と value を追加する |
get(key) | マップから key の値を求める |
delete(key) | マップから key とその値を削除する |
clear() | マップを空にする |
forEach(func) | マップの要素に関数 func を適用する |
has(key) | マップに key が含まれていれば真を返す |
[@@iterator]() | 要素を配列 [key, value] に格納して取り出すイテレータ |
entries() | 同上 |
keys() | キーを取り出すイテレータ |
values() | 値を取り出すイテレータ |
簡単な使用例を示します。
> m = new Map() Map(0) {} > m.set("foo", 10) Map(1) { 'foo' => 10 } > m.set("bar", 20) Map(2) { 'foo' => 10, 'bar' => 20 } > m.set("baz", 30) Map(3) { 'foo' => 10, 'bar' => 20, 'baz' => 30 } > m.get("foo") 10 > m.get("oops") undefined > m.has("bar") true > m.delete("bar") true > m.has("bar") false > m.size 2
ほかのメソッドの使い方はあとで説明します。
「セット (Set)」は ES2015 で導入された「集合」を表すコレクションです。集合は要素の順番に意味はありません。たとえば集合 {1, 3, 5, 7} は {7, 5, 3, 1} や {5, 3, 1, 7} と表すこともできます。このように、要素は適当に並べてもかまわないのですが、ある規則で要素を整列させておくと便利な場合もあります。
Set は任意のデータ型を格納することができますが、同じ値のデータは一つだけしか格納できません。
new Set([item1, item2, ...]) => Set_object
new Set() は空のセットを生成します。引数に配列が渡された場合、その要素がセットに追加されます。セットの要素数はプロパティ size に格納されています。
主なメソッドを下表に示します。
メソッド | 機能 |
---|---|
add(item) | セットに item を追加する |
clear() | セットを空にする |
delete(item) | セットから item を削除する |
forEach(func) | セットの要素に関数 func を適用する |
has(item) | セットに item が含まれていれば真を返す |
[@@iterator]() | 要素を順番に取り出すイテレータ |
keys() | 同上 |
values() | 同上 |
entries() | 要素を配列 [item, item] に格納して取り出すイテレータ |
簡単な使用例を示します。
> s = new Set() Set(0) {} > s.add(3) Set(1) { 3 } > s.add(2) Set(2) { 3, 2 } > s.add(1) Set(3) { 3, 2, 1 } > s.has(2) true > s.has(4) false > s.size 3 > s.delete(2) true > s.has(2) false > s Set(2) { 3, 1 } > s.add(1) Set(2) { 3, 1 } > s.add(2) Set(3) { 3, 1, 2 }
ほかのメソッドの使い方はあとで説明します。
シンボル (Symbol) は ES2015 で追加された新しいデータ型で、プロパティなどの識別子として使用することができます。シンボルは関数 Symbol() で生成します。
Symbol("名前")
引数の名前は省略することができます。Symbol() はユニークなシンボル型のデータを返します。引数の名前が同じでも異なるシンボルが生成されます。
> Symbol() Symbol() > foo = Symbol("foo") Symbol(foo) > foo Symbol(foo) > foo === Symbol("foo") false > Symbol("foo") === Symbol("foo") false > foo === foo true
グローバルな環境でシンボルを共有したい場合は Symbol.for() を使います。
Symbol.for(string)
Symbol.for() は名前が string のシンボルを生成します。既に同じ名前のシンボルが生成されていれば、そのシンボルを返します [*2]。
> bar = Symbol.for("bar") Symbol(bar) > bar Symbol(bar) > bar === Symbol.for("bar") true
グローバルな環境にシンボルが登録されているかチェックする関数が Symbol.keyFor() です。
Symbol.keyFor(symbol)
> Symbol.keyFor(foo) undefined > Symbol.keyFor(bar) 'bar'
見つからない場合は undefined を、見つけた場合はシンボルの名前を文字列で返します。
シンボルをオブジェクトのプロパティとして使用するときは、object[symbol] や {[symbol]: 123} のように角カッコを使ってください。object.name は object["name"] と同じなので、この構文でシンボルを使うことはできません。
> foo = Symbol("foo") Symbol(foo) > obj = {} {} > obj[foo] = 123 123 > obj[foo] 123 > obj { [Symbol(foo)]: 123 } > obj.foo = 1.2345 1.2345 > obj.foo 1.2345 > obj["foo"] 1.2345 > obj { foo: 1.2345, [Symbol(foo)]: 123 } > bar = Symbol("bar") Symbol(bar) > obj1 = {[foo]: 10, [bar]: 20} { [Symbol(foo)]: 10, [Symbol(bar)]: 20 } > obj1[foo] 10 > obj1[bar] 20 > for (var x in obj) { console.log(x); console.log(obj[x]); } foo 1.2345 undefined
console.log() はデータをコンソールに出力する関数です。これはブラウザの JavaScript (または Node.js) の組み込み関数です。プロパティがシンボル (Symbol プロパティ) の場合、for ... in 文でそれを取り出すことはできません。for ... in 文はあとで説明します。Symbol プロパティを求めるには Object.getOwnPropertySymbols() を使います。
> Object.getOwnPropertySymbols(obj1) [ Symbol(foo), Symbol(bar) ]
ES2015 でサポートされた「分割代入 (Destructuring assignment)」は、配列またはオブジェクトからデータを取り出して別々の変数に代入するための構文です。JavaScript | MDN では分割と訳していますが、Lisp 好きなユーザならば「分配」のほうがしっくりくるかもしれません。
配列の分割代入は代入演算子 = の左辺式に角カッコを使い、その中に変数を指定します。
[変数1, 変数2, ..., 変数N] = [値1, 値2, ..., 値N];
配列の分割代入は他のスクリプト言語、たとえば Ruby の多重代入とよく似ています。簡単な例を示しましょう。
> [a, b] = [100, 200] [ 100, 200 ] > a 100 > b 200 > [a, b] = [b, a] [ 200, 100 ] > a 200 > b 100
変数 a と b に 100 と 200 を代入しています。a と b の値を交換することも分割代入を使えば簡単に行うことができます。
> [a, b] = [1, 2, 3] [ 1, 2, 3 ] > a 1 > b 2 > [a, b] = [10] [ 10 ] > a 10 > b undefined
右辺の要素数が代入先よりも多い場合、残りの要素は無視されます。最初の例では、a と b に 1 と 2 が代入されますが、要素 3 は捨てられます。逆に、代入先の変数が多い場合、残った変数には undefined がセットされます。[a, b] = [10] の場合、a には 10 が代入されて、b の値は undefined になります。
分割代入はデフォルト値を設定することができます。
> [a, b = 123] = [100] [ 100 ] > a 100 > b 123 > [a, b = 123] = [100, 200] [ 100, 200 ] > a 100 > b 200
分割代入したとき、値が undefined になった変数でデフォルト値が設定される場合は、その値が使用されます。
配列の分割代入はスプレッド演算子 (...) を使用することができます。最後の変数の前に ... を付けると、変数に入りきらない値は、配列に格納されて最後の引数に渡されます。
> [x, y, ...z] = [1, 2, 3, 4, 5] [ 1, 2, 3, 4, 5 ] > x 1 > y 2 > z [ 3, 4, 5 ] > [x, y, ...z] = [1, [2, 3, 4, 5]] [ 1, [ 2, 3, 4, 5 ] ] > x 1 > y [ 2, 3, 4, 5 ] > z [] > [x, y, ...z] = [1, ...[2, 3, 4, 5]] [ 1, 2, 3, 4, 5 ] > x 1 > y 2 > z [ 3, 4, 5 ]
配列の前にスプレッド演算子を付けると、配列を展開することができます。また、角カッコは入れ子になってもかまいません。この機能は関数型言語のパターンマッチングや Common Lisp の defmacro にある機能「分配 (destructuring)」とよく似ています。
> [x, [y, z]] = [1, [2, 3, 4, 5]] [ 1, [ 2, 3, 4, 5 ] ] > x 1 > y 2 > z 3 > [x, [y, ...z]] = [1, [2, 3, 4, 5]] [ 1, [ 2, 3, 4, 5 ] ] > x 1 > y 2 > z [ 3, 4, 5 ]
右辺と左辺で配列の構造が合わないとエラーになります。
> [x, [y, ...z]] = [1, 2, [3, 4, 5]] Uncaught: TypeError: number 2 is not iterable (cannot read property Symbol(Symbol.iterator))
オブジェクトの分割代入は代入演算子 = の左辺式に { ... } を使い、その中に変数を指定します。
{名前1, 名前2, ..., 名前N} = {名前1: 値1, 名前2: 値2, ..., 名前N: 値N};
オブジェクトの分割代入では、オブジェクトのプロパティ名と同じ名前の変数にプロパティの値がセットされます。
簡単な実行例を示します。
> {a, b} = {a: 10, b: 20} { a: 10, b: 20 } > a 10 > b 20 > {b, a} = {a: 100, b: 200} { a: 100, b: 200 } > a 100 > b 200
オブジェクトの分割代入でもデフォルト値を指定することができます。
> {a, b} = {a: 1000} { a: 1000 } > a 1000 > b undefined > {a, b = 2000} = {a: 1000} { a: 1000 } > a 1000 > b 2000
プロパティ名とは異なる変数に値を代入することもできます。その場合は左辺式でプロパティの値に代入先の変数名を指定します。
> {a: x, b: y} = {a: 1234, b: 5678} { a: 1234, b: 5678 } > x 1234 > y 5678
それから、オブジェクトの分割代入は入れ子にすることができ、配列の分割代入と混在してもかまいません。詳細は JavaScript | MDN の「分割代入」をお読みくださいませ。
JavaScript の制御構造を簡単に説明します。
条件分岐には if を使います。JavaScript の if はC言語とほぼ同じですが、真偽の判定が少し異なります。JavaScript の場合、真偽を表すデータ型 (boolean) として true と false が用意されていますが、数値や文字列など他のデータ型でも真偽を判定することができます。
まず、数値 0 (0.0) は偽を表し、それ以外の数値は真となります。文字列の場合、空文字列 "" は偽を表し、それ以外の文字列は真と判断されます。それから、特別な値 null と undefined は偽を表し、それ以外のデータは真と判断されます。
if の構文を示します。
if (test) { 処理A1; ...; 処理AZ; } else { 処理B1; ...; 処理BZ; } if (test_A) { 処理A; } else if (test_B) { 処理B; } else { 処理C; }
条件部 test を実行し、その結果が真であれば、処理A1 から処理AZ を実行します。{ } で囲まれた部分を「ブロック」と呼び、ここに複数の処理を書くことができます。ブロックは最後に実行した処理結果を返します。
test の結果が偽であれば、else から始まるブロックで書かれている処理B1 から処理BZ を実行します。else ブロックは省略することができます。なお、ブロック内の処理 (文) が一つしかない場合は { } を省略することができます。これはC言語と同じです。
また、else if を使うことで、if を連結することができます。test_A が偽の場合は、次の else if の条件 test_B を実行します。この結果が真であれば処理B を実行します。そうでなければ、else ブロックの処理C を実行します。else if はいくつでも繋げることができます。
このほかにも、JavaScript にはC言語と同様の switch 文があります。
JavaScript には下表に示す比較演算子が用意されています。
演算子 | 意味 | 演算子 | 意味 |
---|---|---|---|
== | 等しい | === | 等しい (型変換なし) |
!= | 等しくない | !== | 等しくない (型変換なし) |
< | より小さい | > | より大きい |
<= | より小さいか等しい | >= | より大きいか等しい |
C言語の比較演算子と同じですが、JavaScript は数値だけではなく、文字列の比較にも用いることができます。数値と文字列を比較することもできますが、その場合は文字列が数値に型変換されます。演算子 === と !== は型変換を行わないでデータを比較します。データ型が異なる場合は異なる値と判断されます。
簡単な例を示しましょう。
> 1 < 2 true > 1 > 2 false > 1 == 1 true > 1 != 1 false > 1 == "1" true > 1 === "1" false > "abc" < "def" true > "abc" > "def" false > "abc" == "abc" true > "abc" != "abc" false
JavaScript には下表に示す論理演算子があります。
操作 | 意味 |
---|---|
!x | x の否定(真偽の反転) |
x && y | x が真かつ y が真ならば真 |
x || y | x が真または y が真ならば真 |
これもC言語と同じです。簡単な使用例を示しましょう。
> !true false > !false true > true && true true > true && false false > true || true true > true || false true > false || true true > false || false false
論理積 && は、左項が偽ならば右項を評価せずに偽を返します。論理和 || は、左項が真ならば右項を評価せずに真を返します。
繰り返しは同じ処理を何度も実行することです。JavaScript の繰り返しはC言語とほぼ同じです。まずは簡単な繰り返しから紹介しましょう。while 文は test が真であるあいだ、ブロック内の処理を繰り返し実行します。
while (test) { 処理A; 処理B; ...; 処理Z }
簡単な例を示しましょう。hello, world を 10 回表示します。
> n = 0 0 > while (n < 10) {console.log("hello, world"); n += 1} hello, world hello, world hello, world hello, world hello, world hello, world hello, world hello, world hello, world hello, world 10
変数 n を 0 に初期化し、n の値が 10 よりも小さいあいだ処理を繰り返します。C言語と同様に、n += i は n = n + i と同じ意味です。このほかに、-=, *=, /= も使うことができます。n の値はブロックを実行するたびに +1 されていくので、n が 10 になった時点で繰り返しを終了します。
このほかに、C言語と同様の do - while 文もあります。
do { 処理A; ...; 処理Z } while (test)
while 文と違って、do - while 文は最初にブロック内の処理を実行します。簡単な例を示します。
> n = 0 0 > do { console.log("hello, world"); ++n} while(n < 10) hello, world hello, world hello, world hello, world hello, world hello, world hello, world hello, world hello, world hello, world 10
++n の ++ はインクリメント演算子といって変数の値を +1 します。つまり、n += 1 と同じ処理です。Cプログラマにはお馴染みの処理ですね。実は n++ のように、++ を変数の後ろにつけることもできます。前と後ろでは +1 することは変わりませんが、処理結果が異なるところがあります。これは、それが問題になるところで説明しましょう。値を -1 するデクリメント演算子 -- も使えます。
次は for 文を説明します。JavaScript の for 文はC言語とほぼ同じです。
for (初期化; 条件部; 更新処理) { 処理A; ...; 処理Z; }
↓ ┌─────┐ │ 初期化 │ └─────┘ ├←─────┐ false ┌─────┐ │ ┌───│ 条件部 │ │ │ └─────┘ │ │ ↓true │ │ ┌─────┐ │ │ │ 処理A │ │ │ └─────┘ │ │ ・ │ │ ・ │ │ ┌─────┐ │ │ │ 処理Z │ │ │ └─────┘ │ │ ↓ │ │ ┌─────┐ │ │ │ 更新処理 │ │ │ └─────┘ │ │ └──────┘ └──────┐ ↓ 図 : for の処理
for 文の特徴は、いちばん最初に行われる初期化と、繰り返すたびに行われる更新処理があることです。上図を見ればおわかりのように、初期化はただ一度しか行われず、更新処理はブロックの処理を実行してから行われます。簡単な使用例を示しましょう。
> for (n = 0; n < 10; n++) console.log("hello, world") hello, world hello, world hello, world hello, world hello, world hello, world hello, world hello, world hello, world hello, world undefined
まず最初に、n = 0 で変数 n を 0 に初期化します。この処理は for が始まるときに一度だけ実行されます。次に条件部 n < 10 がチェックされます。n は 0 ですから条件を満たしますね。そこで、ブロックの処理が行われ、console.log() で hello, world を画面に表示します。
更新処理で変数 n の値を +1 したら、条件部のチェックを行います。あとは、while と同様に条件部が成立しているあいだは、ブロックの処理と更新処理を繰り返します。結局、n の値は 10 になるので、条件部が不成立となり繰り返しを終了します。したがって、このプログラムを実行すると hello, world が 10 回表示されます。
オブジェクトのプロパティは for ... in 文で求めることができます。
for (変数 in オブジェクト) { 処理A; ...; 処理Z; }
for ... in 文はオブジェクトから順番にプロパティを取り出して変数に代入し、ブロックに書かれている処理を繰り返し実行します。for と in の間にプロパティを格納する変数を指定し、in の後ろにオブジェクトを指定します。簡単な例を示します。
> d = {foo: 10, bar: 20, baz: 30} {foo: 10, bar: 20, baz: 30} > for (x in d) {console.log(x + " " + d[x]); } foo 10 bar 20 baz 30 undefined
for ... in 文に配列を渡すと、添字を順番に取り出すことができます。
> ary = [10,20,30] [ 10, 20, 30 ] > for (i in ary) console.log(i) 0 1 2 undefined > for (i in ary) console.log(ary[i]) 10 20 30 undefined
配列から取り出される添字はプロパティなので、データ型は文字列になります。数値ではないので注意してください。なお、for ... in 文で取得できないプロパティもあります。たとえば、配列の大きさはプロパティ length に格納されていますが、for ... in 文のオブジェクトに配列を指定しても length を取り出すことはできません。
配列、マップ、セットの要素は for ... of 文を使って順番にアクセスすることができます。
for (変数 of iterable) { 処理A; ...; 処理Z; }
for ... of 文は ES2015 からサポートされた「イテレータ (iterator)」という機能を使っています。イテレータはコレクションの要素を順番にアクセスするための機能です。イテレータが使えるオブジェクトを iterable object といい、イテレータと合わせて iterable といいます。コレクションにはイテレータを返すメソッドがあるので、その返り値を for ... of 文に渡すこともできます。
簡単な例を示しましょう。
> ary = [10, 20, 30] [ 10, 20, 30 ] > for (x of ary) console.log(x) 10 20 30 undefined > m = new Map([["foo", 10], ["bar", 20], ["baz", 30]]) Map(3) { 'foo' => 10, 'bar' => 20, 'baz' => 30 } > for (x of m) console.log(x) [ 'foo', 10 ] [ 'bar', 20 ] [ 'baz', 30 ] undefined > for (k of m.keys()) console.log(k) foo bar baz undefined > for (k of m.values()) console.log(k) 10 20 30 undefined > s = new Set([1, 2, 3, 4, 5]) Set(5) { 1, 2, 3, 4, 5 } > for (x of s) console.log(x) 1 2 3 4 5 undefined
イテレータについてはあとで詳しく説明します。
while 文や for 文は break 文によって繰り返しを脱出することができます。contiune 文は繰り返しの先頭に戻ります。これはC言語と同じ動作です。break 文と continue 文の動作を下図に示します。
# # break と continue # while (test_a) {←─────┐ 処理A; │ if (test_b) continue; ─┘ 処理B; if (test_c) break; ──┐ 処理C; │ } │ ←────────────┘ 処理D; 図 : break 文と continue 文の動作
test_b が真で continue 文が実行されると、それ以降の処理を実行せずに条件部のチェックが行われます。つまり、処理 B, test_c, 処理 C は実行されません。for 文で continue 文が実行されると、それ以降の処理は実行されずに更新処理と条件部のチェックが行われます。
test_c が真で break 文が実行されると、それ以降の処理を実行せずに while 文や for 文の繰り返しを脱出します。上図では、break 文で while 文の繰り返しを脱出すると、while 文の次の処理 D が実行されます。
最後に簡単な例題として、素数を求めるプログラムを作ってみましょう。いちばん簡単な方法は、奇数 3, 5, 7, 9, ...をそれまでに見つかった素数で割ってみることです。見つけた素数は配列に格納しておけばいいでしょう。プログラムは次のようになります。
リスト : 素数を求める (1) prime_list = [2]; for (x = 3; x <= 100; x += 2) { a = true; for (y = 0; y < prime_list.length; y++) { z = prime_list[y]; if (x % z == 0) { a = false; break; } } if (a) prime_list.push(x); } console.log(prime_list);
変数 prime_list は素数の配列で [2] に初期化します。奇数の生成は for 文を使うと簡単です。変数 x を 3 に初期化し、更新処理で +2 していけばいいわけです。次の for 文で prime_list から素数を取り出して変数 z にセットします。
ここで変数 a の使い方がポイントになります。最初に a を true に初期化しておきます。x % z が 0 ならば x は素数ではないので、a を false に書き換えてから break します。そして、for ループが終了した後、変数 a をチェックして true であれば x を push() で prime_list に追加します。最後 に console.log() で prime_list を表示します。
それでは実行してみましょう。プログラムをファイル prime.js に保存して、Node.js の REPL で load してください。
> .load prime.js ... 略 ... [ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97 ] undefined
100 以下の素数は全部で 25 個あります。
ところで、この方法には無駄があります。x が素数か判別するため、x より小さい素数で割り切れるか調べていますが、実は√x より小さい素数を調べるだけでいいのです。次のリストを見てください。
リスト : 素数を求める (2) prime_list = [2]; for (x = 3; x <= 100; x += 2) { a = true; for (y = 0; y < prime_list.length; y++) { z = prime_list[y] if (z * z > x) break; if (x % z == 0) { a = false; break; } } if (a) prime_list.push(x); } console.log(prime_list);
z > √x のかわりに z * z > x をチェックし、真であれば break で for 文を脱出します。これでプログラム (1) よりも高速に素数を求めることができます。
ところで、これらのプログラムはちょっとわかりにくいですね。この場合、関数を使うとわかりやすいプログラムを作ることができます。関数は次回で詳しく説明します。