2015年11月11日水曜日

Backbone.JSからAngular2まで、全9大JavaScriptフレームワークを書き比べた!

引用元:
http://paiza.hatenablog.com/entry/2015/03/11/Backbone_JS%E3%81%8B%E3%82%89Angular2%E3%81%BE%E3%81%A7%E3%80%81%E5%85%A89%E5%A4%A7JavaScript%E3%83%95%E3%83%AC%E3%83%BC%E3%83%A0%E3%83%AF%E3%83%BC%E3%82%AF%E3%82%92%E6%9B%B8%E3%81%8D%E6%AF%94%E3%81%B9



f:id:paiza:20150320133304p:plain
(English article is here.)
f:id:paiza:20140712194904j:plainこんにちは、吉岡(@)です。
ウェブ開発に欠かせないJavaScriptフレームワークですが、日々発展しておりReact.js, Ractive.js, Aurelia.js, AngularJS2.0など次々と新しいフレームワークが出てきています。
一体どれを使えばいいのか?何が違うのか?何から調べていいのか迷うことがあります。
そこで、現時点で事実上全てとなる、9大主要フレームワークについて、実際に使ってみて比較を行います。
  • Backbone.js
  • Ember.js
  • Knockout.js
  • AngularJS(1.x)
  • React.js
  • Ractive.js
  • vue.js
  • Aurelia.js
  • AngularJS2.0(アルファ版)
これらのフレームワークでは、以下のような機能が実現されています。
フレームワークによって実現する範囲は異なりますが、すべてのフレームワークにおいて、HTMLとJavaScriptをうまく結びつけることが大きな目的になっており特徴も出てきまので、その点に着目して比較します。

■サンプルコードの動作

書いたコードは苗字と名前を入れるとフルネームをその場に直ぐに表示するコードです。f:id:paiza:20150310154810p:plain
単純なコードですが、これだけでも以下のような主要な動作を見ることができます。
  • HTMLとJavaScriptで、変数、コントローラ、モデルをどのように表記するか
  • フォームで入力された内容をどのようにJavaScript上のモデルに伝えるか
  • JavaScriptのモデル上の変更をどのようにフォームに伝えるか

■比較

それでは各フレームワークの比較を行います。
フレームワークにも時代の流れがあり作られた時点の考え方を反映していますので、およそ作られた順番で書いていきます。

◆0. フレームワークなし(jQuery)

HTML:
First Name: <input id="firstName"/><br>
Last Name: <input id="lastName"/><br>
Full Name: <span id="fullName"></span><br>
function updateFullName(){
    var fullName = $("#firstName").val() + " " + $("#lastName").val();
    $("#fullName").text(fullName);
}
$("#firstName, #lastName").bind("change keyup", function(){
    updateFullName();
});
$("#firstName").val("Taro");
$("#lastName").val("Yamada");
updateFullName();
フレームワークの例をあげる前に、フレームワークを使わない場合を考えてみます。
jQueryを用いることで簡単に記述することができます。しかし、この方法には問題点があります。まず、”firstName”,”lastName”といった変数がJavaScript上で、文字列として操作されており変更が難しくなっています。$()を用いたjQueryオブジェクトもグローバル変数のようになっており構造化が困難です。
一番の問題は、変数がHTML(DOM)上にしか存在しないとこです。そのため、データを構造化して処理することが難しくなっています。

◆1. Backbone.js

HTML:
<div id="person">
    First Name: <input id="firstName" value=""><br>
    Last Name: <input id="lastName" value=""><br>
    Full Name: <span id="fullName"></span>
</div>
Person = Backbone.Model.extend({});
PersonView = Backbone.View.extend({
    el: '#person',
    events: {
        'change': 'change',
    },
    initialize: function(){
        this.listenTo(this.model, 'change', this.render);
        this.render();
    },    
    change: function(){
        var firstName = $('#firstName').val();
        var lastName = $('#lastName').val();
        this.model.set({firstName: firstName, lastName: lastName});
    },
    render: function(){
        this.$('#firstName').val(this.model.get('firstName'));
        this.$('#lastName').val(this.model.get('lastName'));
        var fullName = this.model.get('firstName')
                        + ' ' + this.model.get('lastName');
        this.$('#fullName').text(fullName);
    },
});
person = new Person({lastName: "Yamada", firstName: "Taro"});
personView = new PersonView({model: person});
Backbone.jsはシンプルなフレームワークで、ModelクラスとViewクラスが核となります。
Backbone.jsではMVCモデルを導入することで、フレームワークなし(jQuery)で問題となっていた、グローバル変数を排除したモジュール化、JavaScript上での構造化されたモデルの保持が可能になり、大規模なJavaScriptコードでもモジュール化して記述することが可能になっています。
HTML上の表現は、Viewを通じて行います。Viewは必要に応じて階層化されます。各ビューには対応するモデルがありますが、モデルとHTMLは直接関連することはありません。HTMLとビュークラスはJQuery関数(val()/text())や、イベント監視を通じて連携します。ビュークラスとモデルクラスは、モデルのset/get関数やイベント監視(listenTo)を通じて連携します。
このように自動的に連携するような機能はなく、明示的に関連を作っていきます。凝った機能はありませんが、明示的に疎結合を実現することで、大規模な開発にも耐えられます。

◆2. Ember.js

index.html:
<script type="text/x-handlebars" data-template-name="index">
First Name:{{input type="text" value=firstName}}<br/>
Last Name:{{input type="text" value=lastName}}<br/>
Full Name: {{model.fullName}}<br/>
</script>
App = Ember.Application.create();
App.Person = Ember.Object.extend({
  firstName: null,
  lastName: null,

  fullName: function() {
    return this.get('firstName') + ' ' + this.get('lastName');
  }.property('firstName', 'lastName')
});

var person = App.Person.create({
  firstName: "Taro",
  lastName:  "Yamada"
});

App.Router.map(function () {
});

App.IndexRoute = Ember.Route.extend({
    model:function () {
        return person;
    }
});
Ember.jsでは、Backbone.jsでは明示的に記述する必要があった、HTMLとJavaScript上のコントローラ、モデル上の変数の連携をデータバインディングにより自動的に行うことができます。
具体的には、”{{}}”という特殊記法を用いることで、JavaScript上の変数とコントローラやモデルクラスの変数を直接結びつけます。
HTML上の入力用の変数の変更は自動的にモデル上に反映されます。モデル上の変更は、依存する変数(フルネームの場合、苗字・名前)をproperty関数で明示的に指定し、依存する変数に変更があった場合に自動的にHTML上の変数を更新します。
これりより、明示的にイベント処理を行うことなく、HTMLとモデル・コントローラが連携して動作します。

◆3. Knockout.js

HTML:
<p>First name:<input data-bind="value: firstName" /></p>
<p>Last name:<input data-bind="value: lastName" /></p>
<p>Full name:<span data-bind="text: fullName"></span></p>
function AppViewModel() {
    this.firstName = ko.observable("Taro");
    this.lastName = ko.observable("Yamada");

    this.fullName = ko.computed(function() {
        return this.firstName() + " " + this.lastName();    
    }, this);
}

// Activates knockout.js
ko.applyBindings(new AppViewModel());
Knockout.jsもEmber.jsと同様にHTMLとJavaScriptの間でのデータバインディングによる簡単な記述を実現しています。
Ember.jsでは変数の依存性を依存先でまとめて書いていましたが、Knockout.jsでは監視対象の依存元変数を”observable”として宣言します。
具体的には、HTML上で”data-bind=“value: firstName””のように属性で変数を記述すことで、HTML上の変数がViewModel上の変数と対応することを示します。ViewModelクラスでは、入力変数は”ko.observable()”と明示的に指定することで変数の変更を検知します。変更があった場合、”ko.computed”で作られた変数が自動的に更新され、HTML上に反映されます。

◆4. AngularJS(1.x)

HTML:
<div ng-app ng-controller="PersonController">
First Name: <input type=text ng-model="firstName"> <br>
Last Name: <input type=text ng-model="lastName"><br>
Full Name: {{getFullName()}}
</div>
function PersonController($scope) {
  $scope.firstName = "Taro";
  $scope.lastName  = "Yamada";
  $scope.getFullName = function() {
    return $scope.firstName + " " + $scope.lastName;
   };
}
AngularJSは、双方向バインディング、ルーティング、REST API呼び出し、DIなど機能が豊富な現在主流の本格的フレームワークです。
Ember.js、Knockout.jsによりHTMLとJavaScriptのデータバインディングは自動的に実現されましたが、それでも依存関係は明示的に記述することが必要でしたが、AngularJSでは依存した変数の更新を自動的に行うことで、大幅に記述を簡略化できます。
HTML上の変数とJavaScript上のコントローラを簡単に関連付けられることが特徴です。HTML上でng-model=“変数”や”{{}}”で表されるAngular式は、自動的にコントローラ上の変数と連携します。JavaScript上では、明示的に変数の関連性を記述する必要はなく、通常のJavaScript変数を用いてシンプルな記述が可能です。変数の監視は、何らかの操作が行われたあとに、すべての変数の変化を検出することで自動的に行われます。

◆5. React.js

