ES2015で始めるJavaScript入門

  • 952
    いいね
  • 3
    コメント
この記事は最終更新日から1年以上が経過しています。

はじめに

JavaScript勉強会の資料です。
内容は、簡単なコマンドラインツールを作りながら、JavaScript(ES2015を含む)の書き方を学ぶものとなってます。
内容的には初心者向けなので、JavaScriptを勉強したい、JavaScriptは触ったことあるけどES2015を知らないので勉強したい、といった方が対象です。


JavaScriptとは

  • Webブラウザで実行可能なインタプリタ言語
  • 最近ではサーバサイドのものもある(Node.jsなど)
  • 各ブラウザで独自の実装があるが、標準化されたECMAScriptに準拠している
    • ES5, ES2015(ES6), ES2016, …
    • ES2015からは毎年新しい規格が策定される
      • これによりナンバリングは策定した年に変更

ES2015

  • ES5(2011年策定)から4年ぶりに策定されたECMAScript
  • 非常に多くの機能が追加された
  • 新しいブラウザはだいたい対応してきている

今回扱う内容

  • Node.jsで簡単なアプリを作りながらJavaScriptの書き方を学ぶ
    • コマンドラインツールを作る
    • HTMLは触らない
  • よく使う機能を中心に説明
    • 全部は紹介しない
    • ES2015もどんどん紹介
  • npmについても少し触れる

全体の流れ

  1. 環境の導入
  2. HelloWorld
    • 基本的な構文
    • データ型
    • 演算子
  3. コマンドライン引数の値を計算
    • 制御構文
    • オブジェクト、関数、配列
  4. APIを叩いて表示
    • 非同期処理
    • モジュール
    • クラス
  5. おまけ

npm

  • JavaScript用のパッケージマネージャ
  • できること
    • 公開されているパッケージを自分のプログラムで使う
    • 作成したプログラムをパッケージとして公開
    • 簡易的なタスクランナーとしても使える
  • package.jsonで情報を管理

環境


cloud9でnode.js v6をインストール

  • ワークスペース作成時にnode.jsを選択
  • 最初からnvmがインストールされている
    • nvmからnode.js v6をインストール
    • デフォルトのバージョンを6.5.0に設定
$ nvm install v6.5.0
$ nvm use v6.5.0
$ nvm alias default v6.5.0
$ node -v
v6.5.0

まずはHelloWorld


作業用ディレクトリの作成

  • cloud9の場合はディレクトリのファイルを消しておく
$ rm -rf *
  • それ以外の場合はディレクトリを作成して移動
$ mkdir hogehoge
$ cd hogehoge

プロジェクトの初期化

  • npm initコマンドを利用
    • package.jsonを作成
    • 各項目が気になる人はこのサイトをチェック
$ npm init -y
Wrote to /home/ubuntu/hhoge/package.json:

