この記事は、
- ES6を書いてる時に、参考になった入門的な書き方
- ES6を書いてる時に、この書き方は便利だな~と感動した書き方
- ES6を書いてる時に、自分がとても役立った書き方
をまとめたものです。
基本文法、Class、Promise、Webpack の4段構成です。
長いので、右下の目次から知りたい情報は辿るのをオススメします。
#基本文法
変数
JavaScirptでは var
を使いますが、ES6では const
と let
を使います。
なお、 'use strict'
が必要です。
-
const
-> 再代入不可能な 変数を宣言 -
let
-> 再代入可能な 変数を宣言
なお、同時に複数宣言するときは配列を使って表現します。
const num = 10;
const [a,b,c] = [1,2,3];
console.log(a,b,c);
ES6では、基本9割近くを const
の変数宣言で書くのが望ましいと言われています。
再代入が出来ないimmutableな制約は素晴らしいです。
ただし、悲しいことに、配列の const
宣言は直感とは反する動きをするので注意が必要です。
const arr = [];
arr.push(1); // push出来てしまう。
arr.push(2);
console.log(arr); // [1, 2]
配列
[]
でくくります。詳しくは下のページを見れば十分なので割愛します。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array
が、mapとforEachだけは説明します。この2つがあるため、ES6ではfor文はほとんど使いません。
共にループをしますが、mapは値を返す、forEachは値を返さないという違いがあります。
const a = [1,2,3,4,5];
let b=0;
a.forEach(i => {b += i})
const c = a.map(i => i*2)
console.log({b, c});
console出力
Object {
"b": 15,
"c": Array [2, 4, 6, 8, 10]
}
また、ES6の配列は...
で展開できます。
console.log([...[1,2,3], ...[4,5,6]]); // [1,2,3,4,5,6]
Range
Rangeを作りたい場合、Array.fromを使うと、今までよりかはちょっと手軽な方法で記述可能になります。
Array.from(Array(5).keys()) // [0,1,2,3,4]
Array.from({length:5}, (k,v)=>v); // [0,1,2,3,4]
Object(連想配列)
javascript同様に、Object は {}
の形で宣言できます。いわゆる連想配列です。
const c='C';
const hoge = {a:1, b:'b', c};
console.log(hoge); // Object {"a": 1, "b":"b", "c": "C"}
ここで注目すべきはc
です。
すでに変数として宣言されているものがあり、キー名と変数名が同一であればvalue
を省略する事ができます。
これは地味に超便利です。
また、配列同様にObjectも...
で展開できます。
const hoge= {a:1};
const fuga= {...hoge, b:2};
console.log(fuga); // {"a": 1, "b": 2}
文字列内での変数展開
文字列内に変数を埋め込みたいときは、`を使い、${}
で囲むことで文字列内で展開できます。
const a=1, b=2, c=3;
console.log(`${a}, ${b}, ${c}`) // "1, 2, 3"
Map と Set
今まで配列と連想配列しか無かった世界にもついにMapとSetが来ました!
ただし、ほとんどのブラウザがまだサポートしてないので、polyfillをしてあげないといけません。
Mapは連想配列よりデータのループが扱いやすいです。
const a = new Map([['a', 10], ['b', 20]]);
a.set('c', 30);
console.log(a.get('b'));// 20
a.delete('c');
console.log(a.has('a'), a.has('c')); // true, false
a.forEach((key, value) => {
console.log(key, value);
})
getElementsByClassNameで取得した要素のループ
さて、ES6はJavaScriptなので、当然 HTML要素を使用することも多いでしょう。
配列が[...] で展開できるのを利用すると綺麗に書けます。
[...document.getElementsByClassName('class-name')].forEach(elm => {
elm.addEventListener('click', () => {
console.log('click');
});
})
(なお、余談ですが、カスタムイベントは EventEmitter
が便利です。)
jsonの取得
jsonの取得に特別なメソッドは必要ありません。
moduleを使う時と同じように、 require()
を使うと、Objectの形で取得できます。超簡単です。
const json = require('hoge.json');
ブラウザ上でコードを書く
ES6のスクリプトは、以下のように babeljs のページを使うと簡単に動作検証ができます。
ブラウザ上で簡単にコードが書けるのもES6の魅力です。
なお、最近はChromeのConsoleでもふつーにES6が書けるようになりました。
便利~~~
サーバーサイドでスクリプトとして実行する
(ES6とはちょっと離れた話になります。)
node が使えれば、shなんて面倒なものを使わずに ES6で全てのスクリプトを書く事が出来ます。
hoge.js
#!/usr/bin/env node
'use strict';
const a = 'Hello World';
console.log(a);
node hoge.js // Hello Worldが出力される
素敵ですね。
その他
- Generator
- 処理を途中で中断できる
- 乱数やカウント、複数回バラバラに実行するような再起関数などで使える
- async/await
- Promise に近いもの。非同期処理を便利に扱える品物。
Class
クラス変数
ES6はクラス変数として const
での定数が使えないため、再代入不可能なクラス変数を使いたければClass定義の外に書くと良いです。
再代入可能なクラス変数は、 this.hoge
の形で書けます。
const a = 10; // 定数化
class Car {
constructor() {
this.b = 20;
}
}
クラスの上に書いても、 module.exports
しなければ、グローバル変数にはならないのでスコープを短く出来ますし安全です。
関数の引数の展開
ES6では関数を () => {}
の形で書けます。 function(){}
と基本的には同じ意味です。アロー関数と呼びます。
const show = (str) => {
console.log(str);
}
クラスでの関数の場合、以下の形で定義できます。
show(str) {
console.log(str);
}
ES6のオブジェクトの展開の機能を使うことで、run2()
のようにより分かりやすく引数を表現出来ます。
class Car {
constructor(gasoline) {
this.gasoline = gasoline;
}
run(name, status) {
this.run2({name ,status, driver:'unknown'})
}
run2({name ,status, driver}) {
this.gasoline--;
console.log(`${name} is ${status}. gasoline=${this.gasoline} driver is ${driver}.`);
console.log({name ,status, driver});
}
}
const car = new Car(10);
car.run('hoge', 'normal');
car.run2({name: 'hoge', status: 'normal', driver:'me'})
=>
console出力
"hoge is normal. gasoline=9 driver is you."
Object {
"driver": "you",
"name": "hoge",
"status": "normal"
}
run2()
のようにObjectを渡すようにすると、使用側はどの引数にどの値を入れてるかわかりやすくなり、可視性の向上が期待できます。
また、Objectなので、引数の順番もいかようにも変えれます。
car.run2({driver:'me', status: 'normal', name: 'hoge'})
でもOKです。
console.log()
での出力も、なにか変数を出力する時は {}
で括ってObject化するだけでprintデバッグがしやすくなります。
引数のデフォルト値
引数にデフォルト値を指定できます。
関数の通常の引数はもちろん、Objectの一部のみにもデフォルト値を適用できます。
class Car {
static run(a=10) {
console.log({a})
}
static run2({a=10, b=20}) {
console.log({a, b});
}
}
Car.run(); // {"a": 10}
Car.run(1); // {"a": 1}
Car.run2({a:5}) // {"a": 5, "b": 20}
staticクラス化
class Car {
static run() {
console.log('run');
}
}
Car.run();
クラス変数を使用しないクラスは、メソッドを全部static化することで、Staticクラスとして利用できます。(当然 new 不要)
継承
extends で継承できます。
class Car {
run() {}
}
class Track extends Car {
constructor() {
super();
}
run() {
super.run();
}
}
継承できるのは一つのクラスのみです。複数のクラスを多重継承することは出来ません。
(本来はmodule毎にClassを分けるのが望ましいですが、ここでは省略して2つのクラスを並べています。)
Promise
同期・非同期処理を一緒に書ける便利メソッド
なんといっても、ES6の一番の見所はPromiseです。
Promiseは、javascriptの難関であった、同期・非同期処理をシンプルに書きやすくしてくれます。
これを使うことでコールバック地獄を解決する事が出来ます。
new Promise((resolve, reject) => {
resolve({res:true});
}).then(({res}) => {
console.log(res);
return false;
}).then(res => {
console.log(res);
}).catch(e => {
console.error(e);
}).then(() => {
console.log("エラー後でも通る");
});
console
true
false
Promiseのメソッド内では、正常終了のレスポンスをresolve
、異常終了のレスポンスをreject
で返すことができ、
それを次のthenメソッドで受け取れます。
また、thenメソッドのreturnによる返り値は、次のthenメソッドで受け取れます。
Promise内のエラーは、catchで受け取れますし、その後にthenメソッドを繋ぐことも出来ます。
それぞれ繋ぎとして変数や関数の返り値として返すことも当然出来、チェーンメソッドとして連結できます。
const a = new Promise((resolve, reject) => {
resolve({res:true});
}).then(({res}) => {
// thenの中でPromiseを作って、返すことで、結果を繋ぐことが可能
return new Promise((resolve, reject) => {
reject('error');
});
});
return a.then(res => {
console.log(res);
}).catch(e => {
console.error(e);
});
例外catchの罠
なお、残念なことに、Promiseを使っても、非同期処理でthrowされた例外はcatch出来ません。
例えば、Promise内で、setTimeoutをして遅延させた例外throwを送っても、catchする事が出来ません。
原因はtry catchでは非同期処理をcatch出来ないためです。JSの未来に期待しましょうorz
const a = new Promise((resolve, reject) => {
setTimeout(() => {throw('err')}, 100);
}).catch(e => {
console.error(e); // ここではキャッチされない
});
Promise.resolve()
たまに、普通に関数の結果をチェーンで繋ぎたい時もあるでしょう。
でもそんな時にいちいちnew Promise(resolve, reject)
を作り、結果をresolveで囲むのは面倒です。
最初のPromiseの結果をresolveにしてしまうことで、thenメソッドだけ扱える便利君になります。
return Promise.resolve().then(() => {
return MyFunc();
}).then(res => {
...
});
MyFunc()
をnew Promise
で括ろうと思ってる方もいると思います。しかし、
- MyFuncの結果がresolveとは限らない。 rejectかもしれない。
- resolve()を忘れるとthen以降のチェーンメソッドが動かないという致命的なバグが埋め込まれる
という理由からオススメしません。特に2番目は凶悪です。
なので関数を呼び出す、関数の結果を返す時は、thenの方がコーディングとしては安全です。
但し、thenの中が非同期(ex setTimeout)の場合は、then句では同期的に待ってくれないので、new Promise()
を使う必要があります。
MochaのテストのためにPromiseは関数の返り値としてreturnで返す
ところで、私は関数内でPromiseを返すつもりで、Promiseの結果をreturnしてしまいました。
このように関数内でPromiseをreturnすると、Promiseのテストが書けます。
最近のMochaのassertメソッドは、Promiseの結果がreturnされる事がわかってる場合、その結果が返ってくるまで待ってくれます。
なので、必ずPromiseを返すメソッドはreturnしましょう。
http://efcl.info/2014/0314/res3708/
並列処理(all)
上のPromiseの書き方は、順番に同期処理を行う方法でしたが、並列して同時に処理を行うことも出来ます。
const a = new Promise(r => r(1));
const b = new Promise(r => r(2));
const c = new Promise(r => r(3));
Promise.all([a,b,c]).then(([r1, r2, r3]) => {
console.log({r1, r2, r3});
});
APIアクセス等、時間のかかる処理を行うときは、allを使うことで処理を並列に行えるため、とても便利です。
Promise.all([1, 2, 3].map(func));
のようにすると、配列の各要素にたいしてPromise処理を同時に行うことも出来ます。
Ajax
余談ですが、npmライブラリのAjaxクライアントはまだ決め手が無いという状況です。
理想的には、APIレスポンスは基本非同期処理のため、Promiseで結果が得られてほしいところです。
色々探しましたが、 axios というライブラリがとても使い勝手が良かったです。
https://github.com/mzabriskie/axios
参考: http://qiita.com/nekobato/items/0cd3ed1742310f74653c
細かい設定ができるのもそうですが、結果がPromiseで返ってくるため、ES6と非常に相性が良いのが特徴です。
Q
ここまでPromiseの話をしてきましたが、実際にコードに適用するのであれば、Qを使うのがベストかなと思います。
https://github.com/kriskowal/q
const Q = require('q')
で扱えるシンプルな品で、Promiseよりも使いやすいと評判です。
やはり非同期処理は細かなニーズが必要なので、こういうライブラリはありがたいですね。
delayのように時間を止めるメソッドもあります。
あと、Promiseより40倍速いとかいう噂も・・・(保証はない)
Webpack
Webpackは開発用ツールで表には出てきません。昔で言うgruntやgulpの代わりでビルド用ツールとでもいうべきでしょうか。
開発でバッグを助けたり、最終的な成果物のjsをconcat+minifyして作成できます。
webpack-dev-server でホットリロードが出来る
webpack-dev-serverを使うと、コードを編集すると自動的にブラウザのページをリロードしてくれます。
いわゆるHot Module Reload
です。神機能です。
module.exports = {
entry: [
'babel-polyfill', // polyfillです。これを書くとES6未対応ブラウザでもES6のコードが書けます。
"webpack/hot/dev-server",
"webpack-dev-server/client?http://localhost",
],
module: {
loaders: [
{
test: /\.js$/,
exclude: /(node_modules)/,
loader: 'babel',
},
],
},
output: {
filename: 'build.js',
path: path.resolve(__dirname, '/build/'),
publicPath: path.resolve(__dirname, '/'),
},
};
起動はpackage.jsonのスクリプトに、
"dev": "webpack-dev-server --inline --hot"
と書けば、npm run dev
で起動します。
上の書き方は、一部moduleのみ更新可能な場合はそのmoduleだけ更新され、不可能なときはページ全体を更新します。
グローバル変数化したい、コンパイル時に変数を埋め込みたい
あまり良くない事でですが、変数やクラスをグローバル変数として使用したいことがあります。(例えば別ライブラリで使いたい時等)
exposeを使うことで、対象のmoduleをGlobal化出来ます。
require('expose?Global!./src/Global'); // `src/Global`というModuleを外に出せる。 完全Global化
ただし、ほとんどのケースではGlobal化は必要ないでしょう。
webpack
を使用している場合、 ProvidePlugin
を使うことで擬似グローバル化が出来ます。
ライブラリの外では使用できませんし、ターミナルからも使えませんが、同じoutputのコード全体で使用できます。
また、環境ごとに設定値をコードに埋め込みたい場合は、 DefinePlugin
を使うと、ES6コンパイル時に外から文字列をコードに埋め込むことが出来ます。
webpack.config.jsでは、
plugins: [
new webpack.ProvidePlugin({
Global: 'Global' // Globalというmoduleを、関連コード全体で使うことが出来る。
}),
new webpack.DefinePlugin({
__HOGE__: `"hoge"`, // コード内の__HOGE__が"hoge"として使える。
__FUGA__: `1`, // コード内の__FUGA__が1として使える。
}),
],
詳しい設定は、 https://webpack.github.io/docs/webpack-dev-server.html を見て下さい。
スクリプト化
gruntやgulpのように、簡易スクリプトをpackage.jsonに書くことで定義できます。
"scripts": {
"hoge": "node hoge.js"
}
$ npm run hoge
Webpack 設定を環境で分ける。
例えば、本番環境とdev環境でビルド方法を分けたいといったパターンはあるでしょう。
package.jsonのスクリプトを使うことで代用することが出来ます。
"scripts": {
"build-dev": "webpack --config webpack.config.dev.js",
"build-live": "webpack --config webpack.config.live.js",
}
webpackの設定ファイルの中身は、ただのJSのObjectなので、共通要素は module.exportで出力し、
webpack.config.dev.js
, webpack.config.live.js
の中でrequire
で取得することで、
複数の共通要素をまとめつつ、webpack.config.dev.js
, webpack.config.live.js
を分離できます。
(ようするに、webpack.config.dev.js のソースの中で共通要素はrequire
すればいい。)