javascript 中的数据驱动页面模式

本来想写一篇关于前端数据驱动的文章,百度搜了一下关键词,发现有人已经写了,文章内容和我想写的差不多,顺手就转过来了。
前段时间一直在想前端MVC的意义。这个话题仁者见仁,但是MVC的使用方法给我提了一个管理数据的有意思的想法--数据管理和数据驱动页面。我们以前的思路一直是事件驱动页面,事件驱动页面合乎逻辑而且节约代码。但是往往代码组织结构非常松散,这个松散并不是大家所期望的松耦合,而是一种乱七八糟的感觉,后来在一次code中,我尝试了一下用数据来驱动页面,觉得效果也不错,逻辑也比较简单。下面简单分享一下我的思路。

我有一个电子商店,我需要一个购物车功能。
我希望购物车能在前端处理相关逻辑。而后台只是保存用户订单。

下面是订单保存的数据格式:

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
}

首先我们使用一个数据管理器来维护用户的订单数据,我们把它设计为一个单体模式。

var shppingCar = function() {
    var orderList = {}
    this.add = function(obj){
        //添加一条购买数据
    }
    this.remove = function(obj){
        //删除一条购买数据
    }
    this.getTotilPrice = function(obj){
        //获取总价
    }
    this.update = function(obj){
        //更新购买数量
    }
    this.getOrder = function(){
        return orderList;
    }
}

这看起来数据结构清晰,代码组织似乎也不错。接下来涉及到我们DOM部分的操作了。

var order = new shppingCar();
orderList = order.getOrder();
var htmlManager = function(list){
    //用orderList数据渲染页面。
}
//第一次初始化数据
htmlManager();
//添加一条数据
orderList.add({});
orderList = order.getOrder();
htmlManager(orderList);
//删除一条数据
orderList.add(id);
orderList = order.getOrder();
htmlManager(orderList);
//更新一条数据
orderList.update(id);
orderList = order.getOrder();
htmlManager(orderList);

每做一次数据操作,我们都要更新一次数据。我们没有办法改变这个事实,因为事实就是数据改变,我们必然要修改页面。
或许你有更好的办法,那就是不用orderList渲染DOM,而是用一个回调函数来处理。那么代码变为

this.add = function(obj,fn){
    //添加一条购买数据
    if(fn){
        fn();
    }
}
你可以这样使用
orderList.add({},function(){
    //解析一次数据,生成一条DOM结构,插入
    //更改总价
});

这样也意味着你分别要为删除、添加、更新书写不同的回调函数,看起来也并不是一个非常好的办法。

回到前面的代码,我们只需要做一个小小的改变,就可以用数据的改变来驱动我们的页面更新,这也是一个伪观察者模式。其思想就是:数据更新了,我要重新渲染页面。

var shppingCar = function() {
    var orderList = {}
 
    //我们给shppingCar添加了一个私有方法,当数据改变时自动为我们来更新页面。
    var render= function(){
 
    }
    this.add = function(obj){
        //添加一条购买数据
        render();
    }
    this.remove = function(obj){
        //删除一条购买数据
        render();
    }
    this.getTotilPrice = function(obj){
        //获取总价
        render();
    }
    this.update = function(obj){
        //更新购买数量
        render();
    }
    this.getOrder = function(){
        return orderList;
    }
}

这样我们使用的时候,就可以这样了

var orderList = new shppingCar();
//添加一条数据
orderList.add({});

我们只是把外部渲染函数改成了购物车对象的私有方法,然后在数据变动时调用这个私有方法,就可以省去了在外部每次更新数据都要再次调用一个更新页面的方法。虽然代码量减少的不是很多,但是将所有的内容封装起来外面调用看起来更是省心省力。

至于删除数据和更新数据,我们甚至不需要在外部定义,直接在渲染页面的时候把事件绑定到元素之后即可(下面的示例代码我实现了一个删除绑定,修改商品个数的功能大家有兴趣可以自己实现。)

var shppingCar = function() {
    //我们把数据设计为这样的格式
    var orderList = {
        length:0,
        subscriberId:'254',
        totalPrice:0
    }
    //一些工具方法
    //通过图书id获取当前是第几条数据
    var getItemById = function(id){
        for (var i = 0; i < orderList.length; i++) {
            if(orderList[i].id == id) {
                return i;
            }
        }
    }
    //重新整理数据成为标准格式
    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;
    }
    //计算总价
    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;
    };
    //渲染页面
    var htmlManager = function () {
        var items = "<ul>";
        for (var i=0;i<orderList.length;i++) {
            items += "<li><span>商品编号:"+orderList[i].id
                    +"</span> <span>商品名字:"+orderList[i].productName
                    +"</span> <span>商品价格:"+orderList[i].price
                    +"</span> <span>订购数量:"+orderList[i].amount
                    +"</span> <span>商品折扣:"+orderList[i].discount+"</span>"
                    +"<a data-id="+orderList[i].id+" href='###'>删除</a></li>"
        }
        items += "</ul>";
        items+="商品总价格为"+ 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)
        }
        //绑定修改个数事件
    };
    //删除一条数据
    var remove = function(id){
        var item = getItemById(id);
        delete orderList[item];
        orderList.length-=1;
        refreshData();
        orderList.totalPrice = updateTotilPrice();
        htmlManager();
    }
    //更新商品个数
    var update = function(id,amount){
        //TODO:更新购买数量
        orderList.totalPrice = updateTotilPrice();
        htmlManager();
    }
    //对外俩个接口方法,一个可以添加一条购买数据,一个为获取当前购物车的所有数据
    this.add = function(obj){
        //TODO:验证传入的数据是否合法
        //TODO:此处判断是否已经存在该商品,如果存在,则调用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());
};

转载自javascript 中的数据驱动页面模式

另外推荐一篇文章js面向数据编程(DOP)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容