浅谈HTML5单页面架构(二)——backbone + requirejs + zepto + underscore

本文转载自:http://www.cnblogs.com/kenkofox/p/4648472.html

上一篇《浅谈HTML5单页面架构(一)——requirejs + angular + angular-route》探讨了angular+requirejs的一个简单架构,这一篇继续来看看backbone如何跟requirejs结合。

相同地,项目架构好与坏不是说用了多少牛逼的框架,而是怎么合理利用框架,让项目开发更流畅,代码更容易管理。那么带着这个目的,我们来继续探讨backbone。

首先,来看看整个项目结构。

跟上一篇angular类似,libs里多了underscore和zepto。三个根目录文件:

  • index.html:唯一的html
  • main.js:requirejs的配置,程序的入口
  • router.js:整个app或网站的单页面路由配置

第一步,还是建立单页面唯一的HTML

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>Backbone & Requirejs</title>
</head>
<body>
<div id="container"></div>
<script data-baseurl="./" data-main="main.js" src="libs/require.js" id="main"></script>
</body>
</html>

backbone没有在dom属性上做文章,我们还是按原生的或者说熟悉的方法写东西。这里定义了一个container div作为backbone的视图。

然后引入requirejs,data-main表示主程序入口。

第二步,配置main.js

(function (win) {
    //配置baseUrl
    var baseUrl = document.getElementById(‘main‘).getAttribute(‘data-baseurl‘);

    /*
     * 文件依赖
     */
    var config = {
        baseUrl: baseUrl,           //依赖相对路径
        paths: {                    //如果某个前缀的依赖不是按照baseUrl拼接这么简单,就需要在这里指出
            zepto: ‘libs/zepto.min‘,
            jquery: ‘libs/zepto.min‘,
            underscore: ‘libs/underscore‘,
            backbone: ‘libs/backbone‘,
            text: ‘libs/text‘             //用于requirejs导入html类型的依赖
        },
        shim: {                     //引入没有使用requirejs模块写法的类库。backbone依赖underscore
            ‘underscore‘: {
                exports: ‘_‘
            },
            ‘jquery‘: {
                exports: ‘$‘
            },
            ‘zepto‘: {
                exports: ‘$‘
            },
            ‘backbone‘: {
                deps: [‘underscore‘, ‘jquery‘],
                exports: ‘Backbone‘
            }
        }
    };

    require.config(config);

    //Backbone会把自己加到全局变量中
    require([‘backbone‘, ‘underscore‘, ‘router‘], function(){
        Backbone.history.start();   //开始监控url变化
    });

})(window);

关于requirejs的语法,还是不多说,大家自己去官网看吧。这个是基础。

使用backbone,不得不强调requirejs的shim配置。backbone这个土匪,要的东西多了,要给他“鞋”(underscore),还要给他美金$(jquery)。

由于终端使用jquery就太庞大了,所以这里做了个小把戏,用zepto充当jquery,骗了土匪一把。用几张越南盾,戏称是美金,没想到土老冒也信了。

有个地方需要注意的是,

无论在哪里用requirejs引入backbone后,就会多了Backbone和$这两个全局变量,所以后续再使用backbone就不需要拘束于requirejs的AMD写法了。适当放松透透气也是好的。

配置好依赖关系后,就可以引入router,并调用关键的

Backbone.history.start

开始路由监控。

第三步,配置router,路由表

define([‘backbone‘], function () {

    var Router = Backbone.Router.extend({

        routes: {
            ‘module1‘: ‘module1‘,
            ‘module2(/:name)‘: ‘module2‘,
            ‘*actions‘: ‘defaultAction‘
        },

        //路由初始化可以做一些事
        initialize: function () {
        },

        module1: function() {
            var url = ‘module1/controller1.js‘;
            //这里不能用模块依赖的写法,而改为url的写法,是为了grunt requirejs打包的时候断开依赖链,分开多个文件
            require([url], function (controller) {
                controller();
            });
        },

        //name跟路由配置里边的:name一致
        module2: function(name) {
            var url = ‘module2/controller2.js‘;
            require([url], function (controller) {
                controller(name);
            });
        },

        defaultAction: function () {
            console.log(‘404‘);
            location.hash = ‘module2‘;
        }

    });

    var router = new Router();
    router.on(‘route‘, function (route, params) {
        console.log(‘hash change‘, arguments);  //这里route是路由对应的方法名
    });

    return router;    //这里必须的,让路由表执行
});

Backbone.Router.extend这个语法,相信就不必多说了,说多了也说不清楚,大家去官网才是王道:http://backbonejs.org

backbone的路由写法跟angular类似,但对于可选参数的写法是不一样的。angular使用:param?的方式,而backbone使用(:param),哪个方式好,见仁见智吧。

这里定义了一个默认路由,和两个业务路由。

原理很简单,就是遇到module1的哈希(hash)就执行后边这个字符串对应的函数

估计大家早就知道这个玩意。而上述代码中,关键不同点是,这里利用了requirejs做了模块化,路由跳转后做的所有逻辑都在另外的js中定义。

关键的关键,这里使用了url,而且是独立变量的方式配置模块的js,而不是

            require([‘module1/controller1‘], function (controller) {
                controller();
            });

目的是grunt做requirejs打包时,能切断两侧的js,不要合并在一个大js中。

再另外,大家可以善用一下router.on(‘route‘, function)这个接口,及时做一下事件解绑和一些清理工作。

第四步,写一个简单模块

controller1.js

define([‘module1/view1‘], function (View) {

    var controller = function () {
        var view = new View();
        view.render(‘kenko‘);
    };
    return controller;
});

view1.js

define([‘text!module1/tpl.html‘], function (tpl) {

    var View1 = Backbone.View.extend({
        el: ‘#container‘,

        initialize: function () {
        },

        render: function (name) {
            this.$el.html(_.template(tpl, {name: name}));
        }
    });

    return View1;
});

tpl.html

<div>
    Here is module 1. My name: <%=name %><br>
    <a href="#module2">turn to module 2</a>
</div>

模版的写法跟angular不一样,采用的是更普遍的方式,jquery、underscore都是这个模式。简单说就是<%%>包括js代码,用=等号可以直接输出变量值。

这里做了最简单的MVC,M只是一个值name,C就是controller了,V就是view1。

View1的写法需要遵循Backbone的语法,不然这里用Backbone就没意义了。el指向对应的视图dom元素,用的是css选择器,在View中可以使用this.$el获取到这个jquery风格变量。render是自定义的函数。

到这里,运行程序,就能看到module1的效果了。

第五步,再写第二个模块,严格MVC的

 model2.js

define([], function () {
    var Model2 = Backbone.Model.extend({

        //模型默认的数据
        defaults: function () {
            return {
                name: "noname"
            };
        },

        // 定义一些方法
        fetch: function () {
            var o = this;
            //可以做一些http请求
            setTimeout(function(){
                o.set({name:‘vivi‘});
                o.trigger(‘nameEvent‘);     //向view触发事件
            }, 1000);
        }

    });

    return Model2;
});

Model的语法也是遵循Backbone要求了,defaults是默认属性值。读写这些属性,需要通过model.get/set接口,否则就是用toJSON返回整个对象,再不然就解剖式的使用model.attributes.xxx。

fetch是自定义方法,模拟http请求,这是很常规的做法了,不过这个例子没使用backbone的rest化接口。

数据返回后,使用backbone内建的trigger触发事件,通知监听者,也就是view层了。backbone跟angular最大区别就是,backbone不关注view层的组件化,更关注的是model和事件机制,而angular则不重点提事件机制,采用双向绑定把数据更新的破事隐藏起来。各有各的好处,见仁见智吧。

view2.js

define([‘text!module2/tpl.html‘], function (tpl) {

    var View2 = Backbone.View.extend({
        el: ‘#container‘,

        events: {
            ‘click button‘: ‘clickSpan‘     //使用代理监听交互,好处是界面即使重新rander了,事件还能触发,不需要重新绑定。如果使用zepto手工逐个元素绑定,当元素刷新后,事件绑定就无效了
        },

        initialize: function () {
            this.model.on(‘nameEvent‘, this.render, this);      //监听事件
        },

        render: function () {
            this.$el.html(_.template(tpl, {name: this.model.get(‘name‘)}));     //类似java的DAO思想,一切通过get set操作
        },

        clickSpan: function (e) {
            alert(‘you clicked the button‘);
        }
    });

    return View2;
});

接着,我们看看backbone一个典型视图怎么玩。先看initialize方法,这个是new View2()时先执行的初始化逻辑。

我们在这里监听nameEvent这个消息,也就是model2抛出的事件。收到这个通知,就更新界面。逻辑很简单。

这里有一个比较好用的events,交互事件代理机制。

我们不需要单独的写zepto on对dom分别绑定事件,只需要在这里配置一个events映射表即可。

click button等同于zepto的

$(‘button‘).on(‘click‘, function)

这里绑定的就是clickSpan事件。

这个事件代理机制,好处是,在路由切换的时候,可以轻松移除事件监听。

view.undelegateEvents()

tpl.html

<div>
    Here is module 2. My name: <%=name %><br>
    <button>click me!</button>
    <a href="#module1">turn to module 1</a>
</div>

controller2.js

define([‘module2/model2‘, ‘module2/view2‘], function (Model, View) {

    var controller = function (name) {
        var model = new Model();
        name && model.set({
            name:name               //设置默认的属性值
        });
        var view = new View({model:model});
        view.render();      //利用Model定义的默认属性初始化界面
        model.fetch();          //拉取cgi等等,获取数据,再触发事件,界面收到消息做相应的动作
    };

    return controller;
});

