LoginSignup
22
24

More than 5 years have passed since last update.

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

Posted at

そもそも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

引用:wikipedia

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

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

ちなみに仕様策定の手順などはECMAScript6をまるっと学ぶ。重要用語とか、仕様策定の進め方とか、新機能とか。がかなり詳しくまとめてくれています。

ES6の対応状況は?

スクリーンショット 2015-07-27 23.22.11.png

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

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

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

class


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()
  }
}



'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

$('.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();
});
$('.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の本

実はjqueryでは1.5系で既にPromiseが実装されていましたので、存在は知っているかもしれません。
参考) 爆速でわかる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


var name = "moriyaman";
console.log('Hello '+name+', how are you?');
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

イテレータについて

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}

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

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

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

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

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

The Ruby Toolboxで調べた中では一番ちゃんと開発が進んでいるGem。
しかしまだバージョンが0.7ということで、安定はしてなさそう。。。
というかsprocket3系以上だ・・・。

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

まずは gulpを導入

もう入れている人多いと思いますが・・・w
ざっくりGulp.js入門(Mac向け を見て入れてもらえれば。
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して同時で変換かければ、問題なさそうですね。

結論

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

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

22
24
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
24