Ⅳ. ES6によるBotの実装
0. ES6とは
そもそもESとは
ESとはECMAScriptの略称で、標準化団体のEcma Internationalによって標準化された言語仕様です。
Webブラウザ上で動くJavaScriptは基本的に、このECMAScriptの仕様をもとに実装されています。
ECMAScript 6
ECMAScript 6 (公式名称: ECMAScript 2015)は、2015年6月17日に採択された最新のECMAScriptで、多くの新機能が追加されています。
なお、次期バージョンのECMAScript 7 の策定も開始されています。
各ベンダのECMAScriptの対応状況
https://kangax.github.io/compat-table/es6/
トランスパイル
ES6のソースをES5などのソースに変換すること。
トランスパイルを行うツールをトランスパイラーと呼ぶ。
トランスパイラーにはBabelやtraceure-compilerなどがある。
1. ES6の新機能をちょこっと紹介
詳しく知りたい方は以下などを参照してください。
1.1. let, const
let により変数を宣言することでブロックスコープが作れる。
// ES5
if (true) {
var i = 1;
}
console.log(i); // 1
// ES6
if (true) {
let i = 1;
}
console.log(i); // ERROR
const により変数を宣言することで、値の再代入が不可能な変数(定数)を定義できる。
const HOGE = 'hoge';
HOGE = 'fuga'; // ERROR
1.2. class構文
JavaScriptはプロトタイプベースのオブジェクト指向言語なので、今までクラスという概念はありませんでしたが、クラスベースの方が理解しやすいため、ES6からclass構文が取り入れられました。
class Human {
// コンストラクタ作る際には必ずconstructorメソッドを利用する。
// また、1つのクラスに 1つしか定義できない。
constructor(name) {
this.name = name;
}
// メソッド
hello() {
console.log('My name is ' + this.name);
}
// static キーワードによってクラスメソッドを定義できる
static num_of_hands() {
console.log(2)
}
}
1.3. 関数
1.3.1 アロー関数
無名関数を定義する際、従来の「function」で定義していた関数を「=>」で簡易的に定義することができるようになりました。
ただし、functionで定義された関数と動作が異なる点もあるので、注意が必要。
cf. http://analogic.jp/arrow-function/#immediate-function
従来のfunctionによる関数定義
// ES5
function (arg1, arg2) {
// 処理
}
アロー関数 基本形
// ES6
(arg1, arg2) => {
// 処理
}
アロー関数 その2
// 処理が一行で戻り値の場合、処理を囲む{}とreturnを省略できる
(num1 + num2) => num1 + num2
/*
以下と同じ
(num1, num2) => {
return num1 + num2;
}
*/
アロー関数 その3
// 引数が1つの場合、()を省略できる
arg => {
// 処理
}
即時実行
((arg1, arg2) => {
// 処理
})();
1.3.2 引数のデフォルト値
let func = (arg = 'default') => {
console.log(arg);
};
func(); // default
func(undefined); // default
func(null); // null
func('no default'); // no default
余談ですが、JavaScriptは引数が定義されている関数を引数なしで呼び出せます。
1.3.3 可変長引数
let func = (...args) => {
// 引数argsはArrayオブジェクト(配列)として扱われる
for(let val of args) {
console.log(val);
}
}
func('hoge', 'fuga', 'piyo');
// hoge
// fuga
// piyo
※ しれっと for...of 使ってますが、ES6から追加された値を列挙する機能です。
ES5にも for...in という似たような構文がありますが、こちらは値ではなくプロパティの列挙で、prototypeチェーンまで遡って列挙する点で異なります。
1.4. テンプレート文字列
' (シングルクォーテーション)や" (ダブルクォーテーション)の代わりに` (バッククォーテーション)を使うことで、テンプレート文字列を扱えます。
変数の埋め込み
let name = 'makoto';
console.log(`Hello, ${name}!!`); // Hello, makoto!!
// 従来の場合、以下のように文字連結するしかなかった
console.log('Hello, ' + name + '!!');
改行の表現
let lines = `line1
line2
line3`;
console.log(lines);
// line1
// line2
// line3
// 従来の場合、エスケープシーケンス'\n'でしか表現できなかった。
lines = 'line1\nline2\nline3'; // 直感的に分かりにくい
1.5. 分割代入
配列やオブジェクトを要素分割して、一挙に代入することができるようになりました
配列の分割代入
let array = [1, 2, 3];
let [hoge, fuga, piyo] = array;
console.log(hoge, fuga, piyo);
// 1
// 2
// 3
/* 従来の場合
var hoge = array[0],
fuga = array[1],
piyo = array[2];
*/
オブジェクトの分割代入
let obj = { hoge: 1, subObj: { fuga: 2, piyo: 3 } };
let { hoge, subObj, subObj: { fuga, piyo } } = obj;
console.log(hoge, fuga, piyo, subObj);
// 1
// 2
// 3
// [object Object] {
// fuga: 2,
// piyo: 3
//}
/* 従来の場合
var hoge = obj.hoge,
fuga = obj.subObj.fuga,
piyo = obj.subObj.piyo,
subObj = obj.subObj;
*/
分割代入の活用例
// ipアドレスの分割
let ipaddr = '192.168.10.200';
let pattern = /^(\d{1,3}?)\.(\d{1,3}?)\.(\d{1,3}?)\.(\d{1,3}?)$/;
let [whole, oct1, oct2, oct3, oct4] = pattern.exec(ipaddr);
console.log(whole); // 192.168.10.200
console.log(oct1); // 192
console.log(oct2); // 168
console.log(oct3); // 10
console.log(oct4); // 200
1.6. Map
// Mapのインスタンス生成
let map = new Map();
// 要素の設定
map.set('hoge', 'ほげ');
map.set('fuga', 'ふが');
map.set('piyo', 'ぴよ');
map.set(0, 'ゼロ');
// 要素の参照
console.log(map.get('hoge')); // hoge
// キーの存在判定
console.log(map.has(0)); // ture
console.log(map.has('0')); // false
// 要素の数
console.log(map.size); // 4
// キーの列挙
for(let key of map.keys()) {
console.log(key);
}
// hoge
// fuga
// piyo
// 0
// 値の列挙
for(let key of map.values()) {
console.log(key);
}
// ほげ
// ふが
// ぴよ
// ゼロ
// キーおよび値の列挙
for(let [key, value] of map) {
console.log(`key=${key}, value=${value}`);
}
// key=hoge, value=ほげ
// key=fuga, value=ふが
// key=piyo, value=ぴよ
// key=0, value=ゼロ
// キーの削除
map.delete(0);
for(let [key, value] of map) {
console.log(`key=${key}, value=${value}`);
}
// key=hoge, value=ほげ
// key=fuga, value=ふが
// key=piyo, value=ぴよ
// 要素の全削除
map.clear();
for(let [key, value] of map) {
console.log(`key=${key}, value=${value}`);
}
※ 連想配列(オブジェクト)との違い
連想配列
- キーに利用できるのは文字列のみ
- 要素数の取得が面倒
Map
- 文字列以外もキーとして扱える
- sizeプロパティで容易に要素数が取得できる
1.7. Set
// Setのインスタンス生成
let set = new Set();
// 要素の追加
set.add(0);
set.add(1);
set.add(2);
set.add('0');
set.add(1); // 重複する値は追加されない
// 要素数の取得
console.log(set.size); // 4
// 要素の存在を確認
console.log(set.has('0')); // true
console.log(set.has('1')); // false
console.log(set.has(1)); // true
// 要素の列挙
for(let value of set) {
console.log(value);
}
// 0
// 1
// 2
// "0"
// 要素の削除
set.delete(2);
for(let value of set) {
console.log(value);
}
// 0
// 1
// "0"
// 要素の全削除
set.clear();
for(let value of set) {
console.log(value);
}
1.8 import / export
ES5までは言語仕様としてモジュール化をサポートしていませんでしたが、ES6ではモジュール化をサポートしています
moduelA.js
// モジュールとして利用したい関数(変数、クラス)にexportキーワードを付与する
export function add(num1, num2) {
return num1 + num2;
}
export function sub(num1, num2) {
return num1 - num2;
}
// このメソッドはexportしていないので、モジュール外部から参照不可
fuction func () {
// ...
}
moduleB.js
export const HOGE = 'hoge';
export const FUGA = 'fuga';
const PIYO = 'piyo'; // この定数はexportしていないので、モジュール外部から参照不可
main.js
import {add, sub} form 'moduleA';
import {HOGE, FUGA} form 'moduleB';
console.log(add(1, 1)); // 2
console.log(sub(1, 1)); // 0
console.log(HOGE); // HOGE
console.log(FUGA); // FUGA
モジュールの全てimportする場合
import * as alias from 'moduleA';
console.log(alias.add(1, 1)); // 2
console.log(alias.sub(1, 1)); // 0
2. babelによるトランスパイル
babelによるトランスパイルは以下のコマンドで行う
babel --presets [出力形式] [src path] -o [output path]
例)ES6で書かれたscripts/es6/es6.jsをes5に変換して、scripts/es5.jsとして出力
babel --presets es2015 scripts/es6/es5 -o scripts/es5.js
トランスパイルについて注意すること
トランスパイルはあくまで、ES6の機能をES5で実現しているだけなので、ES5の言語仕様では実現できないES6の機能があることは留意しておく必要があります。
cf. http://azu.github.io/slide/nodejs-es6/how-to-learn.html
以下はトランスパイラでは実現できない機能の一部
- SubClass
- Proxy
- Symbol
- etc...