controller负责的做的事就是揉合数据,放到view中。先让view用默认数据渲染,再让model去拉取最新数据,最后通过事件机制更新界面。

当然,这个controller并不是backbone规范,大家可以尽情发挥。

最后回到路由表中,当hash变成module2时,就执行:

        module2: function(name) {
            var url = ‘module2/controller2.js‘;
            require([url], function (controller) {
                controller(name);
            });
        },

至此,简单的requirejs+backbone框架已经完成了。除了router的耦合度很高外,每个模块逻辑代码都已经独立,app可以轻松实现按需加载。

那么追求机制的骚年,要停下来吗?按这个方案,在实际开发中,多人经常会在router这个节骨眼上打架,这里的配置化还不够完美。

第六步,优化router,彻底配置化

现有方案的问题是,router中除了写路由配置外,还需要添加相应的function,这样既冗余又容易冲突,那么能否监听route事件,做一个统一的路由处理器?一个处理函数,处理全部路由响应。

define([‘backbone‘], function () {

    var routesMap = {
        ‘module1‘: ‘module1/controller1.js‘,            //原来应该是一个方法名,这里取巧改为模块路径
        ‘module2(/:name)‘: ‘module2/controller2.js‘,
        ‘*actions‘: ‘defaultAction‘
    };

    var Router = Backbone.Router.extend({

        routes: routesMap,

        defaultAction: function () {
            console.log(‘404‘);
            location.hash = ‘module2‘;
        }

    });

    var router = new Router();
    //彻底用on route接管路由的逻辑,这里route是路由对应的value
    router.on(‘route‘, function (route, params) {
        require([route], function (controller) {
            if(router.currentController && router.currentController !== controller){
                router.currentController.onRouteChange && router.currentController.onRouteChange();
            }
            router.currentController = controller;
            controller.apply(null, params);     //每个模块约定都返回controller
        });
    });

    return router;
});

上述代码,把路由表抽离,目的是可以放到index.html中,可以在服务器做直出,保持0缓存,轻松实现对外网版本的控制。

另外Router中,没有了每个路由对应的函数,而路由表中的key/value改为真正意义的一个字符串——模块路径。

感谢backbone的健壮,我开始还以为这样肯定会报错,结果backbone没找到对应函数就停止执行了,不错,赞一个。

没有了一个个的相应函数,取而代之的是route事件处理器。

处理器中,利用了配置表的value,拉取对应的模块,并调用相应的controller。有了这个小把戏,大家可以自由发挥了,配置成各种字符串,多个controller集合在一个requirejs模块中等等。。。

另外,这里约定controller中有onRouteChange的接口,用于接收路由切换的通知,好做一些销毁工作。

来看看新的controller代码:

define([‘module2/model2‘, ‘module2/view2‘], function (Model, View) {

    var controller = function (name) {
        var model = new Model();
        name && model.set({
            name:name               //设置默认的属性值
        });
        var view = new View({model:model});
        view.render();      //利用Model定义的默认属性初始化界面
        model.fetch();          //拉取cgi等等,获取数据,再触发事件,界面收到消息做相应的动作

        controller.onRouteChange = function () {
            console.log(‘change‘);  //可以做一些销毁工作,例如view.undelegateEvents()
            view.undelegateEvents();
        };
    };

    return controller;
});

至此,大功告成,多人开发中,需要修改路由,只需要修改一个配置,不在这里写任何逻辑,利用svn合并功能,轻松完成协同开发。

本文代码:https://github.com/kenkozheng/HTML5_research/tree/master/BackboneRequireJS

时间: 2024-10-14 04:28:58

浅谈HTML5单页面架构(二)——backbone + requirejs + zepto + underscore的相关文章

AngularJS进阶(二十五)requirejs + angular + angular-route 浅谈HTML5单页面架构

requirejs + angular + angular-route 浅谈HTML5单页面架构 众所周知,现在移动Webapp越来越多,例如天猫.京东.国美这些都是很好的例子.而在Webapp中,又要数单页面架构体验最好,更像原生app.简单来说,单页面App不需要频繁切换网页,可以局部刷新,整个加载流畅度会好很多. 废话就不多说了,直接到正题吧,浅谈一下我自己理解的几种单页面架构: 1.requirejs+angular+angular-route(+zepto) 最后这个zepto可有可无

浅谈HTML5单页面架构(一)——requirejs + angular + angular-route

本文转载自:http://www.cnblogs.com/kenkofox/p/4643760.html 心血来潮,打算结合实际开发的经验,浅谈一下HTML5单页面App或网页的架构. 众所周知,现在移动Webapp越来越多,例如天猫.京东.国美这些都是很好的例子.而在Webapp中,又要数单页面架构体验最好,更像原生app.简单来说,单页面App不需要频繁切换网页,可以局部刷新,整个加载流畅度会好很多. 废话就不多说了,直接到正题吧,浅谈一下我自己理解的几种单页面架构: 1.requirejs

浅谈HTML5单页面架构(三)—— 回归本真:自定义路由 + requirejs + zepto + underscore

本文转载自:http://www.cnblogs.com/kenkofox/p/4650310.html 不过,这一篇,我想进一步探讨一下这两个框架的优缺点,另外,再进一步,抛开这两个框架,回到本真,自己搞个简单的路由一样可以实现单页面. 这个对于刚做前端开发的新同学来说就最好不过了,如果一来到岗位就一大堆angular.backbone.requirejs,看资料都看一两周.其实大家最熟悉的东西还是那个美元$,用美元能解决的问题,就不要麻烦到angular.backbone大爷了. 事先说明,

backbone + requirejs + zepto + underscore

转自:  http://www.cnblogs.com/kenkofox/p/4648472.html 这一篇继续来看看backbone如何跟requirejs结合. 相同地,项目架构好与坏不是说用了多少牛逼的框架,而是怎么合理利用框架,让项目开发更流畅,代码更容易管理.那么带着这个目的,我们来继续探讨backbone. 首先,来看看整个项目结构. 跟上一篇angular类似,libs里多了underscore和zepto.三个根目录文件: index.html:唯一的html main.js:

浅谈服务器单I/O线程+工作者线程池模型架构及实现要点

转自 http://www.cnblogs.com/ccdev/p/3542669.html 单I/O线程+多工作者线程的模型,这也是最常用的一种服务器并发模型.我所在的项目中的server代码中,这种模型随处可见.它还有个名字,叫“半同步/半异步“模型,同时,这种模型也是生产者/消费者(尤其是多消费者)模型的一种表现. 这种架构主要是基于I/O多路复用的思想(主要是epoll,select/poll已过时),通过单线程I/O多路复用,可以达到高效并发,同时避免了多线程I/O来回切换的各种开销,

浅谈 HTML5 的 DOM Storage 机制 (转)

在开发 Web 应用时,开发者有时需要在本地存储数据.当前浏览器支持 cookie 存储,但其大小有 4KB 的限制.这对于一些 Ajax 应用来说是不够的.更多的存储空间需要浏览器本身或是插件的支持,如 Google Gears 和 Flash.不过开发人员需要通过检测当前浏览器所支持的插件类型来使用对应的接口. HTML5 中新引入了 DOM Storage 机制,通过使用键值对在客户端保存数据,并且提供了更大容量的存储空间.本文将详细论述 HTML5 对本地存储的支持,并对存储事件绑定和数

【转】【ASP.NET MVC系列】浅谈ASP.NET 页面之间传值的几种方式

ASP.NET MVC系列文章 原文地址:https://www.cnblogs.com/wangjiming/p/6275854.html [01]浅谈Google Chrome浏览器(理论篇) [02]浅谈Google Chrome浏览器(操作篇)(上) [03]浅谈Google Chrome浏览器(操作篇)(下) [04]浅谈ASP.NET框架 [05]浅谈ASP.NET MVC运行过程 [06]浅谈ASP.NET MVC 控制器 [07]浅谈ASP.NET MVC 路由 [08]浅谈AS

浅谈图片服务器的架构演进

浅谈图片服务器的架构演进 现在几乎任何一个网站.Web App以及移动APP等应用都需要有图片展示的功能,对于图片功能从下至上都是很重要的.必须要具有前瞻性的规划好图片服务器,图片的上传和下载速度至关重要,当然这并不是说一上来就搞很NB的架构,至少具备一定扩展性和稳定性.虽然各种架构设计都有,在这里我只是谈谈我的一些个人想法. 对于图片服务器来说IO无疑是消耗资源最为严重的,对于web应用来说需要将图片服务器做一定的分离,否则很可能因为图片服务器的IO负载导致应用 崩溃.因此尤其对于大型网站和应

浅谈大型web系统架构

动态应用,是相对于网站静态内容而言,是指以c/c++.php.Java.perl..net等服务器端语言开发的网络应用软件,比如论坛.网络相册.交友.BLOG等常见应用.动态应用系统通常与数据库系统.缓存系统.分布式存储系统等密不可分. 大型动态应用系统平台主要是针对于大流量.高并发网站建立的底层系统架构.大型网站的运行需要一个可靠.安全.可扩展.易维护的应用系统平台做为支撑,以保证网站应用的平稳运行. 大型动态应用系统又可分为几个子系统: 1)Web前端系统 2)负载均衡系统 3)数据库集群系