{
  "name": "workspace",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

実行ファイル用の設定

  • package.jsonを編集
    • mainプロパティを削除
    • preferGlobalプロパティの追加
      • グローバルインストールを促す
    • binプロパティの追加
      • 実行するjsファイルの指定を行う
package.json
 {
   "name": "workspace",
   "version": "1.0.0",
   "description": "",
-  "main": "index.js",
   "scripts": {
     "test": "echo \"Error: no test specified\" && exit 1"
   },
   "author": "",
-  "license": "ISC"
+  "license": "ISC",
+  "preferGlobal": true,
+  "bin": {
+    "hello": "hello.js"
+  }
 }

hello.jsを作成して実行してみる

hello.js
#!/usr/bin/env node

'use strict';

// 文字列を変数に代入
const text = 'HelloWorld';
console.log(text); // メソッドを実行
$ node hello.js
HelloWorld

どこからでも実行できるようにしてみる

  • npm linkでパスの通ったディレクトリにaliasを貼ることができる
$ npm link
/home/ubuntu/.nvm/versions/node/v6.5.0/bin/hello -> /home/ubuntu/.nvm/versions/node/v6.5.0/lib/node_modules/workspace/hello.js
/home/ubuntu/.nvm/versions/node/v6.5.0/lib/node_modules/workspace -> /home/ubuntu/workspace
$ hello
HelloWorld

HelloWorldのコードを見てみる

hello.js
#!/usr/bin/env node

'use strict';
// 文字列を代入
const text = 'HelloWorld';
console.log(text); // 実行

Strict モード

  • use strictと書くと有効になる
  • 非推奨な処理をエラーにする
    • 偶発的グローバル変数の作成
    • 書き込みできない変数への代入
  • スクリプトの先頭に書くとスクリプト全体で有効
    • 連結した他のコードにも影響が出る可能性がある
  • 関数の先頭で書くとその関数内で有効
    • こちらの方が安全
  • node.jsのモジュール内は常にStrictモードになる
    • モジュールの話は後の方で
function hoge() {
  'use strict';
  // 処理
}

コメント

  • コードとして実行されない文字列
// 1行コメント
// 行末までコメントになる

/*
複数行
コメント
*/
/*この間がコメントになる*/

セミコロン

  • 文を区切る
    • 文は一つの手続き
    • 文の末尾にはセミコロンを付ける
  • ブロック文の後ろには付けない
    • ブロックは文をグループ化する
    • {}で文を囲む
  • 関数やクラスの{}の後ろにもつけない
    • 関数やクラスについては後述
'use strict'; // 文
const text = 'HelloWorld'; // 代入も文
console.log(text); // メソッドの呼び出しも文

// ブロックの前にはifやforなどの制御文が来ることが多い
if (text) {
  console.log(text); // 文
} // ブロックの後ろには付けない

function print(str) {
  console.log(str);
} // 関数の後ろにも付けない

変数

  • データを格納するための入れ物
  • constの他にletvarなどの修飾子がある
    • スコープ(変数の有効範囲)や代入の制限などに違いがある
    • constletはES2015から
  • 修飾子の後に変数名を書くことで変数を作れる(変数宣言)
    • 同時に代入も可能
      • 代入は=演算子を利用
    • constは宣言時に代入が必須
  • 基本的にconstを使い、再代入が必要な場合はletを使うのがオススメ
const let var
スコープ ブロック ブロック 関数
再代入 不可能 可能 可能
{ // ブロック(単体で使うことはあまりない)
  const foo = '1'; // 変数の定義時に代入を行う
  foo = '10'; // 再代入はエラー
  let bar = '2';
  bar = '20'; // 再代入可能
  var baz = '30';
}
console.log(foo); // スコープの外なので参照できずエラー
console.log(bar); // 上に同じ

// varはスコープが関数
// 関数の外の場合はグローバル変数になる
console.log(baz); // 30

文字列

  • ''で囲むと文字列のデータになる
    • ""で囲んでも同じように文字列になる
    • 文字列結合する場合は+演算子を利用
  • ``(バッククオート)で囲むと、式展開ができる
    • テンプレートリテラルと呼ばれている
    • ES2015からの機能
    • ${hoge}と書いた場合、hogeが展開される
    • +演算子を使って結合するより、テンプレートリテラルを使う方がいい
const name = 'abcang';
console.log('hello ' + name); // hello abcang
console.log("hello " + name); // hello abcang
console.log(`hello ${name}`); // hello abcang

consoleオブジェクト

  • consoleオブジェクトはデバッグコンソールにアクセスする機能を提供
    • Node.jsでは標準出力に出力される
    • log以外にも実行時間を計測する機能(time, timeEnd)もある
  • オブジェクトのプロパティにアクセスする場合は、オブジェクトとプロパティ名の間を.で繋ぐ
    • プロパティが関数のときはメソッドと呼ぶ
    • オブジェクトについては後で詳しく説明
  • 関数を呼び出す場合は、うしろに()を付けて引数を指定する
    • 関数ついても後で詳しく説明
console.log('HelloWorld'); // 一般的な情報
console.time('timer');  // 計測開始
console.timeEnd('timer');  // 計測終了

データ型

  • プリミティブ型: 不変な値
    • 文字列(String)
    • 数値(Number)
    • 真偽値(Boolean)
    • null
    • undefined
    • シンボル(Symbol)
  • オブジェクト: 名前の付けられた値を集めたもの
    • オブジェクト(Object)
    • 関数(Function)
    • 配列(Array)
    • など
  • リテラル
    • プログラム内に直接記述する数値や文字列などのこと
      • true42のような
  • typeof演算子でデータ型を調べることができる
typeof true; // => 'boolean'
typeof 42; // => 'number'
typeof 'js'; // => 'string'
typeof Symbol('sym');// => 'symbol'
typeof undefined; // => 'undefined'
typeof null; // => 'object'
typeof ['array']; // => 'object'
typeof { 'key': 'value' }; // => 'object'
typeof function() {}; // => 'function'

文字列

(割愛)


数値

  • 整数、小数点、その他がある
// 整数リテラル(10進数、16進数、8進数、2進数)
10; // 10進数リテラル
-10; // マイナス値
0xa;  // 16進数リテラル
// 浮動小数点リテラル
3.14;
.1;
1.2e-5;
//その他
Infinity; // 正の無限大
-Infinity; //負の無限大
NaN; // Not a Number

真偽値

  • 真(true)か偽(false)を取り扱う
  • 比較演算でよく利用される
1 == 1; // => true
1 == 2; // => false
  • falsy
    • 偽と見なされる値
    • 型変換するとfalseになる
    • 0, -0, null, false, NaN, undefined, 空文字列 ('')
  • truthy
    • 真と見なされる値
    • 型変換するとtrueになる
    • falsy以外の値
Boolean(0); // => false
Boolean('0'); // => true

null, undefined

  • null
    • 値がないことを表す値
    • オブジェクトが無いときなどに使われる
  • undefined
    • 未定義値
    • 関数の引数に値が渡されなかった場合や、オブジェクトの定義されていないプロパティを参照した時に返却される
var obj = null;
console.log({}.a); // undefined

シンボル

  • ユニークで不変なデータ型
  • オブジェクトのプロパティとして使われたりする
  • ES2015からの機能
Symbol('foo') === Symbol('foo'); // => false

オブジェクトについて

後述


演算子

  • 一部を紹介
    • 算術演算子
    • 比較演算子
    • 論理演算子
    • 代入演算子
    • その他よく使うもの

算術演算子

オペランドを計算し、その結果を返す

  • 加算(+)
    • 文字列の場合連結
  • 減算(-)
  • 乗算(*)
  • 除算(/)
  • 余剰(%)
  • べき乗(**)
    • ES2016から追加
    • Node.js v6.5.0では未対応
 6  +  2; //  => 8
'6' + '2'; // => '62'
 6  + '2'; // => '62'
'6' +  2; //  => '62'
 6  -  2; //  => 4
'6' -  2; //  => 4
 6  *  2; //  => 12
 6  * '2'; // => 12
 6  /  2; //  => 3
'6' / '2'; // => 3
 6  %  2; //  => 0
 6  ** 2; //  => 36

比較演算子

オペランドを比較し、その真偽値を返す

  • 等しい(==)
  • 等しくない(!=)
    • ==!=は型が違うと型変換後に比較される
  • 厳密に等しい(===)
  • 厳密に等しくない(!==)
    • 厳密な比較は型変換を行わない
  • より大きい (>)
  • より大きいまたは等しい(以上) (>=)
  • より小さい (<)
  • より小さいまたは等しい(以下) (<=)
1 == '1'; //   => true
1 != true; //  => false
1 === '1'; //  => false
1 !== true; // => true
2 >  1; // => true
1 >= 1; // => true
2 <  1; // => false
1 <= 1; // => true

論理演算子

  • AND演算(&&)
    • 左辺がfalsyなら左辺を返す
    • 左辺がtruthyなら右辺を返す
    • 連続で繋げた場合、左から順に評価して最初にfalsyになる値を返す
      • falsyな値がない場合は最後の値
  • OR演算(||)
    • 左辺がtruthyなら左辺を返す
    • 左辺がfalsyなら右辺を返す
      • 連続で繋げた場合、左から順に評価して最初にtruthyになる値を返す
    • truthyな値がない場合は最後の値
  • NOT演算(!)
    • falsyな値ならtrueを返す
    • truthyな値ならfalseを返す
1 && 2 && 0 && null; // => 0
1 && 2 && 3; // => 3
0 || false || 1 || 2; // => 1
0 || false || null; // => null
!0; // => true
!'0'; // => false

代入演算子

  • 代入(=)
  • 算術演算子との組合せもある
let hoge = 10;
hoge += 10; // => 20
hoge -= 10; // => 10
hoge *= 2; //  => 20
hoge /= 2; //  => 10
hoge %= 4; //  => 2
hoge **= 3; // => 8

その他よく使うもの

  • グループ化演算子(())
    • 評価の優先順位を制御
2 + 3 * 4; // => 14
(2 + 3) * 4; // => 20
  • インクリメント(++)
    • 数値を1加算
  • デクリメント(--)
    • 数値を1減算
let hoge = 1;
// 後ろに付けると評価後に計算される
hoge++; // => 1
hoge; // => 2
// 前に付けると評価前に計算される
--hoge; // => 1
  • 三項演算子(条件式 ? 式1 : 式2)
    • 条件式がtruthyの場合、式1の評価結果を返す
    • そうでない場合は式2の評価結果を返す
const age = 24;
age >= 20 ? 'adult' : 'kids'; // => 'adult'

式と評価

  • 評価
    • 値や変数などを計算したり、関数を実行したりすること
    • 評価後には値を返す
    • 評価が可能な(計算後に値を返す)文
    • ifforなどの制御構文、constなどの宣言は式ではない
// 全体は文(constで変数宣言しているため)
// `hoge = 1`の部分は式
const hoge = 1;

// `if(){}`は文
// `hoge`の部分は式(値を返している)
if (hoge) {
  // 全体が文(console.logは値を返す)
  console.log(hoge);
}

演習

  • HelloWorldの代わりに別のものを出力
    • 文字列を結合してみる
      • +演算子を利用
      • テンプレートリテラルを利用
    • 数値
      • 算術演算の結果を出力してみる
    • 真偽値
      • 比較演算の結果など

コマンドライン引数の数字を計算


コマンドライン引数を表示してみる

calc.js
#!/usr/bin/env node

'use strict';

// 引数の配列の要素を順番に表示
for (const arg of process.argv) {
  console.log(arg);
}
package.json
   "bin": {
-    "hello": "hello.js"
+    "hello": "hello.js",
+    "calc": "calc.js"
   }
$ npm link
$ calc a b 1 2
/home/ubuntu/.nvm/versions/node/v6.5.0/bin/node
/home/ubuntu/workspace/calc.js
a
b
1
2

process.argv

  • コマンドライン引数が格納されている配列
    • 最初の要素はnodeのパス
    • 2番目の要素は実行するJavaScriptのパス
    • 3番目からコマンドライン引数
  • processオブジェクトはプロセス関連のプロパティを持っている

配列

  • データの集合
  • []で要素を囲むと配列を作成できる
const arr = [123, 456, 789]; // []で囲むと配列になる
// 配列の長さ
console.log(arr.length); // => 3
// 最初の要素にアクセス
console.log(arr[0]); // => 123
// 配列に要素を追加
arr.push('abc');
console.log(arr); // => [123, 456, 789, 'abc']
// 配列の順番を逆に
console.log(arr.reverse()); // => ['abc', 789, 456, 123]

for of

  • 反復可能な(iterable)オブジェクトから要素を1つづつ取得して処理
    • 配列はiterableなオブジェクト
  • 配列のキー(何番目か)を取り出したい場合は、keys()やentries()を使う
  • ES2015からの機能
for (取り出した要素 of iterableなオブジェクト) {
  処理
}
const arr = [123, 456, 789];
for (const value of arr) {
  console.log(value);
}
// 123
// 456
// 789

for (const key of arr.keys()) {
  console.log(key);
}
// 0
// 1
// 2

for (const entry of arr.entries()) {
  console.log(entry); // キーと値の配列
}
// [ 0, 123 ]
// [ 1, 456 ]
// [ 2, 789 ]

コマンドライン引数を計算

calc.js
#!/usr/bin/env node

'use strict';

// 関数calcを作成
function calc(arg0, arg1) {
  console.log(arg0 + arg1);
  console.log(arg0 - arg1);
  console.log(arg0 * arg1);
  console.log(arg0 / arg1);
}

// メソッドチェーンが長い場合は適当な位置で改行する
// 先頭にドットが来るようにするといい
// 配列を切り出し → 配列の要素を文字列から数値に変換
const argv = process.argv.slice(2)
  .map((i) => Number(i));

// 作成した関数calcを呼び出し
calc(argv[0], argv[1]);
$ calc 6 2
8
4
12
3

関数

  • 一連の処理をまとめて、繰り返し使えるようにしたもの
function 関数名(仮引数1, 仮引数2) {
  処理;
  return 戻り値;
}

// 関数オブジェクトを生成して、それを変数に入れることもできる
// 特別な理由がない限り上の方法を使うべき
const 変数名 = function(仮引数) {
  処理;
  return 戻り値;
}; // 関数オブジェクトを代入する場合はセミコロンを付ける

// 引数や戻り値が必要ない場合は省略可
function 関数名() {
  処理;
}

// 呼び出しはこんな感じ
関数名(引数1, 引数2); 
function print(str) {
  console.log(str);
}
print('hello'); // hello

デフォルト引数

  • 引数がなかった時に指定した値がセットされる
    • 引数がなく、デフォルト引数が指定されていない場合はundefinedがセットされる
    • 仮引数に代入するように記述する
  • ES2015からの機能
function test(a, b = 'hoge') {
  console.log(a, b);
}
test(); // => undefined 'hoge'
test(1, 2);// => 1 2

レストパラメータ

  • 不特定多数の引数を配列として受け取れる
    • 仮引数名の前に...を付ける
  • ES2015からの機能
function test(a, b = 'hoge', ...c) {
  console.log(a, b, c);
}
test(); // => undefined 'hoge' []
test(1, 2, 3, 4);// => 1 2 [ 3, 4 ]

アロー関数

  • 短い構文で匿名関数を作れる
    • このときthisを束縛する
    • thisについては後ほど
  • 引数にコールバック関数として指定する時によく用いられる
  • ES2015からの機能
// 括弧内に仮引数、その後=>を書いてブロック
(仮引数) => {
  処理;
};

// {}で囲わず、シンプルに記述する事もできる
// この場合、文の戻り値が関数の戻り値になる
(仮引数) => ;
仮引数 => ; // 仮引数が1つだけの場合括弧を省略できる
(仮引数) => { return ; }; // これと同義
function doCallback(func) {
  func(1, 2, 3);
}

doCallback((a0, a1, a2) => {
  return a0 + a1 + a2;
})); // => 6

