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

跟上一篇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合并功能,轻松完成协同开发

H5单页面架构:backbone + requirejs + zepto + underscore的更多相关文章

  1. H5单页面架构:requirejs + angular + angular-route

    说到项目架构,往往要考虑很多方面: 方便.例如使用jquery,必然比没有使用jquery方便很多,所以大部分网站都接入类似的库: 性能优化.包括加载速度.渲染效率: 代码管理.大型项目需要考虑代码的 ...

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

    本文转载自:http://www.cnblogs.com/kenkofox/p/4648472.html 上一篇<浅谈HTML5单页面架构(一)--requirejs + angular + a ...

  3. H5单页面架构:自定义路由 + requirejs + zepto + underscore

    angular优点: 强大的数据双向绑定 View界面层组件化 内置的强大服务(例如表单校验) 路由简单 angular缺点: 引入的js较大,对移动端来说有点吃不消 语法复杂,学习成本高 backb ...

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

    心血来潮,打算结合实际开发的经验,浅谈一下HTML5单页面App或网页的架构. 众所周知,现在移动Webapp越来越多,例如天猫.京东.国美这些都是很好的例子.而在Webapp中,又要数单页面架构体验 ...

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

    requirejs + angular + angular-route 浅谈HTML5单页面架构 众所周知,现在移动Webapp越来越多,例如天猫.京东.国美这些都是很好的例子.而在Webapp中,又 ...

  6. H5单页面手势滑屏切换原理

    H5单页面手势滑屏切换是采用HTML5 触摸事件(Touch) 和 CSS3动画(Transform,Transition)来实现的,效果图如下所示,本文简单说一下其实现原理和主要思路. 1.实现原理 ...

  7. 快速构建H5单页面切换骨架

    在Web App和Hybrid App横行的时代,为了拥有更好的用户体验,单页面应用顺势而生,单页面应用简称`SPA`,即Single Page Application,就是只有一个HTML页面的应用 ...

  8. 快速构建H5单页面切换应用

    在Web App和Hybrid App横行的时代,为了拥有更好的用户体验,单页面应用顺势而生,单页面应用简称`SPA`,即Single Page Application,就是只有一个HTML页面的应用 ...

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

    本文转载自:http://www.cnblogs.com/kenkofox/p/4650310.html 不过,这一篇,我想进一步探讨一下这两个框架的优缺点,另外,再进一步,抛开这两个框架,回到本真, ...

随机推荐

  1. (3)选择元素——(5)为项目列表加样式(Styling list-item levels)

    Let's suppose that we want the top-level items, and only the top-level items, to be arranged horizon ...

  2. nodejs 批处理运行 app.js

    1.直接执行run.bat文件         以下的内容为批处理文件run.bat中的内容,批处理命令中NODE_PATH为Node.js的安装路径. 使用express 生成的项目.app.js为 ...

  3. 调起qq临时通话

    1.在qq推广的网站上的推广工具中将个人或者公司的qq确认为推广qq 2. 选择好显示的图片,将代码拷贝到页面上就可以了

  4. SQLLoader2(导入EXCEL或csv格式的文件)

    一.EXCEL文件导入:1.首先将EXCEL文件另存为CSV格式文件--->用UltraEdit工具打开时可看到字段之间以逗号分隔. ,EMPNO,ENAME,JOB,MGR,HIREDATE, ...

  5. 在本地调试微信项目(C#)

    之前一人负责微信的项目,那时2014年LZ还没毕业..啥都不懂,为此特别感谢@SZW,没有你的框架,我可能都无从下手 当时做项目最麻烦的就是调试,因为很多页面都要使用 网页授权获取用户信息 在电脑上打 ...

  6. uva 12100 Printer Queue

    The only printer in the computer science students' union is experiencing an extremely heavy workload ...

  7. Yogurt factory(POJ 2393 贪心 or DP)

    Yogurt factory Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 8205   Accepted: 4197 De ...

  8. 前端上将字符串用语音读出来只能在IE上运行 其他不行 代码极少

    先保存保存自己的笔记 有高手看到求指点 <script type="text/javascript"> var VoiceObj; try { VoiceObj = n ...

  9. List是线程安全的吗?如果不是该怎么办呢?安全的List对性能的影响有多大呢?

    测试条件: 开启2个并行执行任务,往同一个list对象写入值 测试代码: ; static List<int> list = new List<int>(); static v ...

  10. httrack,webdup,WinHTTrack,WebZip

    怎么下载摄像头游戏jabbo,并使其能离线运行?修改 1.摄像头游戏jabbo:JABBO Ultimatum by LiveMurals Interactive电脑为:windows 7 32位.试 ...