溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

BACKBONE.JS 簡單入門范例

發(fā)布時間:2020-09-07 16:08:24 來源:腳本之家 閱讀:99 作者:Perchouli 欄目:web開發(fā)

11年剛開始用前端MVC框架時寫過一篇文章,當(dāng)時Knockout和Backbone都在用,但之后的項目全是在用Backbone,主要因為它簡單、靈活,無論是富JS應(yīng)用還是企業(yè)網(wǎng)站都用得上。相比React針對View和單向數(shù)據(jù)流的設(shè)計,Backbone更能體現(xiàn)MVC的思路,所以針對它寫一篇入門范例,說明如下:

1. 結(jié)構(gòu)上分4節(jié),介紹Model/View/Collection,實現(xiàn)從遠(yuǎn)程獲取數(shù)據(jù)顯示到表格且修改刪除;
2. 名為“范例”,所以代碼為主,每節(jié)的第1段代碼都是完整代碼,復(fù)制粘貼就能用,每段代碼都是基于前一段代碼來寫的,因此每段代碼的新內(nèi)容不會超過20行(大括號計算在內(nèi));
3. 每行代碼沒有注釋,但重要內(nèi)容之后有寫具體的說明;
4. 開發(fā)環(huán)境是Chrome,使用github的API,這樣用Chrome即使在本地路徑(形如file://的路徑)也能獲取數(shù)據(jù)。

0. Introduction

幾乎所有的框架都是在做兩件事:一是幫你把代碼寫在正確的地方;二是幫你做一些臟活累活。Backbone實現(xiàn)一種清晰的MVC代碼結(jié)構(gòu),解決了數(shù)據(jù)模型和視圖映射的問題。雖然所有JS相關(guān)的項目都可以用,但Backbone最適合的還是這樣一種場景:需要用JS生成大量的頁面內(nèi)容(HTML為主),用戶跟頁面元素有很多的交互行為。

Backbone對象有5個重要的函數(shù),Model/Collection/View/Router/History。Router和History是針對Web應(yīng)用程序的優(yōu)化,建議先熟悉pushState的相關(guān)知識。入門階段可以只了解Model/Collection/View。將Model視為核心,Collection是Model的集合,View是為了實現(xiàn)Model的改動在前端的反映。

1. Model

Model是所有JS應(yīng)用的核心,很多Backbone教程喜歡從View開始講,其實View的內(nèi)容不多,而且理解了View意義不大,理解Model更重要。以下代碼實現(xiàn)從github的API獲取一條gist信息,顯示到頁面上:

<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="https://code.jquery.com/jquery-1.11.1.js"></script>
<script type="text/javascript" src="http://underscorejs.org/underscore-min.js"></script>
<script type="text/javascript" src="http://backbonejs.org/backbone-min.js"></script>

<link href="http://cdn.bootcss.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="stylesheet">
</head>
<body>
 <table id="js-id-gists" class="table">
  <thead><th>description</th><th>URL</th><th>created_at</th></thead>
  <tbody></tbody>
 </table>
 <script type="text/javascript">
 var Gist = Backbone.Model.extend({
  url: 'https://api.github.com/gists/public',
  parse: function (response) {
   return (response[0]);
  }
 }),
  gist = new Gist();

 gist.on('change', function (model) {
  var tbody = document.getElementById('js-id-gists').children[1],
   tr = document.getElementById(model.get('id'));
  if (!tr) {
   tr = document.createElement('tr');
   tr.setAttribute('id', model.get('id'));
  }
  tr.innerHTML = '<td>' + model.get('description') + '</td><td>' + model.get('url') + '</td><td>' + model.get('created_at') + '</td>';
  tbody.appendChild(tr);
 });
 gist.fetch();
 </script>
</body>
</html>

LINE4~8: 加載要用到的JS庫。ajax請求和部分View的功能需要jQuery支持(或者重寫ajax/View的功能);Backbone的代碼是基于Underscore寫的(或者用Lo-Dash代替);加載bootstrap.css只是因為默認(rèn)樣式太難看…

LINE16~22: 創(chuàng)建一個Model并實例化。url是數(shù)據(jù)源(API接口)的地址,parse用來處理返回的數(shù)據(jù)。實際返回的是一個Array,這里取第一個Object。

LINE24~33: 綁定change事件。還沒有使用View,所以要自己處理HTML。這10行代碼主要是get的用法(model.get),其他的功能之后會用View來實現(xiàn)。

LINE34: 執(zhí)行fetch。從遠(yuǎn)程獲取數(shù)據(jù),獲到數(shù)據(jù)后會觸發(fā)change事件。可以重寫sync方法

打開Chrome的Console,輸入gist,可以看到Model獲得的屬性:

BACKBONE.JS 簡單入門范例

Model提供數(shù)據(jù)和與數(shù)據(jù)相關(guān)的邏輯。上圖輸出的屬性是數(shù)據(jù),代碼中的fetch/parse/get/set都是對數(shù)據(jù)進(jìn)行操作,其他的還有escape/unset/clear/destory,從函數(shù)名字就大致可以明白它的用途。還有很常用的validate函數(shù),在set/save操作時用來做數(shù)據(jù)驗證,驗證失敗會觸發(fā)invalid事件:

/* 替換之前代碼的JS部分(LINE16~34) */
 var Gist = Backbone.Model.extend({
  url: 'https://api.github.com/gists/public',
  parse: function (response) {
   return (response[0]);
  },
  defaults: {
   website: 'dmyz'
  },
  validate: function (attrs) {
   if (attrs.website == 'dmyz') {
    return 'Website Error';
   }
  }
 }),
  gist = new Gist();

 gist.on('invalid', function (model, error) {
  alert(error);
 });
 gist.on('change', function (model) {
  var tbody = document.getElementById('js-id-gists').children[1],
   tr = document.getElementById(model.get('id'));
  if (!tr) {
   tr = document.createElement('tr');
   tr.setAttribute('id', model.get('id'));
  }
  tr.innerHTML = '<td>'+ model.get('description') +'</td><td>'+ model.get('url') +'</td><td>'+ model.get('created_at') +'</td>';
  tbody.appendChild(tr);
 });
 gist.save();

跟之前的代碼比較,有4處改動:

LINE7~9: 增加了defaults。如果屬性中沒有website(注意不是website值為空),會設(shè)置website值為dmyz。
LINE10~14: 增加validate函數(shù)。當(dāng)website值為dmyz時,觸發(fā)invalid事件。
LINE18~20: 綁定invalid事件,alert返回的錯誤。
LINE31: 不做fetch,直接save操作。

因為沒有fetch,所以頁面上不會顯示數(shù)據(jù)。執(zhí)行save操作,會調(diào)用validate函數(shù),驗證失敗會觸發(fā)invalid事件,alert出錯誤提示。同時save操作也會向Model的URL發(fā)起一個PUT請求,github這個API沒有處理PUT,所以會返回404錯誤。

在Console中輸入gist.set(‘description', ‘demo'),可以看到頁面元素也會有相應(yīng)的變化。執(zhí)行g(shù)ist.set(‘description', gist.previous(‘description'))恢復(fù)之前的值。這就是Model和View的映射,現(xiàn)在還是自己實現(xiàn)的,下一節(jié)會用Backbone的View來實現(xiàn)。