doCallback((a0, a1, a2) =>  a0 + a1 + a2); // => 6

Array.prototype.slice

  • 配列の一部を切り出して新しい配列を作成するメソッド
    • slice(2)は配列の3番目以降を切り出す
    • 1番目と2番目のコマンドライン引数でない値を除いている
const argv = ['foo', 'bar', '6', '2'].slice(2);
console.log(argv); // => ['6', '2']

Array.prototype.map

  • 配列内の全ての要素について与えられた関数を呼び出し、その結果を格納した新しい配列を生成
    • アロー関数を渡してあげることが多い
  • 例では (i) => Number(i)を渡している
    • 配列の要素全てを文字列から数値に変換
const argv = ['6', '2'];
console.log(argv[0] + argv[1]); // => '62'
console.log(argv[0] - argv[1]); // => 4

const argv_conv = argv.map((i) => Number(i));
console.log(argv_conv); // => [6, 2]
console.log(argv_conv[0] + argv_conv[1]); // => 8
console.log(argv_conv[0] - argv_conv[1]); // => 4

計算方法を指定できるように

calc.js
#!/usr/bin/env node

'use strict';
// 引数をそれぞれの変数に代入
const command = process.argv[2];
const argv = process.argv.slice(3)
  .map((i) => Number(i));

// calcオブジェクトを作成し、その中に計算用関数を用意
// それぞれの関数は計算結果を戻り値として返す
const calc = {
  add(...args) {
    // reduceを使って配列の中身を足していく
    return args.reduce((prev, curr) => prev + curr);
  },
  sub(a0, a1) {
    return a0 - a1;
  },
  mul(...args) {
    // reduceを使って配列の中身をかけていく
    return args.reduce((prev, curr) => prev * curr);
  },
  div(a0, a1) {
    return a0 / a1;
  }
};

// 分割代入
// argv[0]とargv[1]をそれぞれarg0とarg1に代入
const [arg0, arg1] = argv;

// コマンドライン引数で受け取ったコマンドに応じて分岐
switch (command) {
  case 'add':
  case 'mul':
    // スプレッドパラメータを使って引数を全て渡す
    console.log(calc[command](...argv));
    break;

  case 'sub':
    // if文で分岐
    if (argv.length < 2) {
      console.log('引数が足りません');
    } else {
      console.log(calc.sub(arg0, arg1));
    }
    break;

  case 'div':
    if (argv.length < 2) {
      console.log('引数が足りません');
    } else if (arg1 === 0) {
      console.log('0では割れません');
    } else {
      console.log(calc.div(arg0, arg1));
    }
    break;

  // 条件にマッチしない場合
  default:
    console.log('命令が存在しません');
    break;
}
$ calc add 1 2 3
6
$ calc sub 1 2
-1
$ calc mul 1 2 3                                                                                                           
6
$ calc div 1 2 3                                                                                                           
0.5
$ calc hoge
命令が存在しません

オブジェクト

  • 名前の付けられた値を集めたもの
  • オブジェクト内のそれぞれの値の事をプロパティと呼ぶ
    • プロパティが関数の場合はメソッドと呼ぶ
  • {}で囲って作成する
    • :の左側がプロパティ名、右側が値
    • 複数のプロパティを作る場合は,で区切る
    • ブロックではない
  • プロパティのアクセスには.[]を使う
const obj = {
  プロパティ名1: 値1,
  プロパティ名2: 値2
};

// アクセス
obj.プロパティ名1
//もしくは
obj['プロパティ名2']
const person = {
  name: 'abcang',
  age: 21
}; // ブロックではないためセミコロンを付ける

console.log(person['name']); // abcang
console.log(person.age);     // 21

// 代入も可能
person.gender = 'male';
// プロパティにはオブジェクトをいれてもOK
person['icon'] = {
  small: 'http://example.com/icon/abcang/small.png',
  large: 'http://example.com/icon/abcang/large.png'
};

ショートハンドプロパティ名

  • ES2015から変数名がプロパティ名と同じ場合省略可能
const name = 'abcang';
const age = 21;
const person = {
  name,
  age
};

// 従来の書き方
//const person = {
//  name: name,
//  age: age
//};

console.log(person); // { name: 'abcang', age: 21 }

メソッド

  • オブジェクトに属する関数のこと
  • ES2015からは簡潔に書けるようになった
const name = {
  first: 'hiroshi',
  last: 'abe',
  handle: 'abcang',
  // ES2015からの書き方
  full() {
    return `${this.first} ${this.last}`;
  },
  // ES2015より前の書き方
  fullWithHandle: function() {
    return `${this.first} ${this.last} (${this.handle})`;
  }
}

console.log(name.full()); // hiroshi abe
console.log(name.fullWithHandle()); // hiroshi abe (abcang)

this

  • メソッドの呼び出し元のオブジェクトを指すオブジェクト
    • 本当はもうちょっと複雑だけど、今回は割愛
const person = {
  name: {
    first: 'hiroshi',
    last: 'abe',
    full() {
      // full()の呼び出し元はperson.nameになるため、
      // ここのthisはperson.nameを指す
      return `${this.first} ${this.last}`;
    }
  },
  age: 21,
  fullnameWithAge() {
    // fullnameWithAge()の呼び出し元はpersonになるため、
    // ここのthisはpersonを指す
    return `${this.name.full()} (${this.age})`;
  }
};

console.log(person.fullnameWithAge()); // hiroshi abe (21)

gettersetter

  • プロパティにアクセスされた時に実行されるメソッド
    • getterは参照時、setterは代入時に実行される
    • メソッド名の前に、getterの場合はgetを、setterの場合はsetをつける
    • 戻り値がプロパティの返す値になる
    • setterに代入された値は引数に渡される
const name = {
  first: 'hiroshi',
  last: 'abe',
  get full() {
    return `${this.first} ${this.last}`;
  },
  set full(fullname) {
    // スペースで分割して配列にしたものを分割代入
    const [first, last] = fullname.split(' ');
    this.first = first;
    this.last = last;
    return `${this.first} ${this.last}`;
  }
};

name.full; // => 'hiroshi abe'
name.full = 'foo bar';
name.first; // => 'foo'
name.last; // => 'bar'

分割代入

  • 配列やオブジェクトからデータを取り出して個別の変数に代入できる
  • ES2015からの機能
const array = [1, 2, 3, 4];
const object = {foo: 1, bar: 2, baz: 3};

// 配列の分割代入では残りを配列で受け取れる
const [a, b, ...c] = array;
console.log(a, b, c); // 1 2 [ 3, 4 ]

// オブジェクトの分割代入ではプロパティ名と同じ名前の変数に代入
const {foo, bar} = object;
console.log(foo, bar); // 1 2

// 別の変数名に代入することも可能
const {foo: x, bar: y} = object;
console.log(x, y); // 1 2

// 関数と組合せ
function func({hoge, fuga} = {hoge: 1, fuga: 2}) {
  console.log(hoge, fuga);
}
func(); // 1 2
func({hoge: 10, fuga: 20, piyo: 30}); // 10 20

Array.prototype.reduce

  • 配列の要素を順番に処理し、一つの値にする
  • 第1引数にはコールバック関数を渡す
  • 第2引数は最初にprevに渡される値
    • 省略した場合は配列の先頭の要素が最初のprev
  • 2回目からはコールバック関数の返り値がprevに渡される
const array = [1, 2, 3, 4];
array.reduce((prev, curr) => prev + curr); // => 10
prev curr 戻り値
初回 1 2 3
2回目 3 3 6
3回目 6 4 10

switch文

  • 式を評価した値と一致するラベルの文を実行
    • 処理の最後にはbreak文を書く
    • どのラベルにも一致しなかった場合はdefaultが呼ばれる
switch() {
  case 値1:
    値1だった場合の処理;
    break;

  case 値2:
    値2だった場合の処理;
    break;

  default:
    どれにも一致しなかった場合の処理;
    break;
}
const command = 'add';
switch(command) {
  case 'add':
    console.log('足し算');
    break;

  case 'sub':
    console.log('引き算');
    break;

  case 'mul':
  case 'div': // caseを続けて書くことで複数マッチさせることもできる
    console.log('掛け算か割り算');
    break;

  default:
    console.log('その他');
    break;
}

if文

  • 指定した条件がtruthyなら文を実行する
  • elseelse ifで条件に合わなかった場合の条件を指定できる
if (条件式1) {
  処理1; // 条件式1がtruthyの場合実行
} else if(条件式2) {
  処理2; // これまでの条件式がtruthyでなく、条件式2がtruthyの場合実行
} else {
  処理3; // これまでの条件式がtruthyでない場合実行
}
const num = 5;
if (num > 0) {
  console.log('正の整数');
} else if(num < 0) {
  console.log('負の整数');
} else {
  console.log('ゼロ');
}

if (num) { // 5はtruthy
  console.log('ゼロでない数');
}

スプレッド演算子

  • 関数の複数の引数や、配列の複数の要素が置かれるところで式を展開
    • 分割代入の余りを受け取るときもスプレッド演算子を使う
  • ES2015からの機能
const array = [1, 2, 3];

function func(a, b, c) {
  console.log(a, b, c);
}
func(...array); // 1 2 3

console.log([0, ...array, 4, 5]); // [ 0, 1, 2, 3, 4, 5 ]

const [x, ...rest] = array;
console.log(rest); // [2, 3]

演習

  • べき乗と剰余の機能を加えてみる
  • 配列やオブジェクトをもっと触ってみる
    • 苗字と名前と年齢を持ったオブジェクトの配列を作ってみる
    • それをArray.prototype.mapを使って苗字 名前の文字列の配列に変換してみる
    • 年齢の平均値を求めてみる
{
  name: {
    first: 'hiroshi',
    last: 'abe'
  },
  age: 21
}

APIを叩いて表示


npmでパッケージをインストール

$ npm install axios --save
  • npmjs.comに公開されているパッケージをインストールすることができる
    • npm install パッケージ名1 パッケージ名2 ...
  • --saveオプションを付けることで、インストールしたパッケージの情報がpackage.jsonに書き込まれる
  "dependencies": {
    "axios": "^0.14.0"
  }
  • --save-devオプションを付けるとdevDependenciesの方に書き込まれる
    • 開発時にしか使わないパッケージはこのオプションでインストールする
  • 省略形のコマンドが便利
    • npm installnpm i
    • --save-S
    • --save-dev-D

api.jsを作って実行

api.js
#!/usr/bin/env node

'use strict';

// モジュールを利用
const axios = require('axios');

axios.get('http://api.randomuser.me/').then((res) => {
  // 成功した場合
  console.log(res.data);
}).catch((err) => {
  // 失敗した場合
  console.log(err.message);
});

package.json
   "bin": {
     "hello": "hello.js",
-    "calc": "calc.js"
+    "calc": "calc.js",
+    "api": "api.js"
   }
$ npm link
$ api
{ results: 
   [ { gender: 'female',
       name: [Object],
       location: [Object],
       email: 'rosario.alonso@example.com',
       login: [Object],
       dob: '1972-02-21 07:02:50',
       registered: '2008-07-21 11:57:10',
       phone: '911-213-959',
       cell: '636-097-852',
       id: [Object],
       picture: [Object],
       nat: 'ES' } ],
  info: { seed: 'b048d8f2f5d9cf5d', results: 1, page: 1, version: '1.1' } }

