主題:如何在應用中使用MVC模型乳蛾,包括加載和操作遠程數(shù)據
包括以下分支:
1.為什么構建ORM類庫時,MVC和命名空間很重要
2.如何使用ORM類庫來管理模型數(shù)據
3.如何使用JSONP和跨域Ajax來遠程加載數(shù)據
4.如何通過使用HTML5本地存儲和將本地存儲提交至RESTful服務器鄙币,來實現(xiàn)模型數(shù)據持久化
1.為什么構建ORM類庫時肃叶,MVC和命名空間很重要
把數(shù)據管理工作遷到前臺的一個好處:
- 客戶端數(shù)據存儲速度非常快十嘿,幾乎是瞬間讀取因惭,因為數(shù)據是直接從內存中獲得的。這會讓你的應用接口變得與眾不同绩衷,所有交互操作都會瞬間得到響應蹦魔,這極大地提升了用戶體驗激率。
那么怎么在前端,也就是客戶端架構數(shù)據存儲的模型呢勿决?
引入MVC乒躺,那么數(shù)據管理則歸入模型(M)。模型應當從視圖和控制器中解耦出來低缩。與數(shù)據操作和行為相關的邏輯都應當放入模型中嘉冒,通過命名空間進行管理(模塊化,引入需要的咆繁,再暴露出API)讳推。
但這里的命名空間主要指通過對象/類來封裝模型,避免模型暴露在全局玩般,導致可能的作用域污染银觅。
用對象封裝后,數(shù)據通常用數(shù)組來存儲(數(shù)組內又可以嵌套對象)坏为,或者用Ajax等調用遠程服務器數(shù)據的函數(shù)來獲取數(shù)據(然后也可存在前面的數(shù)組究驴,或者看具體場景)。
為了讓模型更加抽象久脯,可復用纳胧,需要把模型寫成類镰吆。在JS里則是用原型繼承
e.g:
var User = function(atts) {
this.attributes = atts || {};
};
User.prototype.destory = function () {
/ ... /
};
(ps:這是一種不被推薦的類寫法帘撰,在這只用作例子說明)
對于那些不需要復用,只在User內部使用的函數(shù)或變量就可以不作為原型繼承万皿,直接寫在User里:
User.fetchRemote = function () {
/ .... /
};
這個方法就只是User擁有摧找,而不會被實例繼承。
小結:通過命名空間和MVC的分層牢硅,能更好的對數(shù)據進行管理蹬耘,避免出現(xiàn)混亂的情況。
2.如何使用ORM類庫來管理模型數(shù)據
對象關系映射(ORM, Object-relational mapper)是除了JS外的編程語言中常見的一種數(shù)據結構减余。
由于現(xiàn)在前端承擔了更多的任務综苔,所以有了管理數(shù)據的需求,因此對象關系映射對于JS來說也成了一種非常有用的技術位岔,它可以用來做數(shù)據管理和用做模型如筛。
比如用ORM將模型和遠程服務綁在一起,然后模型實例的改變都會發(fā)起Ajax請求到服務器端(或get數(shù)據或put數(shù)據)抒抬⊙钆伲或者將模型實例和HTML元素綁定,模型實例的數(shù)據改變會在界面中反映出來擦剑。
如何自定義一個ORM
這里使用Object.create()妖胀。傳入一個原型對象作為參數(shù)芥颈,會返回一個繼承了這個原型對象的新對象。
JS的原型繼承機制十分強大赚抡,用這個繼承機制寫的Model十分靈活(因為在模型創(chuàng)建好后爬坑,還可以動態(tài)擴展屬性方法,不僅對模型本身擴展涂臣,對模型的實例也可以)妇垢。
現(xiàn)在創(chuàng)建Model對象:
var Model = {
inherited: function () {}, // 存放被繼承的模型
created: function () {},
prototype: {
init: function () {}
},
create: function () {
var object = Object.create(this);
object.parent = this;
object.prototype = object.fn = Object.create(this.prototype);
object.created();
this.inherited(object);
return object;
},
init: function () {
var instance = Object.create(this.prototype);
instance.parent = this;
instance.init.apply(instance, arguments);
return instance;
}
};
create()函數(shù)返回新對象,這個對象繼承自Model對象肉康,我們用這個返回的新對象來賦值給新模型闯估。
init()函數(shù)返回新對象,這個對象繼承自Model.prototype吼和,即繼承自Model對象的新對象的實例涨薪。這么說有點繞,展示示例:
var Asset = Model.create();
var User = Model.create();
這是用create()函數(shù)創(chuàng)建的新模型炫乓。
var user = User.init();
這是用init()函數(shù)返回的一個實例刚夺,繼承了Model.prototype的所有屬性和方法。相反末捣,這也意味著可以通過給Model.prototype添加屬性和方法侠姑,來動態(tài)的給所有實例添加這些屬性和方法。
當然箩做,我們不直接這么用莽红,而是用extend()和include()這兩個函數(shù)封裝添加模型的屬性方法
和添加實例的屬性方法
這兩個功能:
var Model = {
inherited: function () {},
created: function () {},
prototype: {
init: function () {}
},
create: function () {
var object = Object.create(this);
object.parent = this;
object.prototype = object.fn = Object.create(this.prototype);
object.created();
this.inherited(object);
return object;
},
init: function () {
var instance = Object.create(this.prototype);
instance.parent = this;
instance.init.apply(instance, arguments);
return instance;
},
// 添加繼承的模型的屬性
extend: function (obj) {
var extended = obj.extended;
jQuery.extend(this, obj );
if (extended) {
extended(this);
}
},
// 添加繼承的模型的實例的屬性
include: function (obj) {
var included = o.included;
jQuery.extend(this.prototype, obj);
if (included) {
included(this);
}
}
};
extend()給Asset和User模型添加屬性:
Model.extend({
find: function () {}
};
Asset.find();
// Asset模型擁有了find,前提是先給Model添加find
// 不是基于原型的這種不能動態(tài)添加屬性方法
include()方法給user實例動態(tài)添加屬性邦邦,即不論創(chuàng)建時間的先后:
Model.include({
init: function (atts) {
if (atts) {
this.load(atts);
}
},
load: function (attributes) {
for (var name in attributes) {
this[name] = attributes[name];
}
}
});
// 實例便擁有了init()和load()方法
var asset = Asset.init( {name: "foo.png"} );
下面則是進一步完善的模型:
var Model = {
created: function () {},
prototype: {
init: function () {}
},
create: function () {
var object = Object.create(this);
object.parent = this;
object.prototype = object.fn = Object.create(this.prototype);
object.created();
this.inherited(object);
return object;
},
init: function () {
var instance = Object.create(this.prototype);
instance.parent = this;
instance.init.apply(instance, arguments);
return instance;
},
// 添加繼承的模型的屬性
extend: function (obj) {
var extended = obj.extended;
jQuery.extend(this, obj );
if (extended) {
extended(this);
}
},
// 添加繼承的模型的實例的屬性
include: function (obj) {
var included = obj.included;
jQuery.extend(this.prototype, obj);
if (included) {
included(this);
}
}
};
Model.extend({
// 一個繼承模型的實例想要持久化記錄數(shù)據
created: function () {
this.records = {};
},
find: function () {
var record = this.records[id];
if (!record) {
throw("Unknown record");
}
return record.dup();
}
});
// 模型繼承時安吁,繼承的模型可以傳入自定義的屬性
Model.include({
init: function (atts) {
if (atts) {
this.load(atts);
}
},
load: function (attributes) {
for (var name in attributes) {
this[name] = attributes[name];
}
}
});
Model.include({
// 初始化為true,因為本就是新增加的一條記錄
newRecord: true,
create: function () {
// 為每個實例加上ID
if (!this.id) {
this.id = Math.guid();
}
// 創(chuàng)建了一天燃辖,所以要把newRecord這個狀態(tài)判斷換位false
this.newRecord = false;
// 存入records
this.parent.records[this.id] = this.dup();
},
destroy: function () {
delete this.parent.records[this.id];
},
update: function () {
this.parent.records[this.id] = this.dup();
},
dup: function () {
return jQuery.extend(true, {}, this);
}
// 這個方法作用是每次給實例添加好數(shù)據后鬼店,都使用save來保存,
// 并且有個判斷黔龟,如果是新記錄則創(chuàng)建妇智,已經存在則更新
save: function () {
this.newRecord ? this.create() : this.update();
}
});
// 生成隨機ID
Math.guid = function(){
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g,
function(c) {
var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
return v.toString(16);
}).toUpperCase();
};
3.如何使用JSONP和跨域Ajax來遠程加載數(shù)據
通過遠程服務器獲取數(shù)據,然后寫入ORM
需要的相關知識:
在解決了向服務器端的數(shù)據抓取后氏身,就需要把這些抓取的數(shù)據添加進我們創(chuàng)建的ORM巍棱。
具體來說,在用Ajax或者JSONP抓取數(shù)據后观谦,我們在其回調函數(shù)里加入我們預定義的處理函數(shù)拉盾,這個處理函數(shù)主要就是把json格式的數(shù)據通過遍歷處理,再創(chuàng)建實例豁状,更新進records對象中捉偏。
具體的代碼為(會合并進前面的完整Model里):
Model.extend({
populate: function( values ) {
// 重置model和records
this.records = {};
for (var i= 0; i < values.length; i++ ) {
var record = this.created(values[i]);
record.newRecord = false;
this.records[record.id] = record;
}
}
});
這個方法的用法示例:
// 當我們用jQuery的JSONP接口獲得了數(shù)據后...
jQuery.getJSON("/assets", function(result) {
Asset.populate(result);
});
4.如何通過使用HTML5本地存儲和將本地存儲提交至RESTful服務器倒得,來實現(xiàn)模型數(shù)據持久化
通過本地緩存獲取數(shù)據,然后寫入ORM
這個就先要在相關位置設置“存儲緩存”夭禽,然后才在需要的方法調用緩存的數(shù)據霞掺。
因此先記錄一下Web Storage API:
這個API的作用是,使得網頁可以在瀏覽器端儲存數(shù)據讹躯。它分成兩類:sessionStorage和localStorage菩彬。
他們的不同之處:
- sessionStorage保存的數(shù)據用于瀏覽器的一次會話,當會話結束(通常是該窗口關閉)潮梯,數(shù)據被清空骗灶;
- localStorage保存的數(shù)據長期存在,下一次訪問該網站的時候秉馏,網頁可以直接讀取以前保存的數(shù)據耙旦。
他們的相同之處:
- 保存期限的長短不同,這兩個對象的屬性和方法完全一樣萝究。
他們很像cookie機制的強化版免都,能夠動用大得多的存儲空間。另外帆竹,與Cookie一樣绕娘,它們也受同域限制。
怎么判斷瀏覽器是否支持這兩個對象栽连?代碼如下:
function checkStorageSupport() {
// sessionStorage
if (window.sessionStorage) {
return true;
} else {
return false;
}
// localStorage
if (window.localStorage) {
return true;
} else {
return false;
}
}
怎么使用這兩個對象呢险领?示例代碼如下: