Web.js MVC between client and server

本文为原创文章,出自http://cnodejs.org,转载请注明出处和作者
作者:WillWen
原文:http://cnodejs.org/blog/?p=1730

Web.js 是一个 为简化 HTTP 开发而设计的 Web Framework,它致力于以最简单的语法进行开发高性能的应用。

Web.js between client and server 是指 web.js 这个文件可以同时在客户端和服务端使用吗?

不是的。。这个先不管。。先来看看其他“无关”的。。

以下是 Wikipedia 对 MVC 的解释:

MVC模式(Model-View-Controller)是软件工程中的一种软件架构模式,把软件系统分为三个基本部分:模型(Model)、视图(View)和控制器(Controller)。


先来说说前端 MVC

现在国内来说,相对优秀的前端 MVC 架构是来自豆瓣说的 [(Backbone.js + Underscore.js) + Mustache.js + jQuery]

M – Model

Backbone.js 和 Underscore.js 是暂时来说国内公认的最好的一组 Model 框架

  1. 高性能
  2. 强自定义度
  3. 高灵活性
  4. …………
Backbone.js
//Model of Backbone
var Person = Backbone.Model.extent({
    sayHello: function () {
        alert('Hey, I`m ' + this.get('name') + '.');
        return this;
    },
    setName: function (name) {
        this.set({'name': name});
        alert('My name is ' + this.get('name') + '.');
        return this;
    }
});

var Will = new Person;

Will.setName('Will Wen Gunn')    // --> My name is Will Wen Gunn.
    .sayHello();                // --> Hey, I`m Will Wen Gunn.

其中 Backbone.Model 可以理解为经过封装的 Class 类型,然后 Person 是一个经过 extend 拓展的自定义 Class 类型,

Will 则是继承了 Person 的一个 Model 对象。

//Model of Underscore
$.getJSON(location.origin + '/persons')
    .done(function (data) {
        sessionStorage.persons = data;
        var persons = JSON.parse(sessionStorage.persons);
        // --> [{name: 'Will Wen Gunn'}, {name: 'Foo'}, {name: 'Bar'}]
        $('body').append(
            JSON.stringify(
                _.map(persons, function (person) {
                    if (person.name == 'Will Wen Gunn' || person.name !== 'Bar') {
                        return true;
                    }
                })
            )
        );
    });
// --> [{"name": "Will Wen Gunn"}, {"name": "Foo"}]

与其说 Underscore.js 是 Model 框架,还不如说是 Model 操作库,Underscore.js 并没有封装 Class ,而是使用 JavaScript 原有的 Array, Object, Function 类型进行操作 (其中人们比较常用 Object 和 Function 来模拟 Class)

这种 Model 会比 利用 Object 或者 Function 来定义 Class 要实际,因为这样可以很好地对对象和对象集 (Collection) 进行管理和操作。


V – View

View 更直接点地说就是我们平时所构建的 HTML ,而 MVC 其实源自于 DHTML ,W3C 的解释是动态页面(Dynamic HTML),而我更认为是 Data to HTML。

而这个正是Mustache.js诞生的原因

{{Mustache}}

var person,
    tmpl-persons,
    proxy = new Eventproxy(),
    persons = function (data, tmpl) {
        $('body').append(
            Mustache.to_html(tmpl, data)
        );
    };
proxy.assign('data', 'tmpl', persons);
$.getJSON(location.origin + '/persons')
    .done(function (data) {
        persons = JSON.parse(data);
        /*
         * [ {Name: "Will Wen Gunn", Age: 15, Sex: "Man"},
         *   {Name: "Foo", Age: 15, Sex: "Man"},
         *   {Name: "Bar", Age: 15, Sex: "Man"} ]
         */
        proxy.trigger('data', data);
    });
$.get(location.origin + '/tmpls/persons.html')
    .done(function (data) {
        tmpl-persons = data;
        /*
         * {{#peoples}}
         *     {{Name}}
         *     {{Age}}
         *     {{Sex}}
         * {{/peoples}}
         */
        proxy.trigger('tmpl', tmpl);
    });
/*
 * Will Wen Gunn
 * 15
 * Man
 * Foo
 * 15
 * Man
 * Bar
 * 15
 * Woman
 */

这里使用到了朴灵小田同学的 EventProxy.js,相当好玩的一个小工具,但是发挥出来的作用很强阿。

它能让几个异步请求并行处理,最后集中处理。

https://github.com/JacksonTian/eventproxy

另外也还有一个来自国外的,类似的东西,叫Step,它也让异步函数分开,但是是串列的队列式,所以会产生阻塞,小问并不建议使用。


C – Controller

这个恐怕是争议最大的一块了,有人推崇jQuery,有人推崇YUI,有人推崇MooTools……