モジュールとパッケージ

  • モジュールは機能をまとめたもの
  • パッケージはモジュールをまとめたもの
  • Node.jsではrequireを使うことでモジュールを利用することができる
    • npmに公開されているパッケージに含まれるモジュール
    • 自分で作ったモジュール
  • ブラウザ向けのコードでは、webpackを使うことでモジュールを利用できる
const 変数名 = require('モジュール名');

Promise

  • 非同期処理を制御するためのオブジェクト
  • ES2015からの機能
  • 非同期処理とは
    • 処理の実行完了を待たずに、他の処理を実行する
    • コードの上から順番に実行されるとは限らない
  • コールバック関数(処理が完了した後に実行するための関数)を関数に渡してあげることが多い
// 1秒ごとに表示したい
// けどsetTimeoutは非同期処理されるので、1秒後に一斉に表示されてしまう
setTimeout(() => console.log(1), 1000);
setTimeout(() => console.log(2), 1000);
setTimeout(() => console.log(3), 1000);

// 1秒ごとに表示される
setTimeout(() => {
  console.log(1);
  setTimeout(() => {
    console.log(2);
    setTimeout(() => {
      console.log(3);
    }, 1000);
  }, 1000);
}, 1000);

Promiseを使うとコードがキレイになる

  • ネストが減り、コードが読みやすくなる
    • Promiseを使った場合は1ネスト
    • 使わない場合はsetTimeoutの数だけ深くなっている
// Promiseを使わない場合
setTimeout(() => {
  console.log(1);
  setTimeout(() => {
    console.log(2);
    setTimeout(() => {
      console.log(3);
    }, 1000);
  }, 1000);
}, 1000);


function setTimeoutAsync(time) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve();
    }, time);
  });
}

// Promiseを使った場合
setTimeoutAsync(1000).then(() => {
  console.log(1);
  return setTimeoutAsync(1000);
}).then(() => {
  console.log(2);
  return setTimeoutAsync(1000);
}).then(() => {
  console.log(3);
})

Promiseの使い方

  • Promiseオブジェクトを作成する時に関数を渡す
    • 関数内では処理が終わった後に、成功時にはresolve関数を、失敗時にはreject関数を実行して終了する
    • resolverejectの引数に値を渡すと、thencatchで受け取ることができる
  • Promiseオブジェクトに登録した関数が実行されたあとに呼ばれる関数をthencatchで設定できる
    • 処理が成功した場合はthen、失敗した場合はcatchが呼ばれる
function setTimeoutAsync(time, occurError = false) {
  return new Promise((resolve, reject) => {
    if (occurError) {
      reject(new Error('エラー!'));
    } else {
      setTimeout(() => {
        resolve(`${time}ミリ秒経った!`);
      }, time);
    }
  });
}

setTimeoutAsync(1000).then((text) => {
  console.log(text); // '1000ミリ秒経った!'が表示
});

setTimeoutAsync(1000, true).catch((err) => {
  console.log(err.message); // 'エラー!'が表示
});

thenを繋ぐ

  • thencatchもPromiseオブジェクトを返すため、さらにthencatchを繋げることができる
  • thenの中で戻り値を設定すると、次に繋いだthenで受け取ることができる
    • 戻り値がPromiseオブジェクトの場合、Promiseオブジェクトの関数が実行された後にthenに繋がる
    • 戻り値がrejectだった場合はcatchが実行される
function setTimeoutAsync(time, occurError = false) {
  return new Promise((resolve, reject) => {
    if (occurError) {
      reject(new Error('エラー!'));
    } else {
      setTimeout(() => {
        resolve(`${time}ミリ秒経った!`);
      }, time);
    }
  });
}

setTimeoutAsync(1000).then((text) => {
  console.log(text);
  return '値を返すと次のthenが実行される';
}).then((text) => {
  console.log(text);
  return setTimeoutAsync(1500); // Promiseの関数が実行された後に次のthenへ
}).then((text) => {
  console.log(text);
  return setTimeoutAsync(2000, true); // '失敗した場合はcatchが呼ばれる
}).then((text) => {
  console.log('catchに飛んでしまうためここは実行されない');
}).catch((err) => {
  console.log(err.message);
});
実行結果
1000ミリ秒経った!
値を返すと次のthenが実行される
1500ミリ秒経った!
エラー

axios

  • HTTPクライアント
  • レスポンスをPromiseで返してくれる
  • 成功した場合はレスポンスの情報が渡される
    • 取得したデータそのものはdataプロパティにある
    • JSONの場合は自動的にJavaScriptのオブジェクトに変換してくれる
  • 失敗した場合はエラー情報が渡される
// GETパラメータは第二引数にparamsプロパティを含んだオブジェクトで指定
axios.get('http://example.com/', {
  params: {
    id: 12
  }
}).then((res) => {
  console.log(res.data);
}).catch((err) => {
  console.log(err.message);
});

// POSTパラメータは第二引数に直接オブジェクトで指定
axios.post('http://example.com/', {
  id: 12,
  text: 'hogehoge'
}).then((res) => {
  console.log(res.data);
}).catch((err) => {
  console.log(err.message);
});

RANDOM USER GENERATOR

  • ランダムなユーザのJSONを返してくれるWebサービス
  • 今回はこのAPIを使ってみる
  • 以下のようなJSONが取得できる
  • 詳しくはドキュメントを参照
{
  "results": [
    {
      "gender": "male",
      "name": {
        "title": "mr",
        "first": "romain",
        "last": "hoogmoed"
      },
      "location": {
        "street": "1861 jan pieterszoon coenstraat",
        "city": "maasdriel",
        "state": "zeeland",
        "postcode": 69217
      },
      "email": "romain.hoogmoed@example.com",
      "login": {
        "username": "lazyduck408",
        "password": "jokers",
        "salt": "UGtRFz4N",
        "md5": "6d83a8c084731ee73eb5f9398b923183",
        "sha1": "cb21097d8c430f2716538e365447910d90476f6e",
        "sha256": "5a9b09c86195b8d8b01ee219d7d9794e2abb6641a2351850c49c309f1fc204a0"
      },
      "dob": "1983-07-14 07:29:45",
      "registered": "2010-09-24 02:10:42",
      "phone": "(656)-976-4980",
      "cell": "(065)-247-9303",
      "id": {
        "name": "BSN",
        "value": "04242023"
      },
      "picture": {
        "large": "https://randomuser.me/api/portraits/men/83.jpg",
        "medium": "https://randomuser.me/api/portraits/med/men/83.jpg",
        "thumbnail": "https://randomuser.me/api/portraits/thumb/men/83.jpg"
      },
      "nat": "NL"
    }
  ],
  "info": {
    "seed": "2da87e9305069f1d",
    "results": 1,
    "page": 1,
    "version": "1.1"
  }
}

取得したデータを管理するクラスを作る

user-list.js
const axios = require('axios');

class User {
  // new演算子を使ってインスタンスを生成する時に呼ばれる
  constructor(user) {
    // オブジェクトのプロパティをコピー
    Object.assign(this, user);
    this.name = Object.assign({
     get full() { 
        return `${this.first} ${this.last}`;
      } 
    }, user.name);
  } // classの中で宣言した関数の末尾には,を付けない

  get age() {
    return Math.floor((Date.now() - Date.parse(this.dob)) / (1000 * 60 * 60 * 24 * 365));
  }
}

// extendsを付けることで継承できる
class UserList extends Array {
  // クラスメソッド
  static fetch(params) {
    return axios.get('http://api.randomuser.me', { params })
      .then(({data}) => {
        // Userのインスタンスに変換
        const results = data.results.map((user) => new User(user));
        // 配列をUserListに変換
        // アロー関数内ではアロー関数の外のthisが適用される
        // ここのthisはUserList
        const users = new this(...results);
        return users;
      });
  }

  // インスタンスメソッド
  getAverageAge() {
    // ここのthisはUserListのインスタンス
    return this.reduce(((sum, user) => sum + user.age), 0) / this.length;
  }

  getMaxAge() {
    return Math.max(...(this.map((user) => user.age)));
  }

  getMinAge() {
    return Math.min(...(this.map((user) => user.age)));
  }
}

// クラスをエクスポート
module.exports = UserList;
api.js
#!/usr/bin/env node

'use strict';

// user-list.jsからUserListをインポート
const UserList = require('./user-list');

UserList.fetch({ results: 5, nat: 'us' })
  .then((users) => {
    for (const user of users) {
      console.log(`${user.name.full} (${user.age})`);
    }

    console.log();

    console.log('Average age:', users.getAverageAge());
    console.log('Max age:', users.getMaxAge());
    console.log('Min age:', users.getMinAge());
  }).catch((err) => {
    console.log(err.message);
  });
$ api
eileen king (45)
zachary thomas (37)
ralph henderson (59)
terrance banks (56)
letitia bowman (51)

Average age: 49.6
Max age: 59
Min age: 37

クラス

  • 「オブジェクト」の情報を抽象化したもの
    • オブジェクトの設計図
    • クラスから生成したオブジェクトのことをインスタンスと呼ぶ
  • クラスの文法は以下の通り
    • インスタンスを生成する時はnew演算子を付けて実行する
    • newするときの処理が不要な場合はconstructorは省略可能
    • インスタンスのプロパティの初期化はconstructor内で行うといい
  • クラス構文はES2015から導入された
    • それ以前はprototypeにインスタンスメソッドを追加していくことで実現
class クラス名 {
  constructor(仮引数) {
    // newした時に実行する処理
  }
}

const 変数名 = new クラス名(引数);
class User {
  constructor(name) {
    this.name = name;
  }
}

const taro = new User('taro');
console.log(taro); // User { name: 'taro' }

インスタンスメソッドとクラスメソッド

  • インスタンスメソッド
    • 生成したインスタンスから呼べるメソッド
    • インスタンスメソッドのthisはインスタンスを指す
    • setterやgetterも定義できる
  • クラスメソッド
    • クラスから呼べるメソッド
      • インスタンスからは呼べない
    • クラスメソッドのthisはクラスを指す
    • 先頭にstaticを付ける
class User {
  constructor(name) {
    this.name = name;
  }

  static showClass() {
    console.log(this);
  }

  showInstance() {
    console.log(this);
  }

  greet() {
    console.log(`${this.name}さんこんにちは`);
  }
}

const taro = new User('taro');
console.log(taro); // User { name: 'taro' }
taro.greet(); // 'taroさんこんには'と表示

User.showClass(); // [Function: User]
taro.showInstance() // User { name: 'taro' }

継承

  • 既存のクラスを元にクラスを作る
  • extendsキーワードを使って継承元クラスを指定する
  • 継承元クラスはsuperでアクセスできる
class Person {
  static walk() {
    return '二本足で歩く';
  }
}

class Baby extends Person {
  static walk() {
    return '四本足で歩く';
  }
}

class Elderly extends Person {
  static walk() {
    return '杖を使って' + super.walk();
  }
}

console.log(Baby.walk()); // 四本足で歩く
console.log(Person.walk()); // 二本足で歩く
console.log(Elderly.walk()); // 杖を使って二本足で歩く

Object.assign

  • 複数のオブジェクトの要素を一つのオブジェクトにコピーする
  • 第一引数のオブジェクトに第二引数以降のオブジェクトの要素をコピー
  • 既にプロパティが存在する場合上書きされる
  • ES2015からの機能
const obj = {a: 1, b: 2, c: 3};
Object.assign(obj, {d: 4, e: 5}, {a: 10, b: 20});
console.log(obj); // { a: 10, b: 20, c: 3, d: 4, e: 5 }

アロー関数のthisの扱い

  • アロー関数内のthisは、アロー関数の外のthisが適用される
  • functionで関数を作ると、thisが指すオブジェクトが変わってしまう
  • 呼び出し元のオブジェクトがない場合はthisの値がStrictモードかどうかによって変わる
    • 非Strictモードの時はグローバルオブジェクト
    • Strictモードの時はundefined
'use strict';
const obj = {
  normal() {
    console.log(this);
    (function() {
      // メソッドの呼び出し元のオブジェクトがないため、このthisはundefined
      console.log(this);
    })();
  },
  arrow() {
    console.log(this);
    (() => {
      // このthisはアロー関数の外側のthisと同じ
      console.log(this);
    })();
  }
};