2. View

用Backbone的View來改寫之前代碼LINE24~33部分:

<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="https://code.jquery.com/jquery-1.11.1.js"></script>
<script type="text/javascript" src="http://underscorejs.org/underscore-min.js"></script>
<script type="text/javascript" src="http://backbonejs.org/backbone-min.js"></script>

<link  rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="stylesheet">
</head>
<body>
 <table id="js-id-gists" class="table">
  <thead><th>description</th><th>URL</th><th>created_at</th><th></th></thead>
  <tbody></tbody>
 </table>
 <script type="text/javascript">
 var Gist = Backbone.Model.extend({
  url: 'https://api.github.com/gists/public',
  parse: function (response) {
   return response[0];
  }
 }),
  gist = new Gist();

 var GistRow = Backbone.View.extend({
  el: 'tbody',
  MODEL: gist,
  events: {
   'click a': 'replaceURL'
  },
  replaceURL: function () {
   this.MODEL.set('url', 'http://dmyz.org');
  },
  initialize: function () {
   this.listenTo(this.MODEL, 'change', this.render);
  },
  render: function () {
   var model = this.MODEL,
    tr = document.createElement('tr');
   tr.innerHTML = '<td>' + model.get('description') + '</td><td>' + model.get('url') + '</td><td>' + model.get('created_at') + '</td><td><a href="javascript:void(0)" rel="external nofollow" rel="external nofollow" rel="external nofollow" >&reg;</a></td>';
   this.el.innerHTML = tr.outerHTML;
   return this;
  }
 });
 var tr = new GistRow();
 gist.fetch();
 </script>
</body>
</html>

LINE25: 所有的View都是基于DOM的,指定el會選擇頁面的元素,指定tagName會創(chuàng)建相應(yīng)的DOM,如果都沒有指定會是一個空的div。
LINE27~32: 綁定click事件到a標(biāo)簽,replaceURL函數(shù)會修改(set)url屬性的值。
LINE33~35: View的初始化函數(shù)(initialize),監(jiān)聽change事件,當(dāng)Model數(shù)據(jù)更新時觸發(fā)render函數(shù)。
LINE36~42: render函數(shù)。主要是LINE41~42這兩行,把生成的HTML代碼寫到this.el,返回this。
LINE44: 實例化GistRow,初始化函數(shù)(initialize)會被執(zhí)行。

點擊行末的a標(biāo)簽,頁面顯示的這條記錄的URL會被修改成http://dmyz.org。

這個View名為GistRow,選擇的卻是tbody標(biāo)簽,這顯然是不合理的。接下來更改JS代碼,顯示API返回的30條數(shù)據(jù):

/* 替換之前代碼的JS部分(LINE16~45) */
 var Gist = Backbone.Model.extend(),
  Gists = Backbone.Model.extend({
   url: 'https://api.github.com/gists/public',
   parse: function (response) {
    return response;
   }
  }),
  gists = new Gists();

 var GistRow = Backbone.View.extend({
  tagName: 'tr',
  render: function (object) {
   var model = new Gist(object);
   this.el.innerHTML = '<td>' + model.get('description') + '</td><td>'+ model.get('url') + '</td><td>' + model.get('created_at') + '</td><td></td>'
   return this;
  }
 });

 var GistsView = Backbone.View.extend({
  el: 'tbody',
  model: gists,
  initialize: function () {
   this.listenTo(this.model, 'change', this.render);
  },
  render: function () {
   var html = '';
   _.forEach(this.model.attributes, function (object) {
    var tr = new GistRow();
    html += tr.render(object).el.outerHTML;
   });
   this.el.innerHTML = html;
   return this;
  }
 });
 var gistsView = new GistsView();
 gists.fetch();

LINE2~9: 創(chuàng)建了兩個Model(Gist和Gists),parse現(xiàn)在返回完整Array而不只是第一條。
LINE11~18: 創(chuàng)建一個tr。render方法會傳一個Object來實例化一個Gist的Model,再從這個Model里get需要的值。
LINE26~34: 遍歷Model中的所有屬性?,F(xiàn)在使用的是Model而不是Collection,所以遍歷出的是Object。forEach是Underscore的函數(shù)。

Backbone的View更多的是組織代碼的作用,它實際干的活很少。View的model屬性在本節(jié)第一段代碼用的是大寫,表明只是一個名字,并不是說給View傳一個Model它會替你完成什么,控制邏輯還是要自己寫。還有View中經(jīng)常會用到的template函數(shù),也是要自己定義的,具體結(jié)合哪種模板引擎來用就看自己的需求了。

這段代碼中的Gists比較難操作其中的每一個值,它其實應(yīng)該是Gist的集合,這就是Backbone的Collection做的事了。

3. Collection

Collection是Model的集合,在這個Collection中的Model如果觸發(fā)了某個事件,可以在Collection中接收到并做處理。第2節(jié)的代碼用Collection實現(xiàn):

<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="https://code.jquery.com/jquery-1.11.1.js"></script>
<script type="text/javascript" src="http://underscorejs.org/underscore-min.js"></script>
<script type="text/javascript" src="http://backbonejs.org/backbone-min.js"></script>

<link  rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="stylesheet">
</head>
<body>
 <table id="js-id-gists" class="table">
  <thead><th>description</th><th>URL</th><th>created_at</th><th></th></thead>
  <tbody></tbody>
 </table>
 <script type="text/javascript">
 var Gist = Backbone.Model.extend(),
  Gists = Backbone.Collection.extend({
   model: Gist,
   url: 'https://api.github.com/gists/public',
   parse: function (response) {
    return response;
   }
  }),
  gists = new Gists();

 var GistRow = Backbone.View.extend({
  tagName: 'tr',
  render: function (model) {
   this.el.innerHTML = '<td>' + model.get('description') + '</td><td>'+ model.get('url') + '</td><td>' + model.get('created_at') + '</td><td></td>'
   return this;
  }
 });

 var GistsView = Backbone.View.extend({
  el: 'tbody',
  collection: gists,
  initialize: function () {
   this.listenTo(this.collection, 'reset', this.render);
  },
  render: function () {
   var html = '';
   _.forEach(this.collection.models, function (model) {
    var tr = new GistRow();
    html += tr.render(model).el.outerHTML;
   });
   this.el.innerHTML = html;
   return this;
  }
 });
 var gistsView = new GistsView();
 gists.fetch({reset: true});
 </script>
</body>
</html>

LINE17~23: 基本跟第2節(jié)的第2段代碼一樣。把Model改成Collection,指定Collection的Model,這樣Collectio獲得返回值會自動封裝成Model的Array。
LINE38: Collection和Model不同,獲取到數(shù)據(jù)也不會觸發(fā)事件,所以綁定一個reset事件,在之后的fetch操作中傳遞{reset: true}。
LINE42~45: 從Collection從遍歷Model,傳給GistRow這個View,生成HTML。

Collection是Backbone里功能最多的函數(shù)(雖然其中很多是Underscore的),而且只要理解了Model和View的關(guān)系,使用Collection不會有任何障礙。給Collection綁定各種事件來實現(xiàn)豐富的交互功能了,以下這段JS代碼會加入刪除/編輯的操作,可以在JSBIN上查看源代碼和執(zhí)行結(jié)果。只是增加了事件,沒有什么新內(nèi)容,所以就不做說明了,附上JSBIN的演示地址:http://jsbin.com/jevisopo/1

/* 替換之前代碼的JS部分(LINE16~51) */
 var Gist = Backbone.Model.extend(),
  Gists = Backbone.Collection.extend({
   model: Gist,
   url: 'https://api.github.com/gists/public',
   parse: function (response) {
    return response;
   }
  }),
  gists = new Gists();

 var GistRow = Backbone.View.extend({
  tagName: 'tr',
  render: function (model) {
   this.el.id = model.cid;
   this.el.innerHTML = '<td>' + model.get('description') + '</td><td>'+ model.get('url') + '</td><td>' + model.get('created_at') + '</td><td><a href="javascript:void(0)" rel="external nofollow" rel="external nofollow" rel="external nofollow" class="js-remove">X</a> <a href="javascript:void(0)" rel="external nofollow" rel="external nofollow" rel="external nofollow" class="js-edit">E</a>&nbsp;</td>'
   return this;
  }
 });

 var GistsView = Backbone.View.extend({
  el: 'tbody',
  collection: gists,
  events: {
   'click a.js-remove': function (e) {
    var cid = e.currentTarget.parentElement.parentElement.id;
    gists.get(cid).destroy();
    gists.remove(cid);
   },
   'click a.js-edit': 'editRow',
   'blur td[contenteditable]': 'saveRow'
  },
  editRow: function (e) {
   var tr = e.currentTarget.parentElement.parentElement,
    i = 0;

   while (i < 3) {
    tr.children[i].setAttribute('contenteditable', true);
    i++;
   }
  },
  saveRow: function (e) {
   var tr = e.currentTarget.parentElement,
    model = gists.get(tr.id);

   model.set({
    'description' : tr.children[0].innerText,
    'url': tr.children[1].innerText,
    'created_at': tr.children[2].innerText
   });
   model.save();
  },
  initialize: function () {
   var self = this;
   _.forEach(['reset', 'remove', 'range'], function (e) {
    self.listenTo(self.collection, e, self.render);
   });
  },
  render: function () {
   var html = '';
   _.forEach(this.collection.models, function (model) {
    var tr = new GistRow();
    html += tr.render(model).el.outerHTML;
   });
   this.el.innerHTML = html;
   return this;
  }
 });
 var gistsView = new GistsView();
 gists.fetch({reset: true});

Afterword

雖然是入門范例,但因為篇幅有限,有些基本語言特征和Backbone的功能不可能面面俱到,如果還看不懂肯定是我漏掉了需要解釋的點,請(在Google之后)評論或是郵件告知。

Backbone不是jQuery插件,引入以后整個DOM立即實現(xiàn)增刪改查了,也做不到KnockoutJS/AnglarJS那樣,在DOM上做數(shù)據(jù)綁定就自動完成邏輯。它是將一些前端工作處理得更好更規(guī)范,如果學(xué)習(xí)前端MVC的目的是想輕松完成工作,Backbone可能不是最佳選擇。如果有一個項目,100多行HTML和1000多行JS,JS主要都在操作頁面DOM(如果討厭+號連接HTML還可以搭配React/JSX來寫),那就可以考慮用Backbone來重寫了,它比其他龐大的MVC框架要容易掌握得多,作為入門學(xué)習(xí)也是非常不錯的。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI