jQuery to Backbone アーキテクチャを意識したJavaScript入門 Frontrend Vol.4 さとう歩 / @ahomu CyberAgent, Inc.
依存するライブラリ
✓ jQuery✓ Zepto.js (lightweight clone)
✓ Underscore.js✓ Lodash (more faster)
UtilityBelt
Library
SelectorBasedLibrary
_.$.
jQueryについての所感http://havelog.ayumusato.com/
develop/javascript/e333-jquery_thiking_misc.html
Webサイト
従来のWebサービスWebアプリ
新しめなWebサービスWebアプリ
ややリッチなインターフェース
静的HTMLCMS利用
シングルページリッチ&シームレスなUI
RESTful API
Webサイト
従来のWebサービスWebアプリ
新しめなWebサービスWebアプリ
サーバーサイドで画面遷移を設計
静的HTMLCMS利用
シングルページリッチインターフェース
RESTful API
次第に高まってきたフロントエンド実装の比重
Most Developers realize that structured, maintainable code is important.
via. Digesting JavaScript MVChttps://speakerdeck.com/addyosmani/digesting-javascript-mvc?slide=10
ディベロッパーにとって、構造的でメンテナンスしやすいコードが何たるかを知ることが重要。
“
古典 of SmalltalkSmalltalk(スモールトーク)は、Simulaのオブジェクト(およびクラス)
Lispの機能、LOGOのエッセンスを組み合わせて作られたクラスベースの純粋オブジェクト指向プログラミング言語、および、それによって記述構築された
統合化プログラミング環境の呼称。
via. Smalltalk - Wikipediahttp://ja.wikipedia.org/wiki/Smalltalk
Todo MVChttp://addyosmani.github.com/
todomvc/
Backbone.js AngularJS Ember.js KnockoutJS
Dojo YUI Agility.js Knockback.js CanJS Maria cujo.js dermis
Montage Ext.js Sammy.js Stapes Epitome soma.js
DUEL PureMVC Olives PlastronJS Dijon rAppid.js
Funnyface.js Spine Batman.js GWT Closure
MVC Extension Frameworks MarionetteJS
Thorax Chaplin
巷に溢れかえるMV*フレームワークTodoMVCだけで30以上が列挙される
aurahttps://github.com/aurajs/aura
Flighthttp://twitter.github.com/
flight/
サバクラ両方で動くJavaScript の大規模
開発を行うためにhttps://gist.github.com/tily/
1362110
“it serves as a foundation for your application, you're meant to extend and enhance it in the
ways you see fit
方法は1つではないし、柔軟に拡張できる
via. Backbone.js FAQhttp://backbonejs.org/#FAQ-why-backbone
Abstract Factory Builder Factory Method
Prototype Singleton Adapter Bridge
Composite Decorator Facade Flyweight Proxy Chain of Responsibility Command Interpreter
Iterator Mediator Memento Observer
State Strategy Template Method Visitor
GoFに代表される様々なデザインパターン一般に共有できるボキャブラリ
JavaScriptDesign Patterns
http://www.joezimjs.com/javascript/javascript-design-
patterns-singleton/
{}Facade - 複数の処理をまとめる
// クリックしたときに複数の操作を同時に行う// 最も広義には誰もがあたりまえに行うパターンonLikeClick: function() { this.likeModel.save(); this.changeState(‘liked’); this.$el.addClass(‘animate’);
// ... // .. // .}
{}Singleton - ただひとつのオブジェクト
// 簡易には、不変的オブジェクトになるために// コンストラクタなしでオブジェクトリテラルで表現var singletonClass = { init: function() { // initialize logic }, method: function() { // some logic }}singletonClass.init();
{}Flyweight - インスタンス生成
// 動的にインスタンスを作りたい場合など// 同じ役割のインスタンスは複数生成させずに使い回すvar factory1 = Flyweight.getFactory('model');var factory2 = Flyweight.getFactory('view');var factory3 = Flyweight.getFactory('model');
// 期待する動作として、modelとして呼んだら同一であることconsole.log(factory1 === factory2); //=> falseconsole.log(factory1 === factory3); //=> true
{}Observer - イベントを監視
// 監視される側のModelと、監視する側のViewvar model = new FooModel();var view = new BarView();
// Modelに直接、Viewのメソッドをリスナーで登録model.on('change', view.render);
// fetch()によって、changeイベントがトリガーmodel.fetch(); // view.render()が実行される
{}Mediator - イベントを仲介
// Mediatorを作成(Backboneの例)var mediator = _.clone(Backbone.Events);
// Mediatorにリスナーを登録mediator.on('model:change', view.render);
// Mediatorを通してイベントをトリガーmediator.trigger('model:change', this);
{}典型的なView
var ViewClass = Backbone.View.extend({ el: ‘#main’, initialize: function() { // process... }, render: function() { this.$el.html(‘rendering html strings’); }});var view = new ViewClass(); // `initalize`view.render(); // `render`
{}典型的なModel
var ModelClass = Backbone.Model.extend({ defaults: {}, url: ‘api/v1/path/to’, initialize: function() { // process... }});var model = new ModelClass(); // `initalize`var view = new ViewClass({model: model});model.fetch({ success: view.render});
{}典型的なCollection
var Persons = Backbone.Collection.extend({ url: ‘api/v1/path/to’, model: Person});var persons = new Persons();persons.fetch({ success: function() { this.where({ name: ‘anonymous’ })[0].sayName(); // ‘anonymous!’ }});
{}典型的なRouter
var Router = Backbone.Router.extend({ routes: { 'store/:storeId': 'gotoStore' }, gotoStore: function(storeId) { new StoreView({ model: new Store(storeId); }); }});var app = new Router();Backbone.history.start();
BackboneRouter
📦 📦 📦
Views
Models
Collection
via. Backbonification - Migrating NewsBlur From DOM Spaghetti to Backbone.jshttps://speakerdeck.com/samuelclay/backbonification-migrating-newsblur-from-dom-spaghetti-to-backbone-dot-js?slide=12
{}ピュアなjQueryコードからスタートvar $list = $('#js-gists');$.ajax({ method: 'GET', url: 'https://api.github.com/gists', data: oauthData, dataType: 'json'}).done(function(data) { var i = 0, html = '', item; while (item = data[i++]) { html += '<li>'+ '<a data-src="'+item.url+'" href="#">'+item.description+'</a>'+ '<a href="'+item.html_url+'">Show in gists</a>'+ '</li>'; } $list.html(html);});$list.on('click', '[data-src]', previewGist);
{}おもむろにViewを作成var GistsListView = Backbone.View.extend({ el: '#js-gists', initialize: function() { var $list = this.$el; $.ajax({ method: 'GET', url: 'https://api.github.com/gists', data: oauthData, dataType: 'json' }).done(function(data) { var i = 0, html = '', item; while (item = data[i++]) { html += '<li>'+ '<a data-src="'+item.url+'" href="#">'+item.description+'</a>'+ '<a href="'+item.html_url+'">Show in gists</a>'+ '</li>'; } $list.html(html); }); $list.on('click', '[data-src]', previewGist); }});var gistsList = new GistsListView();
{}renderメソッドを抽出var GistsListView = Backbone.View.extend({ el: '#js-gists', initialize: function() { _.bindAll(this); $.ajax({ method: 'GET', url: 'https://api.github.com/gists', data: oauthData, dataType: 'json' }).done(this.render); this.$el.on('click', '[data-src]', previewGist); }, render: function(data) { var i = 0, html = '', item; while (item = data[i++]) { html += '<li>'+ '<a data-src="'+item.url+'" href="#">'+item.description+'</a>'+ '<a href="'+item.html_url+'">Show in gists</a>'+ '</li>'; } this.$el.html(html); return this; }}); var gistsList = new GistsListView();
{}テンプレートの分離var GistsListView = Backbone.View.extend({ el: '#js-gists', tmpl: _.template($('#tmpl-js-gists').html()), initialize: function() { _.bindAll(this); $.ajax({ method: 'GET', url: 'https://api.github.com/gists', data: oauthData, dataType: 'json' }).done(this.render); this.$el.on('click', '[data-src]', previewGist); }, render: function(data) { this.$el.html(this.tmpl({items: data})); return this; }});var gistsList = new GistsListView();
{}Underscoreテンプレート<script id="tmpl-js-gists" type="tmpl/text"><% _.each(items, function(item) { %><li> <a data-id="<%= item.id %>" data-src="<%= item.url %>"> <%= item.description %> </a> <a href="<%= item.html_url %>">Show in gists</a></li><% }); %></script>
{}イベントの定義var GistsListView = Backbone.View.extend({ el: '#js-gists', tmpl: _.template($('#tmpl-js-gists').html()), events: { 'click [data-src]': previewGist }, initialize: function() { _.bindAll(this); $.ajax({ method: 'GET', url: 'https://api.github.com/gists', data: oauthData, dataType: 'json' }).done(this.render); }, render: function(data) { this.$el.html(this.tmpl({items: data})); return this; }});var gistsList = new GistsListView();
{}Collectionを作成var Gists = Backbone.Collection.extend({ url: 'https://api.github.com/gists?' + $.param(oauthData)});var GistsListView = Backbone.View.extend({ el: '#js-gists', tmpl: _.template($('#tmpl-js-gists').html()), events: { 'click [data-src]': 'preview' }, initialize: function() { _.bindAll(this); this.collection.fetch({ success: this.render }); }, render: function() { this.$el.html(this.tmpl({items: this.collection.toJSON()})); return this; }});var gistsList = new GistsListView({ collection: new Gists() });
{}Modelを作成// Model & Collectionvar Gist = Backbone.Model.extend({ url: function() { return this.get('url'); }});var Gists = Backbone.Collection.extend({ url: 'https://api.github.com/gists?' + $.param(oauthData), model: Gist});
// GistListView#previewpreview: function(event) { gistPreview.model = this.collection.where({ id: $(event.currentTarget).attr('data-id') })[0]; gistPreview.show(); return false;},
{}Collectionのソートvar Gists = Backbone.Collection.extend({ url: 'https://api.github.com/gists?' + $.param(oauthData), model: Gist, order_by: 'updated_at', comparator: function(gist) { switch(this.order_by) { case 'updated_at': return - new Date(gist.get('updated_at')).getTime(); } }});// GistListViewevents: { 'click #js-sort-updated': 'sortByUpdatedAt',},initialize: function() { _.bindAll(this); this.collection.fetch({success: this.render}); this.collection.on('sort', this.render);},sortByUpdatedAt: function() { this.collection.order_by = 'updated_at'; this.collection.sort();},
jQueryにも言えるが使い方によっては簡単に重くなる
// ループ内でappendすんな、毎回セレクタ走らせんな!!$.each(persons, function(person) { $(‘ul’).append(‘<li>’+person.name+’</li>’);});
Backbone has made me a better programmer
via. Backbone has made me a better programmer | Float Lefthttp://floatleft.com/notebook/backbone-has-made-me-a-better-programmer
“
Dependencies? AMD?勉強会資料シェア Getting Started with RequireJS
http://havelog.ayumusato.com/develop/javascript/e525-into_requirejs.html
en.ja OSSBackbone日本語訳
https://github.com/enja-oss/Backbone/
DevelopingBackbone.jsApplicationshttp://addyosmani.github.com/
backbone-fundamentals/
AdventCalendar
http://www.adventar.org/calendars/15
その他の参考情報
http://havelog.ayumusato.com/develop/javascript/e544-
backbone_learning_resources.html
Vertebraehttps://github.com/
pixelhandler/vertebrae
キャッチアップはつづく!Architecture / ModularDependency / AMDPromises / FlowControlBuild / Package ManagementTesting / Refactoring etc...💡
Thank you! おしまい
http://aho.mu @ahomu github.com/ahomu
⌂
1. Two equestrian riders, girls on horseback, in low tide reflections on serene Morro Strand State Beach http://www.flickr.com/photos/mikebaird/2985066755
2. Energy Drinks - Monster, Red Bull and Rockstar http://www.flickr.com/photos/aukirk/8170825503
3. - Good Friends http://www.flickr.com/photos/ngmmemuda/4166182931
4. Rhino relaxation http://www.flickr.com/photos/macinate/2810203599
5. Whale backbone http://www.flickr.com/photos/vagawi/2257918524/
6. Sleeping 猫 http://www.flickr.com/photos/hansel5569/7687221498/
7. Alien vs Predator http://www.flickr.com/photos/steampirate/1056958115/
Photo Credits...thx♡