var MyApp = React.createClass({
  getInitialState: function(){
      return {
          firstName: this.props.firstName,
          lastName:  this.props.lastName,
      }
  },
  handleChange: function(){
      var firstName = this.refs.firstName.getDOMNode().value;
      var lastName = this.refs.lastName.getDOMNode().value;
      this.setState({
          firstName: firstName,
          lastName: lastName,
              });
  },
  render: function() {
    var fullName = this.state.firstName + this.state.lastName;
    return (
        <div>
        First name: <input ref="firstName" onChange={this.handleChange} value={this.state.firstName}/><br/>
        Last name: <input ref="lastName" onChange={this.handleChange} value={this.state.lastName}/><br/>
        Full name: {fullName}
        </div>);
  }
});

React.render(<MyApp firstName="Taro" lastName="Yamada" />, document.body);
React.jsはHTMLとJavaScriptデータバインディング部分に特化し、VirtualDOMを用いてシンプルに実現したフレームワークです。
AngularJS等のフレームワークでは、双方向データバインディングを実現していますが、JavaScript上のモデル・コントローラ等を更新する場合、JavaScriptのオブジェクトとして保持している状態を、明示的に差分として更新する必要があります。
React.jsでは、表示するHTMLに対応するDOMの状態をフレームワーク側で管理して自動的に変化の差分を検出します。これにより、開発者は明示的に状態の変化を管理する必要がなくなります。
具体的には、React.jsでは、描画するHTMLをJavaScript上にJSX表記で直接埋め込みます。
埋め込まれたHTMLは直接HTML上に反映されるのではなく、VirtualDOMという形式で内部的に保持されます。
再描画が必要な変更がモデルにあった場合、setState()関数で通知します。
この時、React.jsはVirtualDOMの内容の変更点を監視し、変更された差分のみ実際のHTMLに反映させることで毎回全部描画しなおすことが必要なく高速化しています。

◆6. Ractive.js

HTML:
<script type="text/reactive" id="tpl">
First Name:<input type="text" value="{{firstName}}"/><br/>
Last Name:<input type="text" value="{{lastName}}"/><br/>
Full Name: {{fullName()}}<br/>
</script>
<div id='container'></div>
var ractive = new Ractive({
  el: 'container',
  template: '#tpl',
  data: {
    firstName: 'Taro',
    lastName: 'Yamada',
    fullName: function () {
        return this.get( 'firstName' ) + ' ' + this.get( 'lastName' );
    }
  },
});
Ractive.jsはHTMLに簡単に双方向データバインディングを実現したフレームワークです。
React.js同様データバインディングに特化していますが、React.jsがJavaScriptを中心に記述するのに対し、RactiveJSはHTMLを中心に記述してJavaScriptの記述を最低限にすることができます。
HTML中に”{{}}”として変数や関数を記述することで、対応するJavaScriptのクラスに自動的に相互に反映されます。JavaScriptもシンプルに書くことができます。

◆7. vue.js

HTML:
<div id="person">
    First Name: <input v-model="firstName"><br/>
    Last Name: <input v-model="lastName"><br/>
    Full Name: {{fullName}}<br/>
</div>
var demo = new Vue({
    el: '#person',
    data: {
        firstName: 'Taro',
        lastName: 'Yamada',
    },
    computed: {
        fullName: {
            get: function(){
                return this.firstName + ' ' + this.lastName;
            }
        }
    },
})
vue.jsはHTML/JavaScript間の双方向データバインディングを、可能な限りシンプルに実現したフレームワークです。
Ractive.jsより更にHTML記述を自然に行えるようにし、よりHTMLを自然に書くことができるようになっています。
HTML中に”{{}}”またはv-modelで記述した変数が、JavaScript側のモデル変数・関数と自動的に反映されます。
これ以上ないぐらいシンプルに記載でき、デザイン・プロトタイプ・小規模用途で使いやすくなっています。

◆8. Aurelia.js

app.html:
<template>
  <section>
    <form role="form">
      First Name: <input type="text" value.bind="firstName"><br/>
      Last Name: <input type="text" value.bind="lastName"><br/>
      Full name: ${fullName}
    </form>
  </section>
