React を触っていると、ES6 の記法をよく使う(見る)ので、ES6 やるか〜ってなった。
ひとまずバベルの塔(TOWER-OF-BABEL)に登ってみたり、Web で情報を仕入れて覚書してみた。
さて、今回は Babel というトランスパイラを使って ES6 をがりがり書いていく。
ということで・・・
Babel のセットアップをする
まずはコマンドラインで Babel を動くようにしたい。
下記コマンドを実行すると、babel
と babel-node
の 2 つのコマンドが有効になる。
$ npm i -g babel-cli
下記のファイルを hello.js のような名前で保存し、
console.log(`Hello, Babel!`);
babel-node で実行する。下記コマンドを叩くと実行できる。
`$ babel-node <FILE NAME>`
下記の公式 REPL でも Babel 実行結果を確認できる。ちょっとした✓をしたいときに便利。
Babel REPL
ES6 基礎
let と const の特徴
ブロックスコープを作ってくれる
var と違い、関数のブロック以外の、例えば for や if ブロック内でもブロックスコープを作ってくれる。
let は再宣言できない
再代入は可能。
let foo = 'f';
foo = 'foo' // OK
let foo = 'foomoo'; // Identifier 'foo' has already been declared
const は再宣言も再代入もできない
const PI = 3.1415;
PI = 3; // TypeError: Assignment to constant variable.
const PI = 'pi'; // もちろんエラー
ちなみに・・・オブジェクトの要素は再代入可能(注意)。
Object.freeze() を利用すればオブジェクトの要素も再代入不可にできる。
2017.3.30: 追記
const myBooks {
'slum dunk': 28,
'soten koro': 20
};
myBooks['soten koro'] = 18; // これは OK
// Object.freeze() で再代入できなくする
// 'use strict' は必須
'use strict';
const object = Object.freeze({
key: 'foo'
});
object.key = 'bar'; // TypeError
ES6 で書くなら ver は混乱の種になりうるため、 let と const を使っていきたい。
下記のように。
// const は定数やオブジェクト、関数宣言時に使う
const MAX_USERS = 24;
const ALLOW_EDIT = true;
const ALLOW_DELETE = false;
// let はその他のケース。ver の替わり。(ブロックスコープの外でも使いたい変数を宣言する場合は別だが)
let myAge = 48;
let myScore = 10000000000;
オブジェクトリテラル記述時、プロパティ名に式を評価させることができる
これまで
var prop = 'fuga';
var obj = {};
obj[prop] = 'hoge'; // プロパティ名が変数(動的な値)
ES6
var prop = 'fuga';
var obj = {
[prop]: 'hoge';
};
これを Computed Property という。
こんなのも可。↓
var obj = {
[(() => 'fuga' + 'hoge')()]: 'pero' // 無名関数を作って中で実行させている
};
// こんなのも↓
var arg1 = +process.argv[2];
var arg2 = +process.argv[3];
var sum = arg1 + arg2;
var obj = {
[arg1 % 2 === 0 ? 'even' : 'odd']: arg1,
[sum]: sum
};
このように、動的にプロパティ名(キー)が切り替わる場合でも Computed Property をうまく利用すれば演算結果などを、例えば一時的に変数に持たせなくても済むようになる。
arrow 関数について
記法
// このケースは無名関数の省略記法
() => {
console.log('hoge');
};
// 上のはこれと同じ
(function() {
console.log('hoge');
});
色々省略できる。
引数が一つだけの場合は ()
を、処理内容のステートメントが一つだけの場合は {}
をそれぞれ省略できる。また、後者はステートメントがそのまま返り値になるため return
も不要となる。
// これが
const log = function(a) {
console.log(a);
};
// こうなって
const log = (a) => {
console.log(a);
};
// こうもできて
const log = (a) => console.log(a);
// こうもできる
const log = a => console.log(a);
arrow 関数内の this
通常は関数呼び出し時に this が決まるが、 arrow 関数を字句解析した時点で固定する。(レキシカル)
process.argv で コマンドライン引数を受け取る
下記ファイルを実行する。
const name = process.argv[2];
console.log('Hello, ' + name + '!');
process.argv[2]
(第三引数を指定すること)でコマンドライン引数の1つめを受け取っている。
$ babel-node hello.js Octcat
// Octcat がコマンドライン引数
> Hello, Octcat! //出力
クラスを定義する
下記は ES5 におけるクラスライクなもの。
var Person = function() {
return this.name;
};
Person.prototype.getName = function() {
return this.name;
};
Person.prototype.setName = function(name) {
this.name = name;
};
var alice = new Person('alice');
alice.getName(); // => alice
alice.setName('bob');
alice.getName('bob'); // => bob
ES6 のクラスを使ってみる。
class Person {
constructor(name) {
this.name = name;
}
getName() {
return this.name;
}
setName() {
this.name = name;
}
}
var alice = new Person('alice');
alice.getName(); // => alice
alice.setName('bob');
alice.getName(); // => bob
extends を使ってクラスを継承する
クラスが存在するということは継承も存在する。ということで下記がそのサンプル。
// Character クラス
class Character {
constructor(x, y) {
this.x = x;
this.y = y;
this.health_ = 100;
}
attack(character) {
if (!(character instanceof Character)) throw new Error('');
character.health_ -= 10;
}
}
// Character クラスを Monster クラスに継承
class Monster extends Character {
constructor(x, y, name) {
// 親クラスの持つメソッドやメンバーを小クラスから呼び出す場合には super というキーワードを利用する
super(x, y);
this.name = name;
}
attack(character) {
// super で親クラスの attack を呼ぶ
super.attack(character);
character.health_ -= 5;
}
}
モジュールを分割する
export と import が使えるようになった
Node.js/io.js には commonjs が組み込まれているが、ブラウザでは JS ファイルをモジュールの分割ができなかったため、 require.js や browserify といったライブラリを使ってこの問題を解決していたらしい。
基本的には commonjs の require と同じ。
下記はサンプル
// Export 側のファイル (Message.js とする)
export const message = 'Hello, Babel!';
// Import 側のファイル
import {message} from './Message';
// Import した Message.js の message 変数が利用できるようになった
console.log(message); // => Hello, Babel!
ちなみに commonjs では下記のようになる。
// Export
export.message = 'Hello, Babel';
// Import
var message = require('./Message').message;
console.log(message); // => Hello, Babel!
(参照元ファイルの)複数の変数をインポートする。
// Export (Math.js)
export const PI = 3.141592;
export const sqrt = s => {...};
export const square = x => ...;
//Import
import {PI, sqrt, square} from './Math';
// 当然ですね・・・
もう一つのエクスポート、デフォルトエクスポート (default export)
通常のエクスポートをデフォルトエクスポートに書き換えてみる。
// Export (Hoge.js)
const greeting = 'Ohayo';
const name = 'Neko';
const version = 'v1.0.0';
export const obj = {
greeting: greeting,
name: name,
version: version
};
// Import
import {obj} from './Hoge';
console.log(`${obj.greeting} ${obj.name} ${obj.version}`); // => Ohayo Neko v1.0.0
デフォルトエクスポートを使ったらこうなる。
// Export (Hoge.js)
const greeting = 'Ohayo';
const name = 'Neko';
const version = 'v1.0.0';
export default {
greeting: greeting,
name: name,
version: version
}
// Import
import Hoge from './Hoge';
console.log(`${Hoge.greeting} ${Hoge.name} ${Hoge.version}`); // => Ohayo Neko v1.0.0
これは commonjs の module.exports
でモジュール分割するのと似ている・・ようだ。
for .. of 文
var res = [];
// これが for of 文
for (let element of [1, 2, 3, 4, 5]) {
res.push(element * element);
}
console.log(res); // => [1, 4, 9, 16, 25]
for .. in 文と異なり、 of に渡すのは繰り返し可能なもの(iterable)であれば良い。
例えば Symbol.Iterator で作ったものとか。
下記は Symbol.Iterator と for .. of 文を使って作ったFizzBuzz。雰囲気だけ感じてほしい。
const MAX = process.argv[2];
let FizzBuzz = {
[Symbol.iterator]() {
let current = 1;
return {
next() {
let val = current;
if (current > MAX) {
return { done: true };
}
if (current % 3 === 0) {
if (current % 5 === 0) {
val = 'FizzBuzz';
} else {
val = 'Fizz';
}
} else if (current % 5 === 0) {
val = 'Buzz';
}
current++;
return {
done: false,
value: val
};
}
};
}
};
for (var n of FizzBuzz) {
console.log(n);
}
正直 [Symbol.iterater]
についてはあまりわかっていない。特に next()
とか。勉強したい。
ジェネレータで 繰り返し可能(Iterable)なオブジェクトを作る
let fibonacci = function*() {
let currentVal = 0,
nextVal = 1;
while (currentVal < 1000) {
// destructuring (破壊) で値を swap させる
[currentVal, nextVal] = [nextVal, currentVal + nextVal];
// yield (産出(直訳)) で値を返す(return じゃないよ)
yield currentVal;
}
}();
for (let n of fibonacci) {
console.log(n);
}
何が起きているのか、筆者もわからない。
キーは下記か。
function*()
yield
ジェネレータを使って FizzBuzz を書き換えた。
const MAX = process.argv[2];
let FizzBuzz = function*() {
let current = 0;
while (current < MAX) {
current++;
if (current % 15 === 0) {
yield 'FizzBuzz';
} else if (current % 5 === 0) {
yield 'Buzz';
} else if (current % 3 === 0) {
yield 'Fizz';
} else {
yield current;
}
}
}();
for (var n of FizzBuzz) {
console.log(n);
}
デストラクチャリング(Destructuring) とかいう機能
破壊・・・か分配束縛とかなんとかと訳すことができるが野暮ったいので、デストラクチャリングで良いかなと。
これを利用することで、配列やオブジェクトで設定した値を取り出しやすくなるようだ。
まずは値を swap (入れ替え)してみよう。
// 従来
// ----------
var a = 1;
var b = 2;
// swap
var temp = a;
a = b; // 2
b = temp; // 1
// デストラクチャリングると
// ----------
var a = 1;
var b = 2;
// swap
[a, b] = [b, a]; // これだけで値が入れ替わっちゃう
下記のように配列やオブジェクトから任意の要素を取得することも可能。
// ※ 取得したい配列と同じように左辺を組む必要あり
// 配列
var [a, [b, c]] = ['Hello, ', ['Wo', 'rld!']];
console.log(a, b, c); // => 'Hello, ', 'Wo', 'rld!'
// オブジェクト
var myPoint = {x: 98, y: 196};
var {x, y} = myPoint;
console.log(x, y) //=> 98, 196
さらに。
下記は json の任意の値を取得(束縛って言い方が合っているかもしれない)
var json = {
"name": {
"first": "Sota",
"family": "Suzuki"
},
"birth": {
"year": 1986,
"month": 9,
"day": 3
}
};
var {name: {family: familyName}, birth: {day: birthDay}} = json;
// こっちでも可。こっちの方がわかりやすい気がしてる・・・
// var [familyName, birthDay] = [json.name.family, json.birth.day];
console.log(familyName); // => Suzuki
console.log(birthDay); // => 3
arguments を使わずに可変長パラメータの処理を実装する
arguments とはすべての引数が格納された配列ライクなオブジェクトであり、 配列用のメソッドが軒並み使用できない。
ES6 では引数に ...
を付けることで可変長パラメータを実現できる。
この機能を Rest パラメータ という。
Rest パラメータを使うことで arguments のような配列ライクなオブジェクトではなく、配列として可変長パラメータを受け取ることができる。
// 従来
var sum = function() {
var result = 0;
for (var i = 0; i < arguments.length; i++) {
result += arguments[i];
}
return result;
};
console.log(1, 2, 3, 4); // => 10
// ES6
var sum = (...args) {
return args.reduce((sum, n) => sum + n);
};
console.log(sum(1, 2, 3, 4)); // => 10
さらに、関数呼び出し時に配列を可変長パラメータに変換することもできる。
これを Spread コールという。
var sum = (...args) {
return args.reduce((sum, n) => sum + n);
};
var array = [1, 2, 3, 4];
// Spread コールを使用
console.log(sum(...array)); // => 10
// このケース、 ...array が [1, 2, 3, 4] へ変換されている
おわりに
便利だ。一部、例えば Symbol だったり、 Generater だったり使い所が難しそうな機能もあったが、概ね理解できた。これらが使えそうな場面があれば積極的に使っていきたい。
arrow 関数や let / const、クラス、モジュールはもう当然のごとく使っていく。
使わないとすぐ忘れてしまうので。Nodeschoolにチュートリアル系が充実していたので、まずはこういったのをひたすら数こなしていきたい。
そして勝手に指が動くようになったら、今度はドキュメントを読んで深い所までディグろうかなってスタンスでいる。