本來(lái)想寫(xiě)一篇關(guān)于前端數(shù)據(jù)驅(qū)動(dòng)的文章,百度搜了一下關(guān)鍵詞,發(fā)現(xiàn)有人已經(jīng)寫(xiě)了,文章內(nèi)容和我想寫(xiě)的差不多,順手就轉(zhuǎn)過(guò)來(lái)了。
前段時(shí)間一直在想前端MVC的意義。這個(gè)話題仁者見(jiàn)仁,但是MVC的使用方法給我提了一個(gè)管理數(shù)據(jù)的有意思的想法--數(shù)據(jù)管理和數(shù)據(jù)驅(qū)動(dòng)頁(yè)面。我們以前的思路一直是事件驅(qū)動(dòng)頁(yè)面,事件驅(qū)動(dòng)頁(yè)面合乎邏輯而且節(jié)約代碼。但是往往代碼組織結(jié)構(gòu)非常松散,這個(gè)松散并不是大家所期望的松耦合,而是一種亂七八糟的感覺(jué),后來(lái)在一次code中,我嘗試了一下用數(shù)據(jù)來(lái)驅(qū)動(dòng)頁(yè)面,覺(jué)得效果也不錯(cuò),邏輯也比較簡(jiǎn)單。下面簡(jiǎn)單分享一下我的思路。
我有一個(gè)電子商店,我需要一個(gè)購(gòu)物車功能。
我希望購(gòu)物車能在前端處理相關(guān)邏輯。而后臺(tái)只是保存用戶訂單。
下面是訂單保存的數(shù)據(jù)格式:
var orderList = {
0:{
'id':'12653',
'productName':'Kindle fire',
'price':790,
'amount':2,
'discount':0.75
},
1:{
'id':'2653',
'productName':'iPad',
'price':2790,
'amount':10,
'discount':0.70
},
2:{
'id':'653',
'productName':'Mac',
'price':7900,
'amount':1,
'discount':0.95
},
length:3,
subscriberId:'254',
totalPrice:0
}
首先我們使用一個(gè)數(shù)據(jù)管理器來(lái)維護(hù)用戶的訂單數(shù)據(jù),我們把它設(shè)計(jì)為一個(gè)單體模式。
var shppingCar = function() {
var orderList = {}
this.add = function(obj){
//添加一條購(gòu)買數(shù)據(jù)
}
this.remove = function(obj){
//刪除一條購(gòu)買數(shù)據(jù)
}
this.getTotilPrice = function(obj){
//獲取總價(jià)
}
this.update = function(obj){
//更新購(gòu)買數(shù)量
}
this.getOrder = function(){
return orderList;
}
}
這看起來(lái)數(shù)據(jù)結(jié)構(gòu)清晰,代碼組織似乎也不錯(cuò)。接下來(lái)涉及到我們DOM部分的操作了。
var order = new shppingCar();
orderList = order.getOrder();
var htmlManager = function(list){
//用orderList數(shù)據(jù)渲染頁(yè)面。
}
//第一次初始化數(shù)據(jù)
htmlManager();
//添加一條數(shù)據(jù)
orderList.add({});
orderList = order.getOrder();
htmlManager(orderList);
//刪除一條數(shù)據(jù)
orderList.add(id);
orderList = order.getOrder();
htmlManager(orderList);
//更新一條數(shù)據(jù)
orderList.update(id);
orderList = order.getOrder();
htmlManager(orderList);
每做一次數(shù)據(jù)操作,我們都要更新一次數(shù)據(jù)。我們沒(méi)有辦法改變這個(gè)事實(shí),因?yàn)槭聦?shí)就是數(shù)據(jù)改變,我們必然要修改頁(yè)面。
或許你有更好的辦法,那就是不用orderList渲染DOM,而是用一個(gè)回調(diào)函數(shù)來(lái)處理。那么代碼變?yōu)?/p>
this.add = function(obj,fn){
//添加一條購(gòu)買數(shù)據(jù)
if(fn){
fn();
}
}
你可以這樣使用
orderList.add({},function(){
//解析一次數(shù)據(jù),生成一條DOM結(jié)構(gòu),插入
//更改總價(jià)
});
這樣也意味著你分別要為刪除、添加、更新書(shū)寫(xiě)不同的回調(diào)函數(shù),看起來(lái)也并不是一個(gè)非常好的辦法。
回到前面的代碼,我們只需要做一個(gè)小小的改變,就可以用數(shù)據(jù)的改變來(lái)驅(qū)動(dòng)我們的頁(yè)面更新,這也是一個(gè)偽觀察者模式。其思想就是:數(shù)據(jù)更新了,我要重新渲染頁(yè)面。
var shppingCar = function() {
var orderList = {}
//我們給shppingCar添加了一個(gè)私有方法,當(dāng)數(shù)據(jù)改變時(shí)自動(dòng)為我們來(lái)更新頁(yè)面。
var render= function(){
}
this.add = function(obj){
//添加一條購(gòu)買數(shù)據(jù)
render();
}
this.remove = function(obj){
//刪除一條購(gòu)買數(shù)據(jù)
render();
}
this.getTotilPrice = function(obj){
//獲取總價(jià)
render();
}
this.update = function(obj){
//更新購(gòu)買數(shù)量
render();
}
this.getOrder = function(){
return orderList;
}
}
這樣我們使用的時(shí)候,就可以這樣了
var orderList = new shppingCar();
//添加一條數(shù)據(jù)
orderList.add({});
我們只是把外部渲染函數(shù)改成了購(gòu)物車對(duì)象的私有方法,然后在數(shù)據(jù)變動(dòng)時(shí)調(diào)用這個(gè)私有方法,就可以省去了在外部每次更新數(shù)據(jù)都要再次調(diào)用一個(gè)更新頁(yè)面的方法。雖然代碼量減少的不是很多,但是將所有的內(nèi)容封裝起來(lái)外面調(diào)用看起來(lái)更是省心省力。
至于刪除數(shù)據(jù)和更新數(shù)據(jù),我們甚至不需要在外部定義,直接在渲染頁(yè)面的時(shí)候把事件綁定到元素之后即可(下面的示例代碼我實(shí)現(xiàn)了一個(gè)刪除綁定,修改商品個(gè)數(shù)的功能大家有興趣可以自己實(shí)現(xiàn)。)
var shppingCar = function() {
//我們把數(shù)據(jù)設(shè)計(jì)為這樣的格式
var orderList = {
length:0,
subscriberId:'254',
totalPrice:0
}
//一些工具方法
//通過(guò)圖書(shū)id獲取當(dāng)前是第幾條數(shù)據(jù)
var getItemById = function(id){
for (var i = 0; i < orderList.length; i++) {
if(orderList[i].id == id) {
return i;
}
}
}
//重新整理數(shù)據(jù)成為標(biāo)準(zhǔn)格式
var refreshData = function(){
var o = {},n = [];
for (var key in orderList) {
var k = Number(key);
if(!isNaN(k)){
n.push(orderList[key]);
}else{
o[key] = orderList[key];
}
}
for (var i = 0; i < n.length; i++) {
o[i] = n[i];
}
orderList = o;
}
//計(jì)算總價(jià)
var updateTotilPrice = function() {
var totalprice = 0;
for (var i = 0; i < orderList.length; i++) {
totalprice +=orderList[i].price*orderList[i].discount*orderList[i].amount;
}
return totalprice;
};
//渲染頁(yè)面
var htmlManager = function () {
var items = "<ul>";
for (var i=0;i<orderList.length;i++) {
items += "<li><span>商品編號(hào):"+orderList[i].id
+"</span> <span>商品名字:"+orderList[i].productName
+"</span> <span>商品價(jià)格:"+orderList[i].price
+"</span> <span>訂購(gòu)數(shù)量:"+orderList[i].amount
+"</span> <span>商品折扣:"+orderList[i].discount+"</span>"
+"<a data-id="+orderList[i].id+" href='###'>刪除</a></li>"
}
items += "</ul>";
items+="商品總價(jià)格為"+ orderList.totalPrice +"元";
document.getElementsByTagName("body")[0].innerHTML = (items);
//綁定刪除事件
var delBtns = document.getElementsByTagName("a");
for (var j = 0; j < delBtns.length; j++) {
(function(k){
delBtns[k].onclick = function(){
remove(delBtns[k].getAttribute('data-id'));
return false;
}
})(j)
}
//綁定修改個(gè)數(shù)事件
};
//刪除一條數(shù)據(jù)
var remove = function(id){
var item = getItemById(id);
delete orderList[item];
orderList.length-=1;
refreshData();
orderList.totalPrice = updateTotilPrice();
htmlManager();
}
//更新商品個(gè)數(shù)
var update = function(id,amount){
//TODO:更新購(gòu)買數(shù)量
orderList.totalPrice = updateTotilPrice();
htmlManager();
}
//對(duì)外倆個(gè)接口方法,一個(gè)可以添加一條購(gòu)買數(shù)據(jù),一個(gè)為獲取當(dāng)前購(gòu)物車的所有數(shù)據(jù)
this.add = function(obj){
//TODO:驗(yàn)證傳入的數(shù)據(jù)是否合法
//TODO:此處判斷是否已經(jīng)存在該商品,如果存在,則調(diào)用updata方法。
orderList[orderList.length] = obj;
if(orderList[orderList.length]){
orderList.length +=1;
}
orderList.totalPrice = updateTotilPrice();
htmlManager();
}
this.getOrder = function() {
return orderList;
};
};
//使用方法:
var orderList = new shppingCar();
orderList.add({
'id':'6530',
'productName':'Mac mini-0',
'price':4900,
'amount':4,
'discount':0.90
})
orderList.add({
'id':'65301',
'productName':'Mac mini-1',
'price':5000,
'amount':4,
'discount':0.90
})
document.onclick = function() {
console.log(orderList.getOrder());
};
轉(zhuǎn)載自javascript 中的數(shù)據(jù)驅(qū)動(dòng)頁(yè)面模式
另外推薦一篇文章js面向數(shù)據(jù)編程(DOP)