</template>
app.js:
export class Welcome{
  constructor(){
    this.firstName = 'Taro';
    this.lastName = 'Yamada';
  }
  get fullName(){
    return `${this.firstName} ${this.lastName}`;
  }
}
(テンプレートプロジェクト: http://aurelia.io/get-started.html)
Aurelia.jsは、最新のECMAScript6/7の機能を使った、本格的かつシンプルな未来志向フレームワークです。
Aurelia.jsでは、AngularJSで導入された豊富な機能(双方向バインディング、ルーティング、REST API呼び出し、DI)と、Ractive.js/vue.jsのシンプルさやパフォーマンスを、ES6/ES7のmoduleやObject.observe()等の機能を使うことで、同時に自然な形で実現しています。
今年中にも来るES6を見据えた最新のフレームワークで、未来を先取りしている形ですが、ポリフィルを用いることでES6を実装していない現在のブラウザでも動作するようになっています。
HTMLのテンプレート記述もES6標準に沿っており”${}”、”value.bind”で変数を埋め込みます、JavaScript上のクラスもES6で自然に記述でき、thisの変数やget/putによるプロパティで記述します。これらの変数が双方向にバインディングされており、HTML/JavaScript上の変数/関数が自動的に連携します。
Aurelia.jsについては、詳しくは以前書いた記事でも紹介しています。

◆9. AngularJS2.0(アルファ版)

app.html:
First Name: <input type=text [value]="firstName" #first (keyup)="firstNameChanged($event, first)"><br/>
Last Name:  <input type=text [value]="lastName"  #last  (keyup)="lastNameChanged($event, last)"><br/>
Full Name:  {{fullName}}
app.js:
import {Component, Template, bootstrap} from 'angular2/angular2';

// Annotation section
@Component({
  selector: 'my-app'
})
@Template({
  url: 'app.html'
})
// Component controller
class MyAppComponent {
  constructor() {
    this.firstName = 'Taro';
    this.lastName = 'Yamada';
    this.updateFullname();
  }
  changed($event, el){
    console.log("changes", this.name, el.value);
    this.name = el.value;
  }
  updateFullname(){
    this.fullName = this.firstName + " " + this.lastName;
  }
  firstNameChanged($event, first){
    this.firstName = first.value;
    this.updateFullname();
  }
  lastNameChanged($event, last){
    this.lastName = last.value;
    this.updateFullname();
  }
}
bootstrap(MyAppComponent);
(テンプレートプロジェクト: https://angular.io/docs/js/latest/quickstart.html)
AngularJS2.0(アルファ版)は、先日のng-confで開発用サイトangular.ioがアナウンスされたばかりの現在開発中のフレームワークです。
実際のリリースは1年程度先になると考えられますが、注目を浴びているフレームワークですので、まだアルファ版ですが現時点での状況を元に紹介します。
AngularJS2.0(アルファ版)では、Aurelia.IOと同様にES6等の最新のJavaScript規格を取り入れています。ES6に静的型検査やアノテーション("@"記法)を追加した、TypeScriptをベースとしたAtScriptで記述します。これより、文法ミスの検出やIDEとの連携が容易になりバグを減らすことができます。Angular1.xの売りでもあった双方向バインディングは廃止されました。自動的な連携はできなくなりますが、より明示的にJavaScriptとHTMLの動作を表現しやすくなり、動作がわかりやすくなり、ダイジェストループを無くしてパフォーマンスも向上します。
具体的には、@Component/@Templateによりコンポーネントに対応する要素やテンプレートなどを指定します。要素名に対応したJavaScriptクラスで動作を記述します。
ng-xxx記法が廃止され、代わりに"[式]"によりJavaScriptからHTMLへの連携、"(イベント)"によるHTMLからJavaScriptへの連携、"#要素名"による要素の参照、というように記法が変更されています。scopeも廃止され、thisの変数を直接HTML上で参照できるようになります。
AngularJS2.0(アルファ版)については、詳しくは以前書いた記事でも紹介しています。

■まとめ

以上、現在の事実上ほぼ全ての9大主要フレームワークについて出てきた流れに沿って書いてみました。
主な用途としては、Backbone.jsは大規模でカスタマイズして使う向け、Ember.js/Knockout.jsはより簡単にパフォーマンスを重視したプロジェクト向け、AngularJSは万能でどのプロジェクトでも、React.jsは簡単にJavaScriptコードを書きたい場合、Ractive.js/vue.jsはHTML/デザイン中心にシンプルに使いたい場合、Aurealia.jsは今年中にも来るES6時代の将来性を見た場合、AngularJS2.0(アルファ版)は今後の動向を研究したい場合、になると思います。
新規にフレームワークを選択する場合はもちろん、フレームワークが決まっている場合でも他のフレームワークを見ることで、考え方や書き方に幅がでてきますので、ぜひ試してみてください。

paizaではスキルのあるエンジニアがきちんと評価されるようにし、技術を追い続ける事が仕事につながるようにする事で、日本のITエンジニアの地位向上を図っていければと考えています。特にpaizaではWebサービス提供企業などでもとめられる、システム開発力や、テストケースを想定できるかの力(テストコードを書く力)などが問われる問題を出題しています。
テストの結果によりS,A,B,C,D,Eの6段階でランクが分かります。自分のプログラミングスキルを客観的に知りたいという方は是非チャレンジしてみてください。
また数多くの自社サービス提供企業、webサービス提供企業の求人を掲載しています。

0 コメント:

コメントを投稿