obj.normal(); 
// { normal: [Function: normal], arrow: [Function: arrow] }
// undefined

obj.arrow();
// { normal: [Function: normal], arrow: [Function: arrow] }
// { normal: [Function: normal], arrow: [Function: arrow] }

Math

  • 数学的な定数と関数を提供するオブジェクト
  • Math.floor
    • 小数点切り捨て
    • 切り上げはMath.ceil
    • 四捨五入はMath.round
Math.floor(10.5); // => 10
Math.ceil(10.5); // => 11
Math.round(10.5); // => 11
  • Math.max: 最大値
  • Math.min: 最小値
    • 引数に渡した数値の中で一番大きい(小さい)値を返す
Math.max(1, 10, 100); // => 100
Math.min(1, 10, 100); // => 1

Date

  • 日付や時刻を扱うことが可能なオブジェクト
  • Date.now: 現在時刻のunixタイム(ミリ秒)を取得する
  • Date.parse: 引数の日付の文字列からunixタイム(ミリ秒)を取得する
  • new演算子を使ってインスタンスを生成できる
Date.now(); // => 1473093711455
Date.parse('1983-07-14 07:29:45'); // => 426983385000

new Date(); // => 2016-09-05T17:23:22.198Z
new Date('1983-07-14 07:29:45'); // => 1983-07-13T22:29:45.000Z

module.exports

  • オブジェクトをエクスポートする
    • module.exportsにオブジェクトを代入
    • モジュールとして利用できるようになる
  • エクスポートしたオブジェクトは、ファイルをインポートすることで利用できる
    • 自作モジュールをインポートする場合は相対パスで指定
  • Node.jsで使うことができる
    • requireと同じく、ブラウザ向けのコードではwebpackを使うことでモジュールを利用できる
calc-module.js
module.exports = {
  add(a0, a1) {
    return a0 + a1;
  },
  sub(a0, a1) {
    return a0 - a1;
  }  
};
hoge.js
const calc = require('./calc-module');
calc.add(1, 2); // => 3

// 分割代入と組み合わせることも可能
const {add, sub} = require('./calc-module');
sub(1, 2); // => -1

演習

  • Userクラスを別のファイルに分けてインポートしてみる
  • UserListクラスにフィルタやソートの機能を持ったメソッドを追加してみる
  • APIの2ページ目も取得して、配列を結合してみる

おまけ


RegExp

const re = /^([0-9]{3})-([0-9]{4})$/
const match = '525-8577'.match(re);
if (match) {
  const [all, ...capture] = match;
  // マッチした文字列全体
  console.log(all); // 525-8577
  // ()で囲った部分にマッチした文字列
  console.log(capture); // [ '525', '8577' ]
}

オブジェクトに直接メソッドを追加するリスク

  • オブジェクトに関数を追加すると、オブジェクトの数だけ関数が生成される
  • クラスから生成したオブジェクトは、自身にプロパティがない場合は、prototypeのプロパティにアクセスしに行く
    • 関数を共通利用でき、関数の生成も1回のみ
    • classの中で定義したインスタンスメソッドはprototypeに生成される
function addFullMethod(name) {
  return Object.assign(name, {
    getFull() { 
      return `${name.first} ${name.last}`;
    } 
  });
}

class Name {
  constructor(name) {
    Object.assign(this, name);
  }

  getFull() {
    return `${this.first} ${this.last}`;
  }
}

const tarou = addFullMethod({first: 'tarou', last: 'abe'});
const jirou = addFullMethod({first: 'jirou', last: 'abe'});
// それぞれのオブジェクトにgetFullがある
console.log(tarou); // { first: 'tarou', last: 'abe', getFull: [Function: getFull] }
console.log(jirou); // { first: 'jirou', last: 'abe', getFull: [Function: getFull] }
// それぞれのgetFullは別の関数オブジェクト
console.log(tarou.getFull === jirou.getFull); // false

const saburou = new Name({first: 'saburou', last: 'abe'});
const shirou = new Name({first: 'shirou', last: 'abe'});
// オブジェクトにはgetFullは存在しない
console.log(saburou); // Name { first: 'saburou', last: 'abe' }
console.log(shirou); // Name { first: 'shirou', last: 'abe' }
// getFullの参照先は同じ関数
console.log(saburou.getFull === shirou.getFull); // true
// 実体はName.prototypeの中にある
console.log(Name.prototype.getFull); // [Function: getFull]

ES2015でのモジュール(ES module)

  • ES2015ではモジュールの仕様が決まっている
    • Node.jsのrequiremodule.exportsとは別
    • 構文は決まっているが、まだ実装はされていない
    • Babelを使ってES5以前のコードに変換(トランスパイル)することで利用できる
calc-module.js
function add(a0, a1) {
  return a0 + a1;
}
function sub(a0, a1) {
  return a0 - a1;
}
const calc = {
  add,
  sub
};

// エクスポート
export default calc;
export {add, sub};
module.js
// export defaultの方をインポート
import calc from './calc-module.js';
calc.add(1, 2); // => 3

// export {...}の方をインポート
import {sub} from './calc-module.js';
sub(1, 2); // => -1

Babel

  • ECMAScriptの新しい機能をサポートしていない実行環境でも動かせるようにコードを変換してくれるツール
    • ES2015 → ES5以前のコードに変換
  • 古めのブラウザでもES2015の機能を使いたい場合はこれを使うといい

Webpack

  • モジュールを解決して、いくつかのファイルにまとめるツール
  • ブラウザ向けのコードでもnpmのモジュールが使える
  • minifyやsourcemap出力などのオプションがあり、非常に便利
  • Babelと一緒に使われることが多い

ESLint

  • JavaScript向けの構文チェックツール
    • カスタマイズ制が高い
    • チェックのルールはプラグイン形式
  • 構文が正しいかどうかだけでなく、コーディングスタイルのチェックも可能
  • カスタマイズ機能を持つエディタにはESLintを使うためのプラグインが用意されてることが多い
  • eslint-config-airbnbのルールをベースにカスタマイズするのがオススメ
    • カスタマイズしない場合はルールが厳しすぎて少し辛い

参考