Babelで始めるES6入門

このエントリーをはてなブックマークに追加

Babelのキャプチャ

BabelとはJavaScriptの新規格であるES6 ( ECMAScript 6th Edition/ECMAScript 2015 ) で書かれたJavaScriptコードを古いブラウザでも解釈できるようにES5の書式に変換してくれるツール(JavaScriptトランスパイラ)です。

利用方法は様々ありますが今回はgulpでBabelを利用する方法と、ES6の主要な機能をBabelがどのようにES5に変換するかについて解説を行います。
(gulpに関しては、「タスクランナーgulp入門」を参考にしてください)

gulp-babelのインストール

gulpでBabelを利用するにはgulp-babelをインストールする必要があります。インストールは以下のコマンドで可能です。

sudo npm install gulp-babel --save-dev

gulpfile.jsにはBabel用のタスクを追加しておきます。今回はsrc/app.jsの内容をコンパイルしdist/app.jsに書き出すタスクを定義してみました。

var gulp = require('gulp');
var babel = require('gulp-babel');
  
gulp.task('babel', function () {
    return gulp.src('src/app.js')
        .pipe(babel())
        .pipe(gulp.dest('dist'));
});

以降は以下のコマンドを入力することでコンパイルが行われるようになります。

gulp babel

では、ES6の主要な機能とBabelがどのようにES5に変換するかを解説していきたいと思います。

Arrow Function

解りやすいところではES6からArrow Functionが導入されました。これはJavaScriptコードの中からfunctionのキーワードを省略して書くことができる記法です。

例えば以下のようにして関数barを設定できるようになりました。

var bar = () => {
  console.log("ok");
};

これをBabelは以下のように変換します。

"use strict";
 
var bar = function bar() {
  console.log("ok");
};

Babelは関数リテラルを無名関数ではなく有名関数で生成しているのでちょっと違和感がありますが、以下のように変換すると考えた方がES6の理解はしやすいでしょう。

var bar = function() {
  console.log("ok");
};