其实这个并没有什么太大关系,只要是有这样的能力的,哪种 JavaScript Library 都没所谓的。

我这里用jQuery把上面的这些M,V整合起来:

var Person = Backbone.Model.extent({
        sayHello: function () {
            alert('Hey, I`m ' + this.get('name') + '.');
            return this;
        },
        setName: function (name) {
            this.set({'name': name});
            alert('My name is ' + this.get('name') + '.');
            return this;
        }
    }),
    Persons  = Backbone.Collection.extend({
        model: Person,
        sayHello: function () {
            this.each(function (Person) {
                $('body').append('Hey, I`m ' + Person.get('name') + '.');
            });
        },
        sayName: function () {
            this.each(function (Person) {
                $('body').append('My name is ' + Person.get('name') + '.');
            });
        }
    });
var CNodejs = new Persons;
var persons,
    tmpl-persons;
$.get(location.origin + '/persons')
    .done(function (data) {
        persons = JSON.parse(data);
    })
    .get(location.origin + '/tmpls/persons.html')
    .done(function () {
        tmpl-persons = JSON.parse(data);
    });
CNodejs.add(persons.peoples);
$('body').append(
    Mustache.to_html(
        tmpl-persons,
        { peoples: CNodejs.toJSON() }
    )
);

Node.js MVC

M – Model, V – View

其实 Backbone.js, Underscore.js 和 Mustache.js 在 Node.js 上的用法是和前端一模一样的,所以我就不多介绍了。

C – Controller

来看看 Web.js 的 Router :

var urlRouter = {
        '^(\d{4})\/(\d{2})\/(\d{2})\/(.*)\.jpg': '$1-$2-$3-$4.jpg',
        'google': 'http://www.google.com',
        'iwillwen': 'http://www.iwillwen.com'
    },
    getRouter = {
        '^getsomthing': function(req, res, qs) {
            res.sendJSON(qs);
        },
        '^car': function(req, res, qs) {
            switch (qs.action) {
                case 'foo':
                    res.send('Your action is foo');
                    break;
                case 'bar':
                    res.send('Your action is bar');
                    break;
            }
        }
    },
    postRouter = {
        '^postsomthing': function(req, res, data) {
            res.sendJSON(qs);
        },
        '^car': function(req, res, data) {
            switch (data.action) {
                case 'foo':
                    res.send('Your action is foo');
                    break;
                case 'bar':
                    res.send('Your action is bar');
                    break;
            }
        }
    };

web.run(urlRouter, 80)
    .get(getRouter)
    .post(postRouter);

console.log('The app is running on http://localhost');

这个和Express有点区别。

如果结合数据库的 Node.js MVC,Web.js该怎么写呢

var web =require('webjs'),
    mongo = require('mongoskin'),
    Backbone = require('backbone'),
    Underscore = require('underscore'),
    eventproxy = require('EventProxy.js'),
    db = mongo.db('localhost:27017/blog'),
    posts = db.collection('posts'),
    metas = db.collection('metas'),

var urlRouter = {
        '^page/(\d)': 'page.html',
        '^(.*)': 'post.html'
    },
    getRouter = {
        'init': function (req, res, qs) {
            var proxy = new eventproxy.EventProxy(),
            init = function (title, description, posts) {
                    var obj = {
                        title: title,
                        description: description,
                        posts: posts
                    };
                    res.sendJSON(obj);
                };
            proxy.assign('title', 'description', 'posts', init);
            metas.findOne({type: 'title'}, function (err, title) {
                proxy.trigger('title', title);
            });
            metas.findOne({type: 'description'}, function (err, description) {
                proxy.trigger('description', description);
            });
            posts.findTop10(function (err, posts) {
                proxy.trigger('posts', posts);
            });
        },
        'getPost': function (req, res, qs) {
            posts.findOne(qs, function (err, post) {
                res.sendJSON(post);
            });
        }
    },
    postRouter = {
        'setMeta': function (req, res, data) {
            metas.update(
                {type: data.type},
                data,
                {upsert: true},
                function (err) {
                    if (err) return res.send('Set failed.
' + err);
                    res.send('Set successed.');
                }
            );
        },
        'post': function (req, res, data) {
            posts.update(
                {title: data.title},
                data,
                {upsert: true},
                function (err) {
                    if (err) return res.send('Post failed.
' + err);
                    res.send('Post successed');
                }
            );
        }
    };
web.run(urlRouter, 80)
    .get(getRouter)
    .post(postRouter);

最后来讲讲

Web.js between client and server

来看看 Web.js for client (仍在编写中) 的用法

(function ($) {
    //loaded /js/web-client.js
    var getRouter = {
        'getsomething' : function () {
            var proxy = new Eventproxy(),
                post = function (data, tmpl) {
                    $('body').append(
                        Mustache.to_html(tmpl, data)
                    );
                };
            proxy.assign('data', 'tmpl', init);
            web.getData('post', {}, function (data) {
                proxy.trigger('data', data);
            });
            web.getTmpl('post', function (tmpl) {
                proxy.trigger('tmpl', tmpl);
            });
        }
    };
    web.conn(location.origin)
        .get(getRouter);
})(jQuery);

这时候大家可能会问了,Web.js 的 MVC 究竟异样在哪里呢?

前端 MVC 的 M 其实和后端 MVC 的 M 本来是不可能一起使用的,因为如果由后端进行模板渲染,前端就没有渲染的必要,那么前端 MVC就不成立了。同理,如果让前端进行模板渲染,后端也就不存在 MVC 的概念。

如果要前后端的 MVC 同时存在要怎么做呢,其实很简单,就是让客户端进行性能·评估。

比如说 IE6、7 这样的低性能浏览器,Web.js for client 会让服务器先进行 Data to HTML 渲染,然后传输再到客户端,如果是Chrome,FireFox,Safari 和 Opera 等高性能浏览器,则选择在客户端进行渲染,减轻传输荷载。

另外 Web.js 默认会开启缓存应用加速机制 (DOM Storage,Cookies,Buffer,Object……),让一部分数据先存入缓存,让短时间内再次发出的请求从缓存中获取。减少 LAN 资源和数据库资源的损耗。

JavaScript 是一门建立在静态页面上的动态脚本语言,Ajax的普及和发展,使它完全可以完成一些静态页面做不到的事情,比如像PHP自身的文件响应机制 (相比这也是 PHP 吸引人的一个终于优点)。

上面的这一段代码就演示了一个静态页面通过 Web.js 进行 URL action router 识别,并向服务器请求数据和模板,然后在 DOM 中插入渲染得到的 HTML。

两个 Web.js 之间是可以无缝对接的,开发者无须设置太多。

当然 Web.js for client 也是支持 Express 等其他 Server-side 开发框架的。


好,最后来放一个完整的 Web.js MVC Router 代码

//server.js
var web =require('webjs'),
    mongo = require('mongoskin'),
    Backbone = require('backbone'),
    Underscore = require('underscore'),
    eventproxy = require('EventProxy.js'),
    db = mongo.db('localhost:27017/blog'),
    posts = db.collection('posts'),
    metas = db.collection('metas'),

var urlRouter = {
        '^(.*)': 'page.html'
    },
    getRouter = {
        'init': function (req, res, qs) {
            var proxy = new eventproxy.EventProxy(),
                init = function (title, description, posts) {
                    var obj = {
                        title: title,
                        description: description,
                        posts: posts
                    };
                    if (qs.render) {
                        res.send(
                            web.render('init', obj)
                        );
                    } else {
                        res.sendJSON(obj);
                    }
                };
            proxy.assign('title', 'description', 'posts', init);
            metas.findOne({type: 'title'}, function (err, title) {
                proxy.trigger('title', title);
            });
            metas.findOne({type: 'description'}, function (err, description) {
                proxy.trigger('description', description);
            });
            posts.findTop10(function (err, posts) {
                proxy.trigger('posts', posts);
            });
        },
        'getpost': function (req, res, qs) {
            posts.findOne(qs ,function (err, post) {
                if (qs.render) {
                    res.send(
                        web.render('post', post)
                    );
                } else {
                    res.sendJSON(post);
                }
            });
        }
    };
web.run(urlRouter, 80)
    .get(getRouter)
    .set('tmplDir', 'tmpls');

//page.html - client.js
(function ($) {
    var getRouter = {
        '/' : function () {    //init
            var proxy = new Eventproxy(),
                init = function (data, tmpl) {
                    $('body').append(
                        Mustache.to_html(tmpl, data)
                    );
                };
            proxy.assign('data', 'tmpl', init);
            web.getData('init', {}, function (data) {
                proxy.trigger('data', data);
            });
            web.getTmpl('init', function (tmpl) {
                proxy.trigger('tmpl', tmpl);
            });
        }
        '^(.*)' : function (action) {    // action --> (.*)
            var proxy = new Eventproxy(),
                post = function (data, tmpl) {
                    $('body').append(
                        Mustache.to_html(tmpl, data)
                    );
                };
            proxy.assign('data', 'tmpl', init);
            web.getData('post', {title: action}, function (data) {
                proxy.trigger('data', data);
            });
            web.getTmpl('post', function (tmpl) {
                proxy.trigger('tmpl', tmpl);
            });
        }
    };
    web.conn(location.origin)
        .get(getRouter)
})(jQuery);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值