本文转载自:http://www.cnblogs.com/kenkofox/p/4650310.htmlhtml
不过,这一篇,我想进一步探讨一下这两个框架的优缺点,另外,再进一步,抛开这两个框架,回到本真,本身搞个简单的路由同样能够实现单页面。前端
这个对于刚作前端开发的新同窗来讲就最好不过了,若是一来到岗位就一大堆angular、backbone、requirejs,看资料都看一两周。其实你们最熟悉的东西仍是那个美圆$,用美圆能解决的问题,就不要麻烦到angular、backbone大爷了。jquery
事先说明,因为个人业务范围窄,不必定能把angular和backbone的功能都用一遍,因此如下的分析可能以偏概全,欢迎你们讨论。git
angular优势:github
angular缺点:web
backbone优势:正则表达式
backbone缺点:闭包
其实,这两个框架都很是优秀,可是,在实际业务中,不必定百试百灵,由于有一些移动端的单页面web,业务就很简单,只是路由分别切换到几个子模块,每一个子模块基本都是拉一次数据,展现给用户,不多用户交互从而修改数据,改变视图的功能。架构
对于这种状况,使用angular未免有点杀鸡用牛刀的感受,而backbone虽然小巧了很多,可是模型的功能也是浪费的。app
因此,在这里,我想探讨一下,可否抛开这两个框架,只索取咱们基本所需,创建一个更简单的架构呢?
经验看来,一些类库是必不可少的:
本身作一套最简单的架构,思想很是简单:
简单的思路,让架构很是简洁明了,新团队成员来到可以轻松上手,而angular和backbone的架构,少说得二、3天才能融入一个已有项目中去。
接下来,咱们具体看看怎么作。
第一步,仍是index.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Underscore & Director & Requirejs</title> </head> <body> <div id="container"></div> <script data-baseurl="./" data-main="main.js" src="libs/require.js" id="main"></script> </body> </html>
这个跟前两篇没什么差异。requirejs引入main.js做为程序入口
第二步,main.js配置requirejs的依赖关系,并启动webapp
(function (win) { //配置baseUrl var baseUrl = document.getElementById('main').getAttribute('data-baseurl'); /* * 文件依赖 */ var config = { baseUrl: baseUrl, //依赖相对路径 paths: { //若是某个前缀的依赖不是按照baseUrl拼接这么简单,就须要在这里指出 director: 'libs/director', zepto: 'libs/zepto.min', underscore: 'libs/underscore', text: 'libs/text' //用于requirejs导入html类型的依赖 }, shim: { //引入没有使用requirejs模块写法的类库。 underscore: { exports: '_' }, zepto: { exports: '$' }, director: { exports: 'Router' } } }; require.config(config); require(['zepto', 'router', 'underscore'], function($, router, _){ win.appView = $('#container'); //用于各个模块控制视图变化 win.$ = $; //暴露必要的全局变量,不必拘泥于requirejs的强制模块化 win._ = _; router.init(); //开始监控url变化 }); })(window);
director.js没有AMD写法,仍是按照shim的方式引入。另外,因为$和_的使用率过高,因此这里直接公开为全局变量。
除此以外,还加了appView变量,目的是方便各个子模块修改界面。
第三步,router.js配置路由
这里使用的路由类库是director(https://github.com/flatiron/director),相对精简的路由,但其实对于咱们这个程序来讲,貌似还不够精简。先凑合着吧。
director官网给出的示例也至关简单,就是“路径”对应“函数”,很是清晰并且实用的方式。
var author = function () { console.log("author"); }; var books = function () { console.log("books"); }; var viewBook = function (bookId) { console.log("viewBook: bookId is populated: " + bookId); }; var routes = { '/author': author, '/books': [books, function() { console.log("An inline route handler."); }], '/books/view/:bookId': viewBook }; var router = Router(routes); router.init();
来看看咱们本身的版本:
define(['director', 'underscore'], function (Router, _) { //先设置一个路由信息表,能够由html直出,纯字符串配置 var routes = { 'module1': 'module1/controller1.js', 'module2/:name': 'module2/controller2.js' //director内置了普通必选参数的写法,这种路由,必须用路径“#module2/kenko”才能匹配,没法缺省 // 'module2/?([^\/]*)/?([^\/]*)': 'module2/controller2.js' //可缺省参数的写法,其实就是正则表达式,括号内部分会被抽取出来变成参数值。backbone作得比较好,把这个语法简化了 // “ /?([^\/]*) ” 这样的一段表示一个可选参数,接受非斜杠/的任意字符 }; var currentController = null; //用于把字符串转化为一个函数,而这个也是路由的处理核心 var routeHandler = function (config) { return function () { var url = config; var params = arguments; require([url], function (controller) { if(currentController && currentController !== controller){ currentController.onRouteChange && currentController.onRouteChange(); } currentController = controller; controller.apply(null, params); }); } }; for (var key in routes) { routes[key] = routeHandler(routes[key]); } return Router(routes); });
这里把director的路由配置修改了一下,原来只能接受<String, Function>这样的key value对,但参考以前backbone篇,更好方式应该是让路由表尽可能只有字符串配置,不要写逻辑(函数)。
因此,上述代码中,多了一个routeHandler,目的就是创建闭包,把string(配置)转换为一个闭包函数。
结果,运行效果就是,遇到一个路由,就根据配置加载对应的子模块代码。后续实际执行什么,由子模块本身决定。这样main/router就能完全跟子模块解耦。
第四步,创建一个模块
tpl.html
<div> Here is module 1. My name: <%=name %><br> <a href="#module2/fromModule1">turn to module 2</a> </div>
controller1.js
define(['text!module1/tpl.html'], function (tpl) { var controller = function () { appView.html(_.template(tpl, {name: 'kenko'})); }; return controller; });
我以为能实现业务逻辑的前提下,越简单的架构就越好,便于传承和维护。
controller就是这个子模块要作的逻辑,appView是整个视图根节点,想怎么玩就怎么玩,这对于不熟悉angular、backbone的同窗最爽不过了。
这里重点是利用了requirejs作模块化和依赖加载,并用了underscore的模版库template。
第五步,再作一个模块,加上一些销毁接口
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(['text!module2/tpl.html'], function (tpl) { var controller = function (name) { appView.html(_.template(tpl, {name: name?name:'vivi'})); $('button').on('click', function clickHandler() { alert('hello'); }); controller.onRouteChange = function () { console.log('change'); //能够作一些销毁工做,例如取消事件绑定 $('button').off('click'); //解除全部click事件监听 }; }; return controller; });
至此,整个简单的框架就完成了。
大道至简,我很是喜欢这样简单的架构。但愿对新手朋友有所帮助。
最后,关于director的路由,要吐槽一下,这个并无backbone那些这么好用,它没有内置的缺省参数写法,须要本身理解正则表达式,写复杂的([?*。参照上边router.js的代码。
路由匹配的本质,实际上是正则表达式的exec匹配和提取参数。我后续会再整一个简单好用的路由,参考backbone的模式,猛击这里:http://www.cnblogs.com/kenkofox/p/4650824.html
本文代码:https://github.com/kenkozheng/HTML5_research/tree/master/UnderscoreRequireJS