つまり、Arrow Functionとは「function() {」を「() => {」に書き換えることができる省略記法です。

引数がある場合も同様です。

var bar = (x) => {
  console.log("ok");
};

括弧の中に引数を入れることで、コンパイル後のコードにも引数が反映されています。
(以降のコンパイル後のコードでは"use strict";を省略します。)

var bar = function bar(x) {
  console.log("ok");
};

引数が1つしかない場合は()括弧も省略可能です。

var bar = x => {
  console.log("ok");
};

このコードは次のようにコンパイルされます。

var bar = function bar(x) {
  console.log("ok");
};

関数内の処理がreturn1行で済んでいる場合は{}括弧も省略できます。

var bar = x => x+1;

このコードは次のようにコンパイルされます。

var bar = function bar(x) {
  return x + 1;
};

Arrow Functionは省略記法でありなにか新しい機能を追加するものではありませんが、慣れておかないと他の人のコードを読むのですら苦労してしまいますので、積極的に利用して慣れる置くのが重要でしょう。
(慣れたら記述も早くなりますし)

let/const

letはブロックスコープ変数の定義が、constは定数の定義ができるキーワードです。これまで変数宣言に利用してたvarの変わりに利用します。

let

letはブロックスコープ変数の定義ができます。letで宣言された変数はブロックスコープつまり{から}の間でした有効でありません。

今回のサンプルではif文のブロックスコープ内でletを利用してみました、

if(true){
	let foo = "bar";
	console.log(foo);
}
console.log(foo);

Babelはこれを次のようにコンパイルします。

if (true) {
	var _foo = "bar";
	console.log(_foo);
}
console.log(foo);

これをブラウザで実行するとコンソールに以下のようなログが出力されます。

bar
Uncaught ReferenceError: foo is not defined

このように局所的にのみ利用したい場合はletを利用します。

const

constは定数を定義ができます。定数は一度定義したら変更できず再代入が不可な変数のようなものです。
慣習的にすべて大文字で、単語毎にアンダースコアで区切って定義します。

const BAR_FOO = "bar-foo";

Babelはこれを次のようにコンパイルします。

var BAR_FOO = "bar-foo";

単純にconst → varじゃ定数として意味がないのではと思うのですが、以下の様な再代入を行おうとするとBabelがコンパイル時にエラーを出してコンパイルできませんので大丈夫です。

const BAR_FOO = "bar-foo";
BAR_FOO = "bar2-foo2";

Function Arguments

関数の引数の受け渡し方にも様々な記法が追加されました。
(解説のフォーカスを引数にあてるためArrow Functionではなく関数リテラルで記述します)

可変長の引数

引数の冒頭に...と記述することにより引数を配列として取得することができます。

var bar = function(...arg){
  console.log(arg);
  console.log(Array.isArray(arg));
};
bar(1,2,3,4);

これは以下のようにコンパイルされます。

var bar = function bar() {
  for (var _len = arguments.length, arg = Array(_len), _key = 0; _key < _len; _key++) {
    arg[_key] = arguments[_key];
  }
 
  console.log(arg);
  console.log(Array.isArray(arg));
};
bar(1, 2, 3, 4);

このコードを実行した場合はコンソールに以下のようなログが出力されます。

[1, 2, 3, 4]
true

引数の初期値

引数の初期値が設定できるようになりました。引数の宣言時に=で初期値を設定することで引数がある場合は引数を、ない場合は初期値を参照するようになります。

function foo(bar = 3) {
	console.log(bar);
}
foo(4);
foo();

これは以下のようにコンパイルされます。

function foo() {
	var bar = arguments.length <= 0 || arguments[0] === undefined ? 3 : arguments[0];
 
	console.log(bar);
}
foo(4);
foo();

このコードを実行した場合はコンソールに以下のようなログが出力されます。

4
3

Generator

Generatorは実行タイミングの制御が可能な関数のようなものです。

Generatorを利用するにはbrowser-polyfill.jsを読み込む必要があります。最初にgulp利用時にインストールしたgulp-babel内の以下のパスにファイルがあるので取得しページ内で読み込んでおきましょう。

./node_modules/gulp-babel/node_modules/babel-core/browser-polyfill.min.js
<script src="dist/browser-polyfill.min.js"></script>
<script src="dist/app.js"></script>

function名の前に*(アスタリスク)をつけると実行時にGenerator Objectを生成します。Generator Objectのnextメソッドを実行するとyield宣言した箇所まで関数を実行します。

今回はgen_funという1から3までをコンソール上に出力するGenerator Objectを作成しました。

// ジェネレータを設定
function *gen_fun(){
	console.log(1);
	yield;
	console.log(2);
	yield;
	console.log(3);
}
 
// ジェネレータを実行
var gen = gen_fun();
console.log("start");
gen.next();
console.log("next");
gen.next();
console.log("next");
gen.next();>

コンパイル後のコードの前に実行結果を見てみましょう。上記のコードを実行すると以下の様な内容が今ロール状に出力されます。

start
1
next
2
next
3

ジェネレータを生成したタイミングではジェネレータオブジェクトの内容は実行されずにnextメソッドが実行されたタイミングでyield宣言した箇所まで実行し、またnextメソッドが実行されたタイミングでyield宣言した箇所まで実行しを繰り返し実行します。

ちなみにBabelでコンパイルされたコードは以下のようになっています。

var marked0$0 = [gen_fun].map(regeneratorRuntime.mark);
function gen_fun() {
	return regeneratorRuntime.wrap(function gen_fun$(context$1$0) {
		while (1) switch (context$1$0.prev = context$1$0.next) {
			case 0:
				console.log(1);
				context$1$0.next = 3;
				return;
 
			case 3:
				console.log(2);
				context$1$0.next = 6;
				return;
 
			case 6:
				console.log(3);
 
			case 7:
			case "end":
				return context$1$0.stop();
		}
	}, marked0$0[0], this);
}
 
// ジェネレータを実行
var gen = gen_fun();
console.log("start");
gen.next();
console.log("next");
gen.next();
console.log("next");
gen.next();

Object initializer

オブジェクトの宣言方法も様々な方法が追加されています。

以下のように変数からオブジェクトを生成する場合、プロパティ名を省略すると変数名が自動的にプロパティ名になります。

var bar = 0;
var foo = { bar };

これは以下のようにコンパイルされます。

var bar = 0;
var foo = { bar: bar };

これは関数からメソッドを生成する場合も同様です。

var bar = function(){
	console.log("ok");
};
var foo = { bar };

これは以下のようにコンパイルされます。

var bar = function bar() {
	console.log("ok");
};
var foo = { bar: bar };

Computed property names

プロパティ名を柔軟に指定できるようになりました。プロパティ名を[...]で囲むことで処理後の値がプロパティ名として採用されます。

var prop_name = "foo";
var foo = {
	[prop_name + "01"]:"ok"
};

これで生成されたオブジェクトfooは「{"foo01":"ok"}」

これは以下のようにコンパイルされています。
(Object.defineProperty()が利用されているのでIE9以上でしか動かない)

function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
 
var prop_name = "foo";
var foo = _defineProperty({}, prop_name + "01", "ok");

Class

最後の新しくなったClass構文を紹介。これが一番大きいアップデートではないですね

これまでのprototype継承を利用したclass書式と違い非常にシンプルにclassが定義できます。

class Person{
	//コンストラクタ
	constructor(name) {
		this.name = name;
	}
	//メソッド
	say() {
		return 'My Name is ' + this.name+'.';
	}
}
var person = new Person("Bob");
console.log(person.say());

これを実行すると「My Name is Bob.」と出力されます。

extendsを使い簡単に継承もできます。

class Man extends Person{
	//メソッド
	isMan() {
		return true;
	}
}
var man = new Man("Jon");
console.log(man.say());
console.log(man.isMan());

これを実行すると「My Name is Jon.」「true」と出力されます。

このようにBabelを利用するとES6も実案件で利用可能になります。(ターゲットブラウザにIE8が含まれている場合は使える機能は限定されますが)

ES6は次世代の規格のため、今後主流になっていくのは間違いなく学習コストは無駄にはなりませんので、是非はじめてみてください。

次回はimport / exportについて解説します。

関連エントリー

gulpとnode.jsの連携
gulpでJavaScriptの結合圧縮処理
gulp.spritesmithを便利にカスタマイズ
新MacBook ProにインストールしたCUIアプリ
Gruntで始めるWeb制作の自動化

スポンサードリンク

«SVGの利用 | メイン | babelifyで始めるES6»

このエントリーのトラックバックURL
コメント

とても参考にさせてもらっています。
コピペで動かずなんでだろうと思っていたら、ところどころ、bableって記述されてました。
タイポなら直しておいて頂けると幸いです。

投稿者:とおりすがり | 2016年8月30日 11:14

ES6入門として、ES5の構文と対比しながらでとても分かりやすかったです。
ありがとうございます。

特に、ES5 からES6 に書き換えるサンプルを乗せているサイトは多いですが、逆の感覚で読めるので、すっとはいってくる感じがしました。

『ES6
ならこんなに簡単にかけるようになる』
という入り方より、
『ES5に戻すと、こんなにたくさん書かないといけないのだよ』
というのが、楽しかったです。

投稿者:pocho | 2016年8月31日 08:46

>とおりすがりさん

タイポです。失礼しました。

投稿者:西畑一馬 | 2016年9月 8日 15:08
コメントを投稿