# 速習JavaScript ## 簡単な歴史 javascriptは1995年に開発された言語です。主要なブラウザすべてで動作するため、インターネットバンキングなどウェブアプリケーションの開発に使用されています。 当初は書き方に癖のある言語でしたが、2015年の仕様追加であるES2015により、理解しやすく使いやすい言語に生まれ変わっています。 ## サマリの目的 本サマリでは、受講者が教材用WEBアプリケーション「moneta3」のソースコードを読み、修正できる最小限の知識を身につけるよう、解説を行います。 前提として、JavaやC言語を学んだことがある人を対象にしています。また、厳密な仕様をお伝えするより、サンプルコードから「書き方」「読み方」を理解してもらうことを中心にしています。 ## Node.js javascriptはブラウザで実行することができますが、ターミナルからNode.jsで実行することもでき、学習に便利です。 ターミナルを起動して実行してみましょう。 ```bash= $ node > x = 10 10 > y = 20 20 > x+y 30 > x+z ReferenceError: z is not defined > 10/2 5 > 10/3 3.3333333335 > 10/0 Infinity > 0/0/0 NaN ``` 式や文を1行入力するごとに、結果を評価して出力しています。エラーもその場で確認できるため、効率よく学習をすすめることができます。 ## visual studio code 実行して確認するための仕組みを準備しています。 ターミナルから、以下のコマンドを入力しましょう。 ``` $ rails watch ``` この状態で、`code/javascript/00_練習用.js`を開きましょう。以下のプログラムが表示されます。 ```javascript= const { stdout } = process; for (let i = 1; i <= 10; i++) { stdout.write(`${i} little `); if (i % 3 === 0) { console.log('indians.'); } } console.log('indian boys.'); } ``` ctrl+sで保存します。先ほどの`rails watch`により、保存時にプログラムが実行されるようになっています。JavaやC言語を学んだことがあれば、なんとなく意味が分かるのではないでしょうか。プログラムと結果を見比べて確認してみて下さい。 ``` 1 little 2 little 3 little indians. 4 little 5 little 6 little indians. 7 little 8 little 9 little indians. 10 little indian boys. ``` # 変数・定数 さて、javascriptを動かして確認する方法について2つ紹介できましたので、いよいよ構成要素について説明していきます。これからの説明内容については、2つの方法を自由に使い分けながら、いつでも手元で確認して構いません。 ## 変数と定数 プログラム言語に追いて、データの入れ物を変数や定数と呼ぶことはご存知かと思います。javascriptでは、定数の宣言にはconstキーワードを使用します。 ```= $ node > const x = 10 undefined > x = 20 TypeError: Assignment to constant variable > const a = [] undefined > a.push(1) 1 > a.push(2) 2 > a [ 1, 2 ] ``` constで宣言した変数には再代入ができず、4行目はエラーになります。一方aは配列(後述)として宣言されており、8行目や10行目はa自体に再代入をしているわけではないので、エラーになりません。 どうしても再代入が必要な場合は、letキーワードを使用します。ただし、ループの添字などを除き、必要とする場面は多くありませんし、バグの少ないプログラムのためには、再代入は避けるべきです。 ```= $ node > let y = 10 undefined > y = 'abc' 'abc' ``` ## 識別子 変数名や定数名にはunicodeの文字、「$」、「_」が使用できます。日本語全角の変数名も誤りではありませんが、通常はアルファベットの大文字・小文字を使用します。 ```= $ node > let _x = 10 undefined > _x 10 > let $$$$ = 4 undefined > $$$$ 4 ``` ## キャメルケース・スネークケース newUserAccountなど、単語の区切りを大文字で表す書き方は、大文字がラクダの背中のコブに見えることから、キャメルメースと呼ばれます。 また、new_user_accountなど、単語の区切りにアンダーバーを使用したものは(それほど一般的ではありませんが)スネークケースと呼びます。 研修で使用する「moneta」については、キャメルケースを使用します。 # データ型 ## プリミティブ型 javascriptでは値は「プリミティブ」であるか「オブジェクト」であるかのいずれかです。プリミティブ(文字列や数字)は「不変(immutable)」です。 プリミティブを表すデータ型には次の6種類があります。 - 数値(number) - 文字列(string) - 論理値(boolean) - null - undefined - シンボル(symbol) typeof演算子を使用して型を確認できます。 ```= $ node > typeof 42 'number' > typeof 'abc' 'string' > typeof true 'boolean' > typeof null 'object' > typeof undefined 'undefined' > typeof Symbol() 'symbol' ``` nullについては歴史的経緯がありこのようになっています。興味がある人は以下リンクを参照。 https://web.archive.org/web/20160331031419/http://wiki.ecmascript.org:80/doku.php?id=harmony:typeof_null ## オブジェクト 上述の6種のプリミティブ型以外に「オブジェクト」型が利用できます。以下のようなオブジェクトが組込で用意されていますが、ゼロから自分で設定することもできます。 - 配列(Array) - 日付(Date) - 正規表現(Regexp) - マップ(Map) - セット(Set) # リテラル 既に説明なしでいくつもの「リテラル」を使用しています。constキーワードで初期値を宣言するときの、=の右側はリテラルです。javascriptの処理系は、プログラム中にリテラルが現れると、記載方法に応じて値に変更します。 ## 数値リテラル javascriptは数値を表すデータ型を一つしか持ちません。内部では全て64ビットの倍精度浮動小数点数として保持します。(C言語はint, long, floart, doubleを別のデータ型として持ちます) ですが、プログラムコード上の表現(リテラル)としては、10進数、2進数、8進数、16進数の4種類を持ち、また、10進数のリテラルでは、整数、少数、および10を底とした指数表現で表すことができます。 また、特殊な値としてInfinity(無限大)、-Infinity(無限小)、NaN(Not a number)があります。(厳密にはリテラルではありませんが、計算の結果としてこの値になることがあり、ここで取り上げます) ```= $ node > 10 10 > 0xff 255 > 0o77 63 > 0b111 7 > 0.1+0.2 0.30000000000000004 > 1.3e100 1.3e+100 > 1/0 Infinity > -1/0 -Infinity > (1/0)+(-1/0) NaN > 1 + undefined NaN > typeof NaN 'number' ``` > 20行〜23行の通り、他のプログラム言語では例外となるような場所で処理が継続するので、見つかりにくいバグの原因になります。 > ## 文字列リテラル javascriptでは文字列はUnicodeのテキストを表します。文字列リテラルは、引用符(ダブルクォート「”」か、シングルクォート「’」)で囲んだものです。どちらを使用しても違いはありませんが、「moneta」ではツールによりシングルクォート「’」に統一しています。 またバッククォート「`」を使用すると、文字列中に値を埋め込むことが出来ます。 ```= $ node > 'abc' 'abc > "abc" 'abc' > `ab${1+2}c` 'ab3c' ``` ## 論理値リテラル 論理値(boolean)は、trueとfalseのいずれかの値を取ります。リテラルはそのまま、true/falseになります。 「!」記号は後ほど述べるように論理否定演算子です。 ```= $ node > true true > false false > !false true > !true false ``` ## nullとundefined javascriptにはnullとundefinedという2つの特殊な型があります。nullが取る値はnull一つのみで、undefinedが取る値もundefinedのみです。 例: 通帳を表すデータを作成した。口座番号はデータ作成時点では付与されておらず、本人確認終了後に採番号される。そのため、データ作為時の口座番号の値をnullにした。 一方、通帳データから存在しない属性(硬さなど)を読み取ろうとした場合、undefinedになる。 ## オブジェクトリテラル オブジェクトは複数の値(プロパティ)を持つデータです。プロパティは名前と値からなり、名前のことをキーと呼ぶ場合があります。キーとしては文字列からシンボルを使用することができ、値は任意のデータ型(他のオブジェクトを含む)を使えます。 通帳(book)オブジェクトに口座番号(名前: account, 値: null)を追加してみましょう。 プロパティにアクセスするには、「.」ドット演算子を使用します。また、プロパティ名に変数を使いたい場合などには、[](ブラケット演算子)を使用します。 ```= $ node > const book = {} {} > book.account = null null > book { account: null } > book.account null > book['account'] null > book.hardness undefined > Object.keys(book) [ 'account' ] ``` ## 配列リテラル 特別なオブジェクトとして配列(Array)が利用できます。データには順序があり、キーは0から始まる連続した整数になっています。配列リテラルは要素を「[]」で囲んで表現します。要素の間は「,」カンマで区切ります。 ```= $ node > [1,2,3,4].length 4 > [1,2,3,4].reverse() [4,3,2,1] ``` 配列のメンバには、数字をキーとしてアクセスできます。 ```= $ node > let a = [10,20,30] undefined > a[1] 20 > a[1] = 70 70 > a[1] 70 ``` ## スプレッド構文 オブジェクトリテラル、配列リテラルと合わせて、スプレッド構文(...)を使用することで、要素をバラバラにして他の配列やオブジェクトに混ぜ込むことができます。 ``` const a = [1, 2]; const b = [3, 4]; const c = [a, b, 5]; const d = [...a, ...b, 5]; console.log(c); console.log(d); // output [ [ 1, 2 ], [ 3, 4 ], 5 ] [ 1, 2, 3, 4, 5 ] ``` この後、コードサンプルに`// output`とあったら、その後がプログラムの実行結果であることを示します。 オブジェクトでキーが重複した場合、後から指定したほうが優先されます。 ```javascript= const a = { dog: 1, cat: 2}; const b = { cat: 3, rat: 4}; console.log({...a, ...b}); // output { dog: 1, cat: 3, rat: 4 } ``` ## 分割代入 配列やオブジェクトの一部の値を利用する場合、分割代入構文を利用することができます。 ```javascript= const [,b] = [1,2,3,4,5]; console.log(b); // output 2 ``` 後述する関数の引数として使用することもできます。スプレッド演算子を併用することにより、残りの引数を纏めて受け取ることもできます。 ```javascript= function partial([a, b, ...rest]) { console.log(`a:${a}`); console.log(`b:${b}`); console.log(`rest:${rest}`); } partial([1,2,3,4,5]); // output a:1 b:2 rest:3,4,5 ``` オブジェクトについても利用できます ```javascript= const { cat, ...rest } = { dog: 1, cat: 2, rat: 3 }; console.log(cat); console.log(rest); // output 2 { dog: 1, rat: 3 } ``` ## 省略記法(ショートハンド) オブジェクトリテラルについて、変数名とプロパティ名が同一である場合、省略記法が使用できます。 ```javascript= const dog = 'DOG'; const cat = 'CAT'; // const animal = { dog: dog, cat: cat } と同じ const animal = { dog, cat }; console.log(animal); // output { dog: 'DOG', cat: 'CAT' } ``` # データの型変換 ## 数値への変換 文字列から数字への変換は非常によく行われます。方法はいくつかありますが、Numberオブジェクトのコンストラクタを利用した方法や、組み込みのparseInt関数を利用した方法があります。 ```= $ node > Number('2') + 5 7 > parseInt('2', 10) + 5 7 > Number('2km') + 5 NaN > parseInt('2km', 10) + 5 7 ``` parseIntの2つめのパラメータは基数で、10進数であることを示します。最近の仕様ではデフォルトが10進数ですが、若干古い仕様ではparseInt('010')が8進数と誤認され、8に変換される場合がありますので、省略せずに指定したほうが良いでしょう。 # 数値への自動変換 整数や小数を引用符で囲むと、数値ではなく文字列になります。しかし、数字を含む文字列は、自動変換される場合があります。 ```= $ node > 10 * "20" 200 > 10 + "20" "1020" ``` > 5行目の結果が思わぬバグのもとになることがあります ## 論理値への変換 演算子(!)は論理否定を表します。対象がboolean値でない場合、「真とみなされる(thruthy)値」と「偽とみなされる(falsy)値」により変換されます。 そのため、演算子(!)を2回使って、自身の対応する論理値に変換できるので、プログラム中の省略記法として良く使用されます。 ```javascript= value !== "" && value !== undefined && value !== null //上と同じ !!value ``` # 演算子 ## 四則演算 以下の算術演算子を利用します。 |演算子|概要|例| |:--|:--|:--| |+|加算|4 + 6 -> 10| |-|減算|10 - 5 -> 5| |*|乗算|6 * 5 -> 30| |/|除算|15 / 5 -> 3| |%|剰余|10 % 3 -> 1| |**|累乗|2 ** 3 -> 8| ## インクリメント演算子、デクリメント演算子 変数の値を1増減させます。constではなくletで宣言されている必要があります。 ```= $ node > let i = 0 undefined > i++ 0 > i 1 > ++i 2 ``` 5行目が0になっているのは、後置きの++は結果を返してから値を増加させるためです。9行目が2になっているのは、値を増加させてから結果を返しているためです。 ```= $ node > let i = 0 > j = i++ 0 > k = --i 0 ``` 代入文で使用する場合など、注意が必要です。 ## 複合代入演算子 変数xの値を、変数yの値だけ増加させる式は、`x = x + y`と記述できますが、よく使う書き方であるため専用の記述方法が用意されています。 |演算子|概要| |:--|:--| |+=|加算した結果を左辺に代入| |-=|減算〃| |*=|乗算〃| |/=|除算〃| |%=|剰余〃| |<<=|左シフト〃| |>>=|右シフト〃| |>>>=|右シフト〃| ## 比較演算子 比較演算子は左辺と右辺を比較し、その結果を真偽値(true/false)として返します。 javascriptでは、一致について`===` `==` 不一致について`!==` `!=`と複数の書き方ができますが、`==` `!=`については想定外の挙動(暗黙の型変換)によりバグの温床になりやすく、使ってはいけません。 ``` $ node > "" == 0 true > "" === 0 false > "" == [] true > "" === [] false ``` |演算子|trueになる場合|例| |:--|:--|:--| |===|両辺が等しい|1 === 1 -> true| |!==|両辺が等しくない|1 !== 2 -> true| |<|左辺が右辺より小さい|1 < 2 -> true| |<=|左辺が右辺と同じか小さい|1 <= 1 -> true| |>|左辺が右辺より大きい|2 > 1 -> true| |>=|左辺が右辺より大きいか等しい|2 >= 2 -> true| ## 条件演算子(三項演算子) `[条件] ? [trueの値] : [falseの値]`の形式で、条件式により値を切り替えることができます。 ```javascript= let x = 10; console.log(x < 5 ? 'a' : 'b') x = 1; console.log(x < 5 ? 'a' : 'b') // output b a ``` ## 論理演算子 真偽値に対して、論理和、論理積、否定など、論理演算を行うことができます。 |演算子|trueになる場合|例| |:--|:--|:--| |&&|両辺がともにtrue|true && false -> false| |\|\||両辺のどちらかがtrue|false || true -> true| |!|対象がfalse|!false -> true| ## 論理演算子の短絡評価(ショートカット演算) 式1 && 式2 && .... のような非常に長い式があったとして、途中で一つでもfalseの結果があれば残りを計算する必要はありません。 また、 式1 || 式2 || .... の場合、一つでもtrueの結果があれば、残りを計算する必要はありません。 このように、`&&` `||`演算子については、左式だけが評価されて、右式が評価されない場合があり、条件により動作を変更する処理として使用される場合があります。 ```javascript= let x = null; let y; if (x !== null && x !== undefined) { y = x; } else { y = 10; } console.log(y); //これを以下のように記載できます y = x || 10; console.log(y); ``` &&の処理打ち切りを利用した例 ```javascript= // xがメンバaを持たないとき例外で異常終了する let x = null; let y = x.a; console.log(y); // 異常終了せず、y=xになる let x = null; let y = x && x.a; console.log(y); ``` ただし、数字のゼロや空文字列がfalsyであることにより、想定外の動作になることがあります。 ```javascript= let x = 0; let y = x || 10; // xが数字以外のnullやundefinedなら10にしたい console.log(y); // 結果は0のはず・・? ``` 逆にこれを利用して以下のようなエラーチェックを行うことができます。 ```javascript= let value = ''; let msg = !!value || 'error'; console.log(msg); value = null; msg = !!value || 'error'; console.log(msg); value = undefined; msg = !!value || 'error'; console.log(msg); value = 'abc'; msg = !!value || 'error'; console.log(msg); // output error error error true ``` # 制御フロー ## if文 基本的な書き方は以下の通りです。else if、else句は不要である場合省略できます。 ```javascript= let x = 180; if (x < 0) { x = 0; } else if (x < 100) { x = 100; } else { x = 200; } console.log(x); // output 200 ``` 中括弧({})も省略可能ですが、将来のプログラム修正で思わぬバグのもとになるため、省略すべきではありません。 ```javascript= let x = 200; if (x < 100) x = 200; x = 0; console.log(x); // output 0 ``` インデントにごまかされそうになりますが、5行目は必ず実行されます。 ## switch文 先程のif文での例のように、一つの変数に対する異なる複数の条件で振り分けを行う場合、switch命令が適している場合があります。 switch命令では、まず先頭の式を評価し、その値に合致するcase句を先頭から探します。一致した場合、「:」以降を、break文に到達するまで実行します。 ```javascript= let x = 3; switch (x) { case 1: case 2: console.log('A'); break; case 3: case 4: console.log('B'); break; default: console.log('ERR'); } // output B ``` 11行目のbreakを忘れると、以下のようになります。 ```javascript= let x = 3; switch (x) { case 1: case 2: console.log('A'); break; case 3: case 4: console.log('B'); default: console.log('ERR'); } // output B ERR ``` ## while, do~whileによる繰り返し 条件式がtrueの間だけ、ブロック「{}」の内容を繰り返し処理します。 ```javascript== let i = 3; while (i-- > 0) { console.log(i); } // output 2 1 0 ``` ```javascript== let i = 0; do { console.log(i++); } while(i < 3); // output 0 1 2 ``` 終了条件を間違えると無限ループとなりフリーズの原因になりますので、注意しましょう。 ## forによる繰り返し(配列) 配列に対する繰り返し処理で、インデックスの値が必要なときには、for文を利用します。C言語を学んだことがあるなら、同じ記述方法になります。 ``` for(初期化式;終了条件;増減式){ 処理 } ``` ```javascript= const a = ['a', 'b', 'c']; for (let i = 0; i < a.length; i++) { console.log(`${i}: ${a[i]}`) } // output 0: a 1: b 2: c ``` java言語で言うところの拡張forを利用することもできます。 ```javascript= const a = ['a', 'b', 'c']; for (const x of a) { console.log(x); } // output a b c ``` 拡張forでインデックスも使用したい場合は、entriesメソッドと配列の分割代入を併用します。 ```javascript= const a = ['a', 'b', 'c']; for (const [i,x] of a.entries()) { console.log(`${i}: ${x}`) } // output 0: a 1: b 2: c ``` ## forによる繰り返し(オブジェクト) オブジェクトのプロパティのキー(名前)を順に取得することもできます。 ```javascript= const animals = { dog: 1, cat: 2}; for (name in animals) { console.log(`${name}: ${animals[name]}`); } // output dog: 1 cat: 2 ``` ## forによる繰り返し(Mapオブジェクト) ArrayやMapなどは、列挙可能なオブジェクトと呼ばれ、繰り返し処理に便利な機能を持っています。 一般のオブジェクトと異なり、Mapオブジェクトは列挙可能ですので、以下のように繰り返し処理ができます。 ```javascript= const animals = new Map([['dog', 1], ['cat', 2]]); console.log(animals); for (let [key, value] of animals) { console.log(`${key}: ${value}`); } animals.set('dog', 3); console.log(animals.get('dog')) // output Map { 'dog' => 1, 'cat' => 2 } dog: 1 cat: 2 3 ``` Mapオブジェクトのメンバにアクセスするには、ドットやブラケットではなく、set/getメソッドを使用します。 ## break/continue forによる繰り返し処理(ループ)を、途中で終了したりスキップすることができます。 breakはその時点でループを中断します。 ```javascript= for (let i = 0; i < 10; i++) { if (i === 5) { break } console.log(i); } // output 0 1 2 3 4 ``` continueは現在のループのみスキップし継続します。 ```javascript= for (let i = 0; i < 10; i++) { if (i % 3 !== 0) { continue; } console.log(i); } // output 0 3 6 9 ``` # 関数 繰り返し利用する処理を、関数として定義することができます。 ```javascript= function hello() { console.log('hello'); console.log('world'); } hello(); hello(); ``` 1行目から4行目までが関数の「宣言」でこれにより変数helloに関数が定義されます。 変数名の後に「()」をつけることにより、関数として実行されます。 javascriptでは関数も普通のオブジェクトであり、関数名も普通の変数ですから、以下のように代入文で上書きしてしまうこともありえます。 ```javascript= function hello() { console.log('hello'); console.log('world'); } hello = 10; hello(); hello(); // output test.js:8 TypeError: hello is not a function ``` hello変数を関数として定義したあと、数字で上書きしてしまったため、8行目で「関数ではないので実行できない」エラーが発生しています。 6行目で誤った処理を行ったのに、離れた場所である8行目でエラーになることが問題で、大きなプログラムになると問題である6行目を発見することが困難になります。 ## アロー関数 ES2015で導入された「アロー関数」という表記法を利用すると、先程の問題を回避できます。 ```javascript= const hello = () => { console.log('hello'); console.log('world'); } hello = 10; hello(); hello(); // output test.js:6 TypeError: Assignment to constant variable. ``` 定数であるhelloに関数を代入していますが、正しく6行目でエラーになっています。 ## 引数と返り値 関数の呼び出しは「式」の一種であり、代入文の右辺などに、変数やリテラルと同じく利用できます。 関数の値は、returnキーワードで指定します。また、引数を受け取ることもできます。 ```javascript= const avg = (x, y) => { return (x+y)/2; } console.log(avg(2,4)); console.log(avg(7,5)); // output 3 6 ``` アロー関数の場合、関数本体が一つの式からなる場合、returnを省略できます。また、関数定義の中で外部の変数を参照することもできます。 ```javascript= const parent = 'red'; const yesno = (color) => color === parent ? 'yes' : 'no'; console.log(yesno('red')); console.log(yesno('blue')); // output yes no ``` 引数の分割代入については、演算子>分割代入の例で説明した通りです。念の為見直しておきましょう。 ## 関数を引数として渡す javascriptでは関数もデータ型の一つであり、変数に代入することができました。データ型であるため、関数の引数や返り値として使用することができます。関数を引数に取る関数のことを高階関数と呼びます。以下のようにアロー関数と組み合わせます。 ```javascript= const arr = [1, 2, 3, 4, 5, 6]; const fn = a => a * 2; const double = arr.map(fn); console.log(double); // output [ 2, 4, 6, 8, 10, 12 ] ``` # スコープ let/constは識別子を「ブロックスコープ」で定義します。ブロックスコープで定義された識別子は、それを囲むブロック「{}」内でのみ有効で、外から参照することはできません。ただし、内部で定義されていない限り、ブロックの外側を参照することができます。 ```javascript= let x = 'out x'; let y = 'out y'; { let x = 'in x'; console.log(x); console.log(y); } console.log(x); console.log(y); // output in x out y out x out y ``` # 配列 今まで断片的に説明してきましたが、利用頻度の多い組み込みオブジェクトである配列(Array)について纏めて説明します。 - 配列は各要素に0から始まる数字(添字、インデックス)によってアクセスするため、順序を持つ - 配列には異なる型の要素を入れることができる。 - 配列やオブジェクトや関数も可能である。 - 配列リテラルは[...]で表現し、要素のアクセスも[...]を使用する - lengthというプロパティで配列の要素数を知ることができる - 配列の最後の要素の添字より大きな添字を使って代入を行うと、配列が自動的に大きくなり、値が指定されていない要素にはundefinedが暗黙のうちに代入される。 関数の配列の例 ```javascript= const addprimes = [ (n) => n + 2, (n) => n + 3, (n) => n + 5, (n) => n + 7, ]; const x = addprimes[2](10); console.log(x); // output 15 ``` 配列の配列の例 ```javascript= const points = [ [1, 2], [3, 4], ]; console.log(points[1][0]); // output 3 ``` このように配列を複数行に分けて定義するとき、最後の要素の末尾にもカンマをつけることができます。将来行を追加した時にエラーを避けられるので、推奨される書き方です。 ## 要素の追加と削除 配列の先頭と末尾に要素を追加・削除する操作は頻繁に行われるため、専用のメソッド(関数であるプロパティ)が用意されています。 ```javascript= const { log } = console; const a = []; a.push(1); //末尾から追加 a.push(2); log('push'); log(a); a.unshift(3); //先頭から追加 a.unshift(4); log('unshift'); log(a); let b = a.pop(); //末尾から取り出し log('pop'); log(a); log(b); b = a.shift(); //先頭から取り出し log('shift'); log(a); log(b); // output push [ 1, 2 ] unshift [ 4, 3, 1, 2 ] pop [ 4, 3, 1 ] 2 shift [ 3, 1 ] 4 ``` ## mapとfilter 配列操作のうち、map/filterは特に利用頻度が高いメソッドです。以下の例は、トランプのカードのデータを、名称に読み替えてから、赤いスート(ハートとダイヤ)だけ取り出し、枚数を数えています。 ```javascript= const { log } = console; const dic = new Map([ [1, 'ハート'], [2, 'ダイヤ'], [3, 'スペード'], [4, 'クラブ'], ]); const data = [1, 3, 3, 4, 2, 5, 2]; const mark = data.map((n) => dic.get(n)); log('map'); log(mark); const red = mark.filter((m) => m === 'ハート' || m === 'ダイヤ'); log('filter'); log(red); log(red.length); // output map [ 'ハート', 'スペード', 'スペード', 'クラブ', 'ダイヤ', undefined, 'ダイヤ' ] filter [ 'ハート', 'ダイヤ', 'ダイヤ' ] 3 ``` ## reduce reduceは配列の全要素を変換しながら一つの値に取りまとめます。以下の例は、数字の配列を合計を求めています。 ```javascript= const { log } = console; const a = [1, 2, 3]; const sum = a.reduce((a, x) => a + x, 0); // 集計用関数, アキュムレータ初期値 log(sum); // output 6 ``` |a|x|集計処理|a = 集計件数の返り値| |:--|:--|:--|:--| |0|1|0 + 1|a = 1| |1|2|1 + 2|a = 3| |3|3|3 + 3|a = 6| 次の例は若干複雑ですが、動物の名前を頭文字ごとにグループ化しています。処理を追いかけてみましょう。 ```javascript= const animals = [ 'dog', 'cat', 'rat', 'rabbit', 'camel', 'donkey', ]; const index = animals.reduce((a,animal) => { const initial = animal[0]; const group = a.get(initial) || []; group.push(animal); return a.set(initial, group); }, new Map()); console.log(index); // output Map { 'd' => [ 'dog', 'donkey' ], 'c' => [ 'cat', 'camel' ], 'r' => [ 'rat', 'rabbit' ] } ``` |a|x|集計処理|a = 集計件数の返り値| |:--|:--|:--|:--| |Map{}|'dog'|set('d',['dog'])|a = Map{'d' => ['dog']}| |Map{'d' => ['dog']}|'cat'|set('c', ['cat'])|a = Map{'d' => ['dog'], 'c' => ['cat]}| # オブジェクト指向構文 ## クラス定義 同じ機能を持ったオブジェクトを、同一の設計書(クラス)から作成できると便利です。研修で使用する「moneta」でも、各ページの設定をクラスとして定義しています。同じ定義を元に、ページが表示されるごとに異なったオブジェクト(インスタンス)が作成されています。 以下の例では銀行クラスと、合併(add)メソッドを定義しています。 ```javascript= class Bank { constructor(name, num) { this.name = name; this.num = num; } get label() { return ('0000' + this.num).slice(-4) + ' ' + this.name + '銀行'; } add(other) { return new Bank(this.name + other.name, this.num + other.num); } } const meguro = new Bank('目黒', 1); const gotanda = new Bank('五反田', 2); const newbank = meguro.add(gotanda); console.log(meguro.label); console.log(gotanda.label); console.log(newbank.label); // output 0001 目黒銀行 0002 五反田銀行 0003 目黒五反田銀行 ``` ### constructor constructor関数は、new演算子でインスタンスが生成されるときに呼ばれ、初期化処理を行います。 ### get(アクセッサプロパティ) get label()はアクセッサプロパティと呼ばれ、読み取り専用のメソッドです。カッコを付けずにプロパティとして呼び出せること、および間違って更新してしまうことがないという利点があります。 ただし、パラメータを取ることはできません。 # 非同期プログラミング ## 非同期処理 プログラムは止まらずに連続して(同期的に)動きますが、ユーザー操作などはいつ発生するかわかりません(非同期的)それ以外にも以下のような処理が非同期的に行われます。 - ネットワーク経由のリクエスト - ファイルシステム関連の操作 - タイマーで遅延された操作 これらの非同期しょりが発生するたびに、プログラムを停止して処理の完了を待ってしまうと「フリーズ」が発生しますが、あとで処理をするために関数(コールバック関数)を登録して、処理を継続すれば「待ち(ブロック)」が発生しません。 非同期処理は記述順序と実行順序が異なるため、バグの原因になります。以下の例は、5行目→8行目→11行目の順で実行され、X=10が表示されることを意図しましたが、実際には5行目→11行目→8行目の順で実施されるため、X=0が表示されます。 ```javascript= const sleep = (s, fn) => setTimeout(fn, s * 1000); const main = () => { // xをゼロにする let x = 0; // 3秒待って、xに10を加える sleep(3, () => x += 10); // xを表示する console.log(x); } // output 0 ``` ## Promise 非同期処理の終了を待って次の処理を行うために、Promiseオブジェクトが提供されています。先程の例をPromiseで修正すると以下のようになります。 ```javascript= const timeout = (sec, fn) => new Promise(resolve => setTimeout(() => { fn(); resolve(); }, sec * 1000) ); // xをゼロにする let x = 0; // 3秒待って、xに10を加える timeout(3, () => x += 10) // xを表示する .then(() => { console.log(x) }); // output 10 ``` 13行目と16行目で合わせて一つの文です。 ## async/await async関数とawait演算子を使うと、プロミスを使ったコードをスッキリと書くことができます。先程の例を書き直すと以下の通りになります。 ```javascript= const timeout = async (sec, fn) => resolve => setTimeout(()=> { fn(); resolve(); }, sec * 1000); const main = async () => { // xをゼロにする let x = 0; // 3秒待つ await timeout(3, x += 10); // xを表示する console.log(x); } main(); // output 10 ```