そもそも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の人気低下の要因かと。
今回なんでこんなに騒がれたの?
引用:wikipedia
実はES5がリリースされたのは2009年12月。
という事は世間がのりピーやら新型ウィルスで騒がしかった頃ですね。
参考: 【年末特集】 2009年芸能界を振り返る 10大NEWSプレイバック!
ということでめちゃくちゃ久しぶりのバージョンUP。
TC39(ECMAScriptを策定してる専門委員会)によるとES7は機能毎に仕様を策定していくらしいので、スピードがあがるようです。
Jsはもはや多岐に渡って活躍する言語になったので、世間からの期待値も高いです。
ちなみに仕様策定の手順などはECMAScript6をまるっと学ぶ。重要用語とか、仕様策定の進め方とか、新機能とか。がかなり詳しくまとめてくれています。
ES6の対応状況は?
- ECMAScript compatibility tableによって確認可能です。
いつもどおり安定の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というオブジェクトが追加されました。
実は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ファイルを試しにここに配置してみます。
'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
タスク実行完了。
"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
'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
という感じでした!
とりあえずウチの会社では入れていけるように、進めて行きます!