Web前端框架 Backbone.Marionette
在Playframework樣例程式zentasks下的app\assets\javascripts\main.coffee中,有這樣一段話:
DISCLAMER :
If you're used to Backbone.js, you may be
confused by the absence of models, but the goal
of this sample is to demonstrate some features
of Play including the template engine.
I'm not using client-side templating nor models
for this purpose, and I do not recommend this
behavior for real life projects.
我斷定Backbone.js是個客戶端模板框架,因此我知道攤上事兒了,攤上大事兒了!便隨手把zentasks丟在地上,循著兔毛去追兔子。一路上不斷看到這隻兔子的各種傳說,撩撥的我心癢難耐,魂不守舍!因此在老婆大人訓話期間走神發呆,致使上峰震怒,被罰關小黑屋 :(
好在皇天不負有心人!幾經輾轉,終於讓我找到祕笈:Developing Backbone.js Applications。還有一篇介紹將Backbone.Marionette 用做Play 前端架構的文章:An advanced front-end architecture for Play! 2.0 with Backbone.js, Marionette, & CoffeeScript. 趕緊揀緊要的摘錄在此,以備後用。
BTW,Marionette = 牽線木偶,這是個寶貝。
Backbone.Marionette
Backbone.js 一夜間紅遍大江南北,以迅雷不及斷網之勢拿下模組化Javascript前端應用框架頭牌的位置。很大程度上得益於她良好的職業素養,見誰都是三分笑,一點不端架子,任誰都能招呼。雖然她天資聰穎,但畢竟出身於類庫之間,缺乏架構氣質,想讓她應付各種複雜局面,客官對不起,自己想轍砌。
所以對於想用Backbone.js做高階貨的玩家,入手Backbone.Marionette再合適不過了。人作者說了:“make[s] your Backbone.js apps dance with a composite application architecture!”看到沒,組合式應用架構,還輕舞飛揚,擎好吧您吶!
Backbone給Javascript準備了很多構件。有組織jQuery DOM事件,打造支援移動裝置以及大型企業應用的核心構造,但在應用設計,架構和擴充套件能力方面,開發人員得到的支援有限。
Backbone.Marionette (就叫 "Marionette" 吧) 站在了Backbone的肩膀上,給我們帶來了很多開發正經玩意兒的特性。它是為簡化大型應用構造工作而生的組合應用類庫。其中包含了一系列通用的Backbone應用設計和實現模式,是其建立者 Derick Bailey和其他 同仁 構建Backbone應用實戰經驗的結晶。
Marionette的核心價值在於:
- 模組化,事件驅動的架構
- 合理的預設配置,比如用Underscore 模板做檢視呈現
- 易於根據特定需求進行修改
- 提供特定的view型別,減少view呈現的套路化程式碼
- 用
Application
及附著在其上的模組實現模組化架構 - 藉助
Region
和Layout
,在執行時組合應用的顯示效果 - 在可見區域內的巢狀式檢視和佈局
- 內建的記憶體管理功能,可以殺死 views, regions 和 layouts 中的殭屍
- 內建的
EventBinder
事件清除 - 藉助
EventAggregator
實現的事件驅動架構 - 靈活, "用啥裝啥" 架構
- 木木的好處數不清。。。
Marionette的哲學思想師承 Backbone,它提供的元件也是那種即可獨立使用互不干擾,又可相互合作共同發力。但它沒像Backbone那樣停留在結構化的元件上,而是在應用層面上提供了很多元件(component)和構件(building block)。好吧,這有點像文字遊戲,按我目前的認識,其實老外也就隨便一說。
Marionette的元件用途很廣,但它們能合在一起形成一個組合式應用層,既可減少套路化程式碼,也能提供更合理的應用結構。其核心元件包括:
- Marionette.Application: 可以用它建立一個通過initializers啟動應用的 application 物件,不過不光如此,它還有很多能力
- Marionette.Application.module: 用來在應用內建立模組和子模組
- Marionette.AppRouter: 路由定義就應該只是配置
- Marionette.View: 讓其它Marionette views擴充套件的基本View型別 (不要和它發生直接接觸)
- Marionette.ItemView: 用來顯示一條資料項的view
- Marionette.CollectionView: 用來遍歷集合,顯示每個模型對應的
ItemView
例項 - Marionette.CompositeView: collection view 和 item view的組合, 用來顯示 分支/組合 模型的層級資料
- Marionette.Region: 管理應用中的可見區域,包括內容的顯示和移除。
- Marionette.Layout: 用來畫布局的view,還會建立區域管理器來管理其內部的regions。
- Marionette.EventAggregator: Backbone.Events的擴充套件,可以當做事件驅動或 pub-sub工具來用。
- Marionette.EventBinder: 事件繫結管理器,實現事件的繫結和unbinding
- Marionette.Renderer: 以一致和通用的方式來渲染帶或不帶資料的模板
- Marionette.TemplateCache: 快取放在
<script>
塊裡的模板, 可以提高後續訪問的速度。 - Marionette.Callbacks: 管理回撥方法集合,需要時執行它們。
但Marionette的元件也是按需取用,畢竟是Backbone家的,三觀一致啊。跟其它Backbone框架、外掛相處也很容易,其樂融融。升級的時候,也可以按元件來,不用眉毛鬍子一把抓。
Marionette App:Todo
Marionette 的核心優勢在前面都介紹過了,但實踐是檢驗真理的唯一標準,不寫個真正的程式,怎麼能體會到一個框架的好。接下來這個叫做Todo的程式,就是Marionette的試金石。所有的程式碼都在 Derick的 TodoMVC上,如果你願意,可以去github上 fork它。Developing Backbone.js Applications上還有用純粹Backbone實現的講解。
一個完整的Marionette程式,一般由以下幾個部分組成:
- application物件,其中會有初始化程式碼,以及預設佈局區域的定義。
- 佈局定義,佈局是一種特殊的view,是
Marionette.ItemView
的直接子型別。 - 路由和工作流 -Controller
- view,常見的是CompositeView和ItemView
- Model 和 Collection
接下來,我們就先看看這個程式的大廳,application物件。
CompositeView
終於可以為單個的Todo項和Todo列表定義view了。為此,我們要用CompositeView
,它是用來表示一個樹狀的組合或層級結構的視覺化元件。
你可以把這些views當做具有父子關係的層級結構,並且預設是可遞迴的。在組成view的item集合中,每個item都用CompositeView 型別的物件渲染。對於非遞迴的層級,我們可以定義一個 itemView
屬性來覆蓋item 的 view。
對於我們的Todo列表而言,我們要把Item View定義為 ItemView,而List View定義為CompositeView,並覆蓋它的itemView
設定,告訴它用ItemView表示集合中的每個item。
TodoMVC.TodoList.Views.js
TodoMVC.module('TodoList.Views', function(Views, App, Backbone, Marionette, $, _){
// Todo List Item View
// -------------------
//
// Display an individual todo item, and respond to changes
// that are made to the item, including marking completed.
Views.ItemView = Marionette.ItemView.extend({
tagName : 'li',
template : '#template-todoItemView',
ui : {
edit : '.edit'
},
events : {
'click .destroy' : 'destroy',
'dblclick label' : 'onEditClick',
'keypress .edit' : 'onEditKeypress',
'click .toggle' : 'toggle'
},
initialize : function() {
this.bindTo(this.model, 'change', this.render, this);
},
onRender : function() {
this.$el.removeClass('active completed');
if (this.model.get('completed')) this.$el.addClass('completed');
else this.$el.addClass('active');
},
destroy : function() {
this.model.destroy();
},
toggle : function() {
this.model.toggle().save();
},
onEditClick : function() {
this.$el.addClass('editing');
this.ui.edit.focus();
},
onEditKeypress : function(evt) {
var ENTER_KEY = 13;
var todoText = this.ui.edit.val().trim();
if ( evt.which === ENTER_KEY && todoText ) {
this.model.set('title', todoText).save();
this.$el.removeClass('editing');
}
}
});
// Item List View
// --------------
//
// Controls the rendering of the list of items, including the
// filtering of active vs completed items for display.
Views.ListView = Marionette.CompositeView.extend({
template : '#template-todoListCompositeView',
itemView : Views.ItemView,
itemViewContainer : '#todo-list',
ui : {
toggle : '#toggle-all'
},
events : {
'click #toggle-all' : 'onToggleAllClick'
},
initialize : function() {
this.bindTo(this.collection, 'all', this.update, this);
},
onRender : function() {
this.update();
},
update : function() {
function reduceCompleted(left, right) { return left && right.get('completed'); }
var allCompleted = this.collection.reduce(reduceCompleted,true);
this.ui.toggle.prop('checked', allCompleted);
if (this.collection.length === 0) {
this.$el.parent().hide();
} else {
this.$el.parent().show();
}
},
onToggleAllClick : function(evt) {
var isChecked = evt.currentTarget.checked;
this.collection.each(function(todo){
todo.save({'completed': isChecked});
});
}
});
// Application Event Handlers
// --------------------------
//
// Handler for filtering the list of items by showing and
// hiding through the use of various CSS classes
App.vent.on('todoList:filter',function(filter) {
filter = filter || 'all';
$('#todoapp').attr('class', 'filter-' + filter);
});
});
在最後一塊程式碼中,你應該能注意到有個事件處理器用的是 vent
。這是一個事件聚合器,我們可以用它處理 來自 TodoList 控制器的 filterItem
觸發器。
主廳 TodoMVC
我們要看下整個應用的主入口TodoMVC
,不廢話,上程式碼:
TodoMVC.js:
var TodoMVC = new Marionette.Application();
TodoMVC.addRegions({
header : '#header',
main : '#main',
footer : '#footer'
});
TodoMVC.on('initialize:after', function(){
Backbone.history.start();
});
區域是用來管理在特定元素中所顯示的內容的, 如果把TodoMVC當做主廳,那各個區域就可以算做不同房間。TodoMVC
物件中的addRegions
方法,是建立Region
物件的快捷方式。我們給每個region提供了一個jQuery選擇器(即 #header
, #main
和 #footer
) ,指明它要管理的元素,然後告訴region在那個元素內顯示各種Backbon view。
在application物件完成初始化之後,我們馬上就呼叫Backbone.history.start()
轉向初始路由。
接下來,我們要定義佈局。佈局是特殊的view,直接擴充套件自Marionette.ItemView
。也就是說我們要用它渲染某個模板,這個模板可能有關聯的model(或 item
),也可能沒有。
裝修設計圖 TodoMVC.Layout
Layout
和ItemView
有個很大的區別,它可以包含region。在定義Layout
時,我們要給出 template
,以及這個template
中所包含的region。在渲染完layout之後,我們可以用所定義的regions顯示其他views。也就是我們要通過Layout的定義確定要在每個房間裡放上什麼,以及應該具備什麼功能。
在下面的TodoMVC Layout模組中,我們要定義下面兩個Layout:
- Header: 我們可以在這裡建立新的Todo
- Footer: 我們可以在這裡顯示還有多少Todo,以及做完了多少之類的彙總資訊
這樣,之前放在 AppView
和 TodoView
中的一些view邏輯就被挪到這裡來了。
注意一下 ,Marionette module (比如下面這個) 實現了一個簡單的模組系統,用來在Marionette apps中實現私有化和封裝。然而我們不是必須要這麼做,還可以用 RequireJS + AMD 實現。
TodoMVC.Layout.js:
TodoMVC.module('Layout', function(Layout, App, Backbone, Marionette, $, _){
// Layout Header View
// ------------------
Layout.Header = Marionette.ItemView.extend({
template : '#template-header',
// UI bindings create cached attributes that
// point to jQuery selected objects
ui : {
input : '#new-todo'
},
events : {
'keypress #new-todo': 'onInputKeypress'
},
onInputKeypress : function(evt) {
var ENTER_KEY = 13;
var todoText = this.ui.input.val().trim();
if ( evt.which === ENTER_KEY && todoText ) {
this.collection.create({
title : todoText
});
this.ui.input.val('');
}
}
});
// Layout Footer View
// ------------------
Layout.Footer = Marionette.Layout.extend({
template : '#template-footer',
// UI bindings create cached attributes that
// point to jQuery selected objects
ui : {
count : '#todo-count strong',
filters : '#filters a'
},
events : {
'click #clear-completed' : 'onClearClick'
},
initialize : function() {
this.bindTo(App.vent, 'todoList:filter', this.updateFilterSelection, this);
this.bindTo(this.collection, 'all', this.updateCount, this);
},
onRender : function() {
this.updateCount();
},
updateCount : function() {
var count = this.collection.getActive().length;
this.ui.count.html(count);
if (count === 0) {
this.$el.parent().hide();
} else {
this.$el.parent().show();
}
},
updateFilterSelection : function(filter) {
this.ui.filters
.removeClass('selected')
.filter('[href="#' + filter + '"]')
.addClass('selected');
},
onClearClick : function() {
var completed = this.collection.getCompleted();
completed.forEach(function destroy(todo) {
todo.destroy();
});
}
});
});
接下來我們要處理應用的路由和工作流,比如對頁面中Layout顯示還是隱藏起來的控制。
路由器 AppRouter 及 Controller
為了簡化路由,Marionette 引入了 AppRouter
的概念。用上它之後,就不用再寫那些路由事件處理的繁瑣程式碼了,並且可以將路由器配置為直接呼叫某個物件上的方法。我們用appRoutes
配置AppRouter
。
原來用Backbone時,我們要在路由器Workspace中定義 '*filter': 'setFilter'
路由:
var Workspace = Backbone.Router.extend({
routes:{
'*filter': 'setFilter'
},
setFilter: function( param ) {
// Set the current filter to be used
window.app.TodoFilter = param.trim() || '';
// Trigger a collection reset/addAll
window.app.Todos.trigger('reset');
}
});
儘管我們用上了可讀性很強的Layouts,但原來在AppView
and TodoView
中的顯示邏輯並沒有挪過去,也要由TodoList
控制器處理。
TodoMVC.TodoList.js:
TodoMVC.module('TodoList', function(TodoList, App, Backbone, Marionette, $, _){
// TodoList Router
// ---------------
//
// Handle routes to show the active vs complete todo items
TodoList.Router = Marionette.AppRouter.extend({
appRoutes : {
'*filter': 'filterItems'
}
});
// TodoList Controller (Mediator)
// ------------------------------
//
// Control the workflow and logic that exists at the application
// level, above the implementation detail of views and models
TodoList.Controller = function(){
this.todoList = new App.Todos.TodoList();
};
_.extend(TodoList.Controller.prototype, {
// Start the app by showing the appropriate views
// and fetching the list of todo items, if there are any
start: function(){
this.showHeader(this.todoList);
this.showFooter(this.todoList);
this.showTodoList(this.todoList);
this.todoList.fetch();
},
showHeader: function(todoList){
var header = new App.Layout.Header({
collection: todoList
});
App.header.show(header);
},
showFooter: function(todoList){
var footer = new App.Layout.Footer({
collection: todoList
});
App.footer.show(footer);
},
showTodoList: function(todoList){
App.main.show(new TodoList.Views.ListView({
collection : todoList
}));
},
// Set the filter to show complete or all items
filterItems: function(filter){
App.vent.trigger('todoList:filter', filter.trim() || '');
}
});
// TodoList Initializer
// --------------------
//
// Get the TodoList up and running by initializing the mediator
// when the the application is started, pulling in all of the
// existing Todo items and displaying them.
TodoList.addInitializer(function(){
var controller = new TodoList.Controller();
new TodoList.Router({
controller: controller
});
controller.start();
});
});
在這個應用中,Controller並沒有給總體工作流新增太多東西。通常來說,按Marionette的觀點,應用中的routers應該是深思熟慮後的成果。可是,開發人員經常濫用Backbone的路由系統,整個程式的工作流和邏輯都放到一個控制器裡。
結果每種可能的程式碼組合都不可避免地放到了router方法中,建立view,載入model,協調應用中的不同部分等等。Drick覺得這破壞了單一職責原則 (SRP) 和 關注點分離原則。
Backbone的router和history是為了解決瀏覽器特定問題的 - 管理 forward 和 back 按鈕。Marionette 認為應該把他們的權力關在籠子裡,不讓他們碰通過導航執行的程式碼。這樣應用有沒有router都能用。我們可以從按鈕點選事件上,從應用的事件處理器上,或者從router上呼叫controller的show
方法,並且不管如何呼叫哪個方法,我們最終得到的應用狀態都是一樣的。
Derick 把他對這一主題的想法都寫在了他的blog上,你如果感興趣可以去看看:
- http://lostechies.com/derickbailey/2011/12/27/the-responsibilities-of-the-various-pieces-of-backbone-js/
- http://lostechies.com/derickbailey/2012/01/02/reducing-backbone-routers-to-nothing-more-than-configuration/
- http://lostechies.com/derickbailey/2012/02/06/3-stages-of-a-backbone-applications-startup/
model
最後,我們要定義表示Todo item的model和collection。
Todos.js:
TodoMVC.module('Todos', function(Todos, App, Backbone, Marionette, $, _){
// Todo Model
// ----------
Todos.Todo = Backbone.Model.extend({
localStorage: new Backbone.LocalStorage('todos-backbone'),
defaults: {
title : '',
completed : false,
created : 0
},
initialize : function() {
if (this.isNew()) this.set('created', Date.now());
},
toggle : function() {
return this.set('completed', !this.isCompleted());
},
isCompleted: function() {
return this.get('completed');
}
});
// Todo Collection
// ---------------
Todos.TodoList = Backbone.Collection.extend({
model: Todos.Todo,
localStorage: new Backbone.LocalStorage('todos-backbone'),
getCompleted: function() {
return this.filter(this._isCompleted);
},
getActive: function() {
return this.reject(this._isCompleted);
},
comparator: function( todo ) {
return todo.get('created');
},
_isCompleted: function(todo){
return todo.isCompleted();
}
});
});
一切就緒,走你!
最後,我們要在index檔案中呼叫application物件中的start
,把一切調動起來:
走你:
$(function(){
// Start the TodoMVC app (defined in js/TodoMVC.js)
TodoMVC.start();
});
打完收工!
出於Backbone而勝於Backbone的Marionette
一個真正優秀的框架,能不露聲色地從碼農手中接過那些重複的工作,而當碼農偶爾想發揮主觀能動性,做點不落俗套的事情時,它還不會讓你處處碰釘子,仍能一如既往地支援你。
有人說,Backbone不是真正的MVC框架,雖然它可能覺得委屈,但見到Marionette之後,它應該會心甘情願的接受這種說法吧。
現在有這樣一個任務,我們要在頁面中顯示一個人的資訊,模板如下所示:
<script type="text/html" id="my-view-template">
<div class="row">
<label>First Name:</label>
<span><%= firstName %></span>
</div>
<div class="row">
<label>Last Name:</label>
<span><%= lastName %></span>
</div>
<div class="row">
<label>Email:</label>
<span><%= email %></span>
</div>
</script>
接下來,我們會讓Marionette用這個機會來證明自己的藍。
套路化的渲染程式碼
看下下面這段程式碼,是用Backbone 和 Underscore 模板渲染檢視的典型實現。首先要有個模板,可以直接放在DOM裡,然後要用Javascript定義使用這個模板的檢視,並從model裡得到資料放到模板裡。是的,這也是MVC。
佛說:一沙一世界,一花一菩提,須彌芥子,皆存玄虛。大MVC裡套著無數個小MVC。
定義模板
<script type="text/html" id="my-view-template">
<div class="row">
<label>First Name:</label>
<span><%= firstName %></span>
</div>
<div class="row">
<label>Last Name:</label>
<span><%= lastName %></span>
</div>
<div class="row">
<label>Email:</label>
<span><%= email %></span>
</div>
</script>
定義view
var MyView = Backbone.View.extend({
template: $('#my-view-template').html(),
render: function(){
// compile the Underscore.js template
var compiledTemplate = _.template(this.template);
// render the template with the model data
var data = this.model.toJSON();
var html = compiledTemplate(data);
// populate the view with the rendered html
this.$el.html(html);
}
});
這些做好之後,可以建立view的例項,將model傳給它。然後把view的el
加到DOM中,顯示的工作就完成了。
給model套上模板
var myModel = new MyModel({
firstName: 'Derick',
lastName: 'Bailey',
email: 'derickbailey@gmail.com'
});
var myView = new MyView({
model: myModel
})
myView.render();
$('#content').html(myView.el)
這是用Backbone定義,構建,渲染和顯示的標準實現。也就是我們所說的“套路化程式碼”,一遍遍的重複,每個專案,每個相同的功能性實現中都有這些程式碼。繁瑣,重複,毫無趣味。
接下來我們要請出 Marionette ItemView
- 用它,view 定義可以變得更簡潔!
Marionette用ItemView拯救你
Marionette的所有view型別,除了 Marionette.View
,都自帶一個render
方法,可以幫你處理渲染的核心邏輯。放棄Backbone.View
吧,給MyView
換個型別,就可以用上這個方法。不用再自己給view實現render
方法,渲染的工作就交給Marionette吧。我們還用Underscore.js模板和渲染機制,但可以不用關心具體實現了。所以,也不用寫那麼多程式碼了。
擴充套件ItemView
var MyView = Marionette.ItemView.extend({
template: '#my-view-template'
});
程式碼就是這些,跟前面那個view實現的功能一模一樣。只要把 Backbone.View.extend
換成 Marionette.ItemView.extend
, 然後刪掉 render
方法就行了。你還是可以用model
建立view例項,在view例項上呼叫render
方法,並以同樣的方式在DOM中顯示view。但view定義只剩一行了,只需要配置下模板就行。
勝在區域管理
在view建立好之後,一般要把它放到DOM中,這樣才能把它顯示出來。Backbone一般是用jQuery選擇器,並設定結果物件的 html()
:
顯示view
var myModel = new MyModel({
firstName: 'Jeremy',
lastName: 'Ashkenas',
email: 'jeremy@gmail.com'
});
var myView = new MyView({
model: myModel
})
myView.render();
// show the view in the DOM
$('#content').html(myView.el)
這還是那種比較繁瑣的程式碼。我們不應該手工呼叫 render
,還手工選擇顯示view的DOM元素。另外,這些程式碼也沒關閉之前附到DOM元素上的view例項。一大波殭屍正在朝你走來!
為了解決這個問題,Marionette 做了個Region
物件 - 由它負責管理每個view的生命週期,以及在特定DOM元素中的顯示。
Region物件
// create a region instance, telling it which DOM element to manage
var myRegion = new Marionette.Region({
el: '#content'
});
// show a view in the region
var view1 = new MyView({ /* ... */ });
myRegion.show(view1);
// somewhere else in the code,
// show a different view
var view2 = new MyView({ /* ... */ });
myRegion.show(view2);
上面的程式碼中有幾個事兒需要注意。首先,我們要告訴region要管理哪個DOM元素,在region例項中指定el
。其次,我們不用親自呼叫view中的render
方法。最後,我們也不用呼叫view中的close
方法,Marionette替我們呼叫它了。
當我們用region管理view的生命週期,並在DOM中顯示view時,region自己就會把這些問題處理掉。把view例項傳給region的 show
方法,它會幫我們呼叫view上的render
方法。然後會拿到view上的結果 el
,組裝DOM元素。
我們再次呼叫 show
方法時,region知道它現在正在顯示view。region會呼叫view上的 close
方法,把它從DOM上挪走,然後就執行新view上的 render & display程式碼。
既然region幫我們呼叫close
,我們還在view例項上用bindTo
事件繫結器,就不用再擔心程式中出現殭屍view了。
勝在記憶體管理
除了縮減了view定義的程式碼,Marionette所有view中還有些先進的記憶體管理功能,使得view例項的清除工作和事件處理更容易了。
看下面的view實現:
var ZombieView = Backbone.View.extend({
template: '#my-view-template',
initialize: function(){
// bind the model change to re-render this view
this.model.on('change', this.render, this);
},
render: function(){
// This alert is going to demonstrate a problem
alert('We`re rendering the view');
}
});
如果我們用相同的變數名稱建立這個view的兩個例項,然後修改model中的一個值,能看到幾次警告框?
建立兩個view例項
var myModel = new MyModel({
firstName: 'Jeremy',
lastName: 'Ashkenas',
email: 'jeremy@example.com'
});
// create the first view instance
var zombieView = new ZombieView({
model: myModel
})
// create a second view instance, re-using
// the same variable name to store it
zombieView = new ZombieView({
model: myModel
})
myModel.set('email', 'jeremy@gmail.com');
按常理,因為兩個例項都用zombieView
變數名,所以view的第二個例項建立後,第一個例項立馬就會落到作用域之外。Javascript可以通過垃圾收集把它清除掉,也就是說第一個view例項不再有用,也不會對模型的“change”事件作出響應。
但執行這段程式碼時,警告框還是出現了兩次!
出現這種問題,是因為model事件繫結是在view的initialize
方法中。無論什麼時候把this.render
當做回撥方法傳給model的 on
事件繫結,model也會得到一個對view例項的直接引用。就是因為model中有對view例項的引用,所以即使換掉 zombieView
變數引用的view例項,不再被 zombieView
引用的view例項也不會落到作用域之外。前面說過了,model中還有對它的直接引用。
既然最初那個view還在作用域內,第二個view例項也在,兩個例項自然都會對model中的資料修改做出響應。
解決這個問題很簡單。只要在用完view後呼叫model上的off
方法,讓這個事件繫結處於準關閉的狀態就行。為此,要在view里加個close
方法。
增加close方法的View定義
var ZombieView = Backbone.View.extend({
template: '#my-view-template',
initialize: function(){
// bind the model change to re-render this view
this.model.on('change', this.render, this);
},
close: function(){
this.model.off('change', this.render, this);
},
render: function(){
// This alert is going to demonstrate a problem
alert('We`re rendering the view');
}
});
不再需要第一個例項後,只需在其上呼叫close
方法就行,活著的view例項就只剩一個了。
close方法演示
var myModel = new MyModel({
firstName: 'Jeremy',
lastName: 'Ashkenas',
email: 'jeremy@example.com'
});
// create the first view instance
var zombieView = new ZombieView({
model: myModel
})
zombieView.close(); // double-tap the zombie
// create a second view instance, re-using
// the same variable name to store it
zombieView = new ZombieView({
model: myModel
})
myModel.set('email', 'jeremy@gmail.com');
再執行程式碼,就只剩一個警告框了。
有了Marionette,我們就不用自己寫程式碼關閉事件處理器了。
Marionette的記憶體管理
var ZombieView = Marionette.ItemView.extend({
template: '#my-view-template',
initialize: function(){
// bind the model change to re-render this view
this.bindTo(this.model, 'change', this.render, this);
},
render: function(){
// This alert is going to demonstrate a problem
alert('We`re rendering the view');
}
});
注意上面的程式碼,我們用bindTo
方法取代了on
方法。這個方法在Marionette的EventBinder
物件中定義,在所有view型別中都能用。 bindTo
的方法簽名跟on
方法差不多,只是把觸發事件的物件不再是呼叫者,而是變成了方法的第一個引數。
Marionette的view中還有一個 close
方法,用來'off'掉事件的繫結。用bindTo
設定的事件繫結會被自動關閉。也就是說我們不用親自定義,和呼叫 close
方法,只要用bindTo
方法設定事件繫結,我們就可以放寬心,事件繫結會被自動'off'掉,我們的views也不會變成殭屍。
但view上 close
方法的自動呼叫是怎麼實現的呢?什麼時候,以及在哪呼叫它呢?請看Marionette.Region
- 這是負責管理每個view生命週期的物件。
相關文章
- 前端web框架前端Web框架
- web前端培訓:常用的Web前端開發框架有哪些?Web前端框架
- Web前端培訓分享:Web前端三大主流框架對比!Web前端框架
- Web前端開發框架有哪些?Web前端框架
- web前端三大主流框架對比分析Web前端框架
- 好程式設計師web前端乾貨之web前端開發框架彙總程式設計師Web前端框架
- web前端技術分享:常用JavaScript框架有哪些?Web前端JavaScript框架
- 入門Web前端要學哪些主流框架呢?Web前端框架
- 移動APP開發框架盤點2:Web移動前端框架大全APP框架Web前端
- Web前端開發最好用的幾個WebGL框架Web前端框架
- 長沙Web前端培訓:怎麼才算學好Vue前端框架Web前端Vue框架
- web前端乾貨:詳細瞭解JS前端開發框架都有哪些Web前端JS框架
- 學習web前端你必須要了解的主流框架!Web前端框架
- 好程式設計師web前端教程分享前端三大框架有哪些異同程式設計師Web前端框架
- 好程式設計師web前端教程分享三大前端框架相關問題程式設計師Web前端框架
- web 前端Web前端
- web前端,使用HTML5+CSS+JS框架有那些好處Web前端HTMLCSSJS框架
- 好程式設計師web前端培訓分享JavaScript框架J程式設計師Web前端JavaScript框架
- Web前端要學什麼框架呢?推薦這幾款Web前端框架
- 從零實現MVVM模式的Web前端框架的雛形MVVM模式Web前端框架
- 前端框架前端框架
- 初學者怎麼學懂前端?Web前端原始碼、框架學習路線圖前端Web原始碼框架
- web ui 框架WebUI框架
- Web前端如何學?Web前端學習方法分享Web前端
- Web前端飽和了?還能學Web前端嗎?Web前端
- 騰訊釋出新版前端元件框架 Omi,全面擁抱 Web Components前端元件框架Web
- 月薪5萬,全靠這款高質量Web前端開發框架!Web前端框架
- 常用的Web前端開發框架有哪些呢?分享這11個Web前端框架
- Web前端攻防Web前端
- Web前端是什麼?Web前端包括哪些技術?Web前端
- 前端框架_Vue前端框架Vue
- Hapi.js 起步 - 寫給前端開發的 Node Web 框架入門APIJS前端Web框架
- 好用的Web前端開發框架有哪些呢?推薦這9款Web前端框架
- golang web框架,golang版本laravel 框架GolangWeb框架Laravel
- Rust Web框架列表RustWeb框架
- Java Web UI框架JavaWebUI框架
- Web框架之TornadoWeb框架
- Web前端怎麼學?如何成為Web前端工程師?Web前端工程師
- Web前端培訓分享:Web前端到底是什麼?Web前端