RailsエンジニアがES6についての解説(今更ver) & どう実際のサービスに入れていくか？の考察


## そもそもESとJSの違いは？(初心者用歴史編)

- その昔Netscapeが今のEcmaScriptの前段階であるLivescriptを開発
- Javaの人気にあやかって(マーケティング的な思考で)Javascriptと命名  
  ※最初にパーフェクトJsで知った時は衝撃でした
- Netscapeに搭載されたJavaScriptをMicrosoftがライセンスの関係で独自開発したのが、ブラウザ毎にjsの挙動が違う事の発端
- その後、標準団体がEcmaScriptを設定し、今のJavascriptのベースとなっている


| ブラウザ | Javascript エンジン |
|:-----------|------------:|
| IE       |        Chakra |
| Chrome     |      V8 |
| Opera       |        V8 |
| Safari         |          JavaScriptCore(Nitro) |
| Firefox       |       SpiderMonkey |

めっちゃ種類ありますねw
昔はこの差分をなくす為にjQueryが流行ったみたいですが、昔ほどひどくなくなったのがjQueryの人気低下の要因かと。


## 今回なんでこんなに騒がれたの？

![スクリーンショット 2015-07-27 23.10.25.png](https://qiita-image-store.s3.amazonaws.com/0/5365/e2f6ce1f-7fa3-6c40-7809-1d133e5f9fbe.png "スクリーンショット 2015-07-27 23.10.25.png")

引用：[wikipedia](https://ja.wikipedia.org/wiki/ECMAScript#.E3.83.90.E3.83.BC.E3.82.B8.E3.83.A7.E3.83.B3)


実はES5がリリースされたのは2009年12月。
という事は世間がのりピーやら新型ウィルスで騒がしかった頃ですね。
参考： [【年末特集】 2009年芸能界を振り返る 10大NEWSプレイバック!](http://www.oricon.co.jp/news/71998/full/)

ということでめちゃくちゃ久しぶりのバージョンUP。
TC39(ECMAScriptを策定してる専門委員会)によるとES7は機能毎に仕様を策定していくらしいので、スピードがあがるようです。
Jsはもはや多岐に渡って活躍する言語になったので、世間からの期待値も高いです。

ちなみに仕様策定の手順などは[ECMAScript6をまるっと学ぶ。重要用語とか、仕様策定の進め方とか、新機能とか。](http://www.yoheim.net/blog.php?q=20140403)がかなり詳しくまとめてくれています。

## ES6の対応状況は？

- [ECMAScript compatibility table](http://kangax.github.io/compat-table/es6/)によって確認可能です。

![スクリーンショット 2015-07-27 23.22.11.png](https://qiita-image-store.s3.amazonaws.com/0/5365/e4fee1ed-e61f-9987-2d23-91aed2af44b4.png "スクリーンショット 2015-07-27 23.22.11.png")

いつもどおり安定のChrome/Firefoxは勿論、マイクロソフトの新型ブラウザEdgeの対応も着実に進んでいるっぽい！w


## ES6の新機能を触ってみよう！

GoogleのChrome Canaryを使って、ES6の新機能に触れて行きたいと思います！

### class

```es5

var AccordionMenu = function(dom){
  this.dom = dom;
  this.list = dom.find('.js-accordion-list');
  this.menu = dom.find('.js-accordion-menu');
  this.initialize();
};

AccordionMenu.prototype = {                                                                                                                                                                    
  initialize : function() {
    if (this.list.hasClass('open')) {
      this.menu.find('i').removeClass('goicon-expand-more').addClass('goicon-expand-less');
    } else {
      this.menu.find('i').removeClass('goicon-expand-less').addClass('goicon-expand-more');
      this.list.hide();
    }   
  },  
  open : function() {
    this.menu.find('i').removeClass('goicon-expand-more').addClass('goicon-expand-less');
    this.list.slideDown();
    this.list.addClass('open');
  },  
  close : function() {
    this.menu.find('i').removeClass('goicon-expand-less').addClass('goicon-expand-more');
    this.list.slideUp();
    this.list.removeClass('open');
  },  
  toggle : function() {
    this.list.hasClass('open') ? this.close() : this.open()
  }
}


```

```es6

'use strict';                                                                                                                                                                                  

class AccordionMenu {

  constructor(dom) {
    this.dom = dom;
    this.list = dom.find('.js-accordion-list');
    this.menu = dom.find('.js-accordion-menu');
    this.initialize();
  }

  initialize() {
    if (this.list.hasClass('open')) {
      this.menu.find('i').removeClass('goicon-expand-more').addClass('goicon-expand-less');
    } else {
      this.menu.find('i').removeClass('goicon-expand-less').addClass('goicon-expand-more');
      this.list.hide();
    }
  }

  open() {
    this.menu.find('i').removeClass('goicon-expand-more').addClass('goicon-expand-less');
    this.list.slideDown();
    this.list.addClass('open');
  }

  close() {
    this.menu.find('i').removeClass('goicon-expand-less').addClass('goicon-expand-more');
    this.list.slideUp();
    this.list.removeClass('open');
  }

  toggle() {
    this.list.hasClass('open') ? this.close() : this.open()
  }
}


// 継承する場合はextendsで対応
class AccordionMenuSub extends AccordionMenu {
  
  constructor(dom) {
    super(dom); // 親コンストラクタを呼び出し
  }

  open() {
    super.open(); // 親メソッドを呼び出し
    alert('継承成功'); 
  }
}

```

いや...素敵だ！
プロトタイプで擬似的にクラスを作りOOPを実現していた長い歴史に遂に開放です！w
僕は普段rubyを作っているので、断然こっちのほうが理解できます！

### Allow Function

```es5
$('.js-company_link').click(function(e){
  e.preventDefault();
  var _self = $(this);
  var moveSlider = function() {
    var listIndex = _self.index();
    $('.js-tab_slider').css({
      left: $($('.header-tab_list')[tabNum-1]).width() * listIndex + 'px'
    });
  }
  moveSlider();
});
```

```es6
$('.js-company_link').click(e => {
  e.preventDefault();
  var moveSlider = () => {
    var listIndex = $(this).index();
    $('.js-tab_slider').css({
      left: $($('.header-tab_list')[tabNum-1]).width() * listIndex + 'px'
    });
  }
  moveSlider();
});
```

coffeescriptっぽい allow function。
function() を () => と書けるという表記以上に中の関数では実行時の関数外のthisをそのまま指す事が特徴です。
例は無理やり今使っているmethodに入れ込みましたが、classの中で使えると便利そう。


### Promise

ES6では新たな非同期処理の方法としてPromiseというオブジェクトが追加されました。
- [JavaScript Promiseの本](http://azu.github.io/promises-book/)

実はjqueryでは1.5系で既にPromiseが実装されていましたので、存在は知っているかもしれません。
参考) [爆速でわかるjQuery.Deferred超入門](http://techblog.yahoo.co.jp/programming/jquery-deferred/)


```
// promiseオブジェクトの生成
var testName = "moriyaman";
var promise = new Promise(function (resolve){
  // ここで最初に実行したいロジック追加
  console.log(testName) // moriyaman
  resolve("test"); // then methodのvalue値

});

promise.then(function(value){
  console.log(testName) // moriyaman
  console.log(value); // test
});

// promiseオブジェクトの生成
var promise = new Promise(function(resolve,reject){
  reject(new Error("エラー"));
});

promise.catch(function(error){
    console.error(error);
});

```

このようにして resolve / rejectを用いて実装します。

### Template Strings

```es5

var name = "moriyaman";
console.log('Hello '+name+', how are you?');
```

```es6
var name = "moriyaman";
console.log(`Hello ${name}, how are you?`)
```

素敵。文字列 + 変数の結合が地味にめんどくさかったけど、これで楽にかけますね。

### let & const

letはブロック内だけの変数。
constは再定義出来ない定数です。

```
  for (var i = 0; i < 5; i++) {                                                                                                                                                                
    if(i) {
      var i = 0;
      console.log(i);
    }
    console.log(i);
  }
  // 無限ループ

  for (let i = 0; i < 5; i++) {                                                                                                                                                                
    if(i) {
      let i = 0;
      console.log(i);
    }
    console.log(i);
  }
  // 5回実行されて終了
```


```
const name = 'moriyaman';
name = 'Moriyama'; // ここでエラー
```

javascriptほど自由度の高い言語だとこういう制御は有難いですね。

### デフォルトパラメータ

```
  function test(name = 'moriyama') {
    console.log(name);
  }
  test(); // moriyamaがlogに出る
```

これはRuby脳的にはかなり便利ですね！

### Iterator

イテレータとは「次の要素を返す」ことだけを担う、統一インターフェイス。

今までjsだとlengthで配列の個数をとって、forで回して要素にアクセスするなんて事をやっていたけど、
それを代わりにやってくれる for-of 構文が導入されました。

```
  var ary = [5, 4, 3]
  for (let n of ary) console.log(n);
  // 5
  // 4
  // 3
```

[イテレータについて](http://js-next.hatenablog.com/entry/2014/06/25/174231)

### Generator

Generatorとは上記のイテレータという仕組みを用いて、意図した状態で停止させたり、
外から関数を代入出来たりする特殊な関数です。

Rubyを使った事がある人なら少し馴染みがありますね。

```
function *callTeamMember() {
 console.log('kawamura');
 let member2 = yield 'next_member';
 console.log('takuya');
 let member3 = yield 'next_member';
 console.log(member2);
 console.log(member3);
}


var task = callTeamMember();
task.next();
// kawamura
// Object {value: "next_member", done: false}
task.next('shigeki');
// takuya
// Object {value: "next_member", done: false}
task.next('taro');
// shigeki
// taro
// Object {value: undefined, done: true}

```

- [Babel で ES6 の Generator を試す](http://smart.ataglance.jp/2015-04-23-try-es6-generator-with-babel/)

- [ES6時代のJavaScript](http://techlife.cookpad.com/entry/2015/02/02/094607)

## 既存のサービスにどう入れていくべきか？(rails編)

現状全てのブラウザでまだ実装されていない以上、そのままES6を入れていく事は不可能。
現在の可能性としては下記が考えられます。
詳しくは[es6-tools](https://github.com/addyosmani/es6-tools)に載ってます。

- Polyfillを導入する
- babel等のTranspilerツールを使ってES6→ES5に変換する

現実まだclassのPolyfillがない(見当たってないw)以上、使える機能が制限されるので、
流行りのbabelを使うのが一番良いかもしれません。

仕事ではRailsで用いるので、その際にどのように使っていくか考えたいと思います。

- [Sprockets ES6](https://github.com/TannerRogalsky/sprockets-es6)

[The Ruby Toolbox](https://www.ruby-toolbox.com/)で調べた中では一番ちゃんと開発が進んでいるGem。
しかしまだバージョンが0.7ということで、安定はしてなさそう。。。
というかsprocket3系以上だ・・・。 

おとなしくgulp+babelでやってみる方法を考える。

### まずは gulpを導入

もう入れている人多いと思いますが・・・w
[ざっくりGulp.js入門（Mac向け](http://qiita.com/kazukichi/items/884a1379eea5918689ed) を見て入れてもらえれば。
Gulpの説明はさすがに要らないですかね？w

Gruntなどと同じくタスク自動化ツールです。
今回はこのGulpにBabelというTranspilerツールを使って書いたES6コードをES5のコードに変換します。

### babelを導入

①まずはbabelをインストール

```
$ npm install babel -g
```

②railsのプロジェクト内でpackage.jsonを作成

```
$ npm init
```

お馴染みですねw

③gulp-babelやらminify用のgulp-uglifyなど、必要なmoduleを入れていきます。

```
$ npm install gulp --save-dev // gulp本体
$ npm install gulp-babel --save-dev // babel module
$ npm install gulp-plumber --save-dev // エラー時の停止を回避してくれるmodule
$ npm install gulp-uglify --save-dev // minify用module
```

④gulpfile.jsにタスクを追加

```
var gulp = require("gulp"),
    babel = require("gulp-babel"),
    uglify = require("gulp-uglify"),
    plumber = require('gulp-plumber');

gulp.task("convertToEs5", function() {
    gulp.src("public/es6/javascripts/*.js") // 一旦ここにes6のコードを入れる
        .pipe(plumber())          
        .pipe(babel())            
        .pipe(uglify())           
        .pipe(gulp.dest("app/assets/javascripts"));
});                                                                                                                                                         
```

先ほどのclassを試す際に使ってみたjsファイルを試しにここに配置してみます。

```public/es6/javascripts/accordion_menu.js

'use strict';

class AccordionMenu {
  constructor(dom) {
    this.dom = dom;
    this.list = dom.find('.js-accordion-list');
    this.menu = dom.find('.js-accordion-menu');
    this.initialize();
  }

  initialize() {
    if (this.list.hasClass('open')) {
      this.menu.find('i').removeClass('goicon-expand-more').addClass('goicon-expand-less');
    } else {
      this.menu.find('i').removeClass('goicon-expand-less').addClass('goicon-expand-more');
      this.list.hide();
    }   
  }

  open() {
    this.menu.find('i').removeClass('goicon-expand-more').addClass('goicon-expand-less');
    this.list.slideDown();
    this.list.addClass('open');
  }

  close() {
    this.menu.find('i').removeClass('goicon-expand-less').addClass('goicon-expand-more');
    this.list.slideUp();
    this.list.removeClass('open');
  }

  toggle() {
    this.list.hasClass('open') ? this.close() : this.open()
  }
}
```

タスクを実行します。

```
$ gulp convertToEs5

[01:18:50] Using gulpfile ~/work/green/gulpfile.js
[01:18:50] Starting 'js'...
[01:18:50] Finished 'js' after 9.56 ms
```

タスク実行完了。

``` app/assets/javascripts/accordion_menu.js
"use strict";function _classCallCheck(e,s){if(!(e instanceof s))throw new TypeError("Cannot call a class as a function")}var _createClass=function(){function e(e,s){for(var i=0;i<s.length;i++){var n=s[i];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(e,n.key,n)}}return function(s,i,n){return i&&e(s.prototype,i),n&&e(s,n),s}}(),AccordionMenu=function(){function e(s){_classCallCheck(this,e),this.dom=s,this.list=s.find(".js-accordion-list"),this.menu=s.find(".js-accordion-menu"),this.initialize()}return _createClass(e,[{key:"initialize",value:function(){this.list.hasClass("open")?this.menu.find("i").removeClass("goicon-expand-more").addClass("goicon-expand-less"):(this.menu.find("i").removeClass("goicon-expand-less").addClass("goicon-expand-more"),this.list.hide())}},{key:"open",value:function(){this.menu.find("i").removeClass("goicon-expand-more").addClass("goicon-expand-less"),this.list.slideDown(),this.list.addClass("open")}},{key:"close",value:function(){this.menu.find("i").removeClass("goicon-expand-less").addClass("goicon-expand-more"),this.list.slideUp(),this.list.removeClass("open")}},{key:"toggle",value:function(){this.list.hasClass("open")?this.close():this.open()}}]),e}();

```

minifyかけてるからよくわかりませんねw

```app/assets/javascripts/accordion_menu.js(minify無し)

'use strict';                                                                                                                                                                                  

var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }

var AccordionMenu = (function () {
  function AccordionMenu(dom) {
    _classCallCheck(this, AccordionMenu);

    this.dom = dom;
    this.list = dom.find('.js-accordion-list');
    this.menu = dom.find('.js-accordion-menu');
    this.initialize();
  }

  _createClass(AccordionMenu, [{
    key: 'initialize',
    value: function initialize() {
      if (this.list.hasClass('open')) {
        this.menu.find('i').removeClass('goicon-expand-more').addClass('goicon-expand-less');
      } else {
        this.menu.find('i').removeClass('goicon-expand-less').addClass('goicon-expand-more');
        this.list.hide();
      }     
    }     
  }, {  
    key: 'open',
    value: function open() {
      this.menu.find('i').removeClass('goicon-expand-more').addClass('goicon-expand-less');
      this.list.slideDown();
      this.list.addClass('open');
    }     
  }, {  
    key: 'close',
    value: function close() {
      this.menu.find('i').removeClass('goicon-expand-less').addClass('goicon-expand-more');
      this.list.slideUp();
      this.list.removeClass('open');
    }     
  }, { 
	key: 'toggle',
    value: function toggle() {
      this.list.hasClass('open') ? this.close() : this.open();
    }
  }]);

  return AccordionMenu;
})(); 

```

勿論rails側でこれを読んであげれば問題なく動きます。
watcherで常にwatchして同時で変換かければ、問題なさそうですね。

- [これからはじめるGulp（3）：gulp.watchでファイルの変更を監視しタスクを実行する](http://whiskers.nukos.kitchen/2014/12/03/gulp-watch.html)

## 結論

- ES6は入れた方が良い。確実に便利なメソッドも多いので、作業効率的にも◯。
- Railsのバージョン次第ではSprockets試しても良いと思います！が...おとなしくBabel使った方が便利かも。
- Gulp+BabelでタスクをWatchしながら開発。プルリクのレビューは変換前のES6に対して行う。
- ES6側のファイルを書き換えないと、タスク実行後元に戻る問題は起きるので、その辺は慣れが必要。
- 毎回Gulp立ち上げるのめんどくさい問題も慣れで解決しましょw
- こうなるといよいよSprockets VS Gulp 的な話も無くはないw

という感じでした！
とりあえずウチの会社では入れていけるように、進めて行きます！