项目背景刚刚参与了一个项目,背景:后台使用的是java,后台服务已经发展的差不多了,现在都会通过web对外提供服务,也就是B/S架构。后端专注于业务逻辑,不想在后端做页面渲染,只为前端提供数据接口。所以协商后,打算前后端完全分离,页面上所有数据通过ajax从后端获取,页面渲染完全由前端完成。此外,还有一个紧急情况。该项目需要紧急启动。整个网站的开发时间只有两周,两周!所以在这样的背景下,我决定开始前后端完全分离的尝试。之前的开发是同步渲染和异步渲染的混合。有些东西后台PHP可以给你编译好,比如通用的页面模板,后台返回的页面参数等等。我提前预感到这个完全分离可能会遇到一些困难,但是项目上线了,并且无法深入架构,所以打算使用jQuery+handlebars,jQuery完成页面逻辑和DOM操作,使用handlebars完成页面渲染。计划如此简单粗暴,但效益最能保证项目按期完工。其实,前后端分离并不是一件容易的事,这样做会有很多不完善的地方,后面再说。浅谈前后端分离所谓前后端分离,究竟是什么分离呢?其实就是页面的渲染。之前,后端渲染页面,交给前端展示。分离之后,前端需要自己组装html代码,然后显示出来。前端管理页面渲染有很多好处,比如减少网络请求量,制作单页应用等。事情听起来很简单,但是这样的分离会涉及到很多问题,比如:?资源的按需加载。特别是在单页应用程序中。?页面呈现逻辑。分离使得前端逻辑急剧增加,需要好的前端架构,比如MVC模型。?数据验证。因为页面数据都是从后台请求的,所以需要验证显示的数据是否合法,避免xss或者其他安全问题。?短暂的白屏。因为页面没有同步渲染,请求的数据还没完成页面就一片空白,体验很差。?代码重用。众多的模板和逻辑模块需要很好地组织起来以实现可重用性。?路由控制。无刷新的前端体验也破坏了浏览器的后退按钮,前端视图需要路由机制。?搜索引擎优化。服务器不再返回页面,前端根据不同的逻辑呈现不同的视图(不是页面)。对搜索引擎友好需要做很多额外的工作。上述每个问题都非常棘手,需要处理适合实际项目的精心设计的解决方案。已经有很多框架可以帮我们做这些事情,Backbone、EmberJS、KnockoutJS、AngularJS、React、avalon等等,用它们来构建丰富的前端。但是框架毕竟是框架,要在实际项目中使用,还是需要有自己的设计,框架并不能解决所有问题。之前也看过淘宝团队的做法,用nodejs作为中间层处理页面渲染、路由控制、SEO等事情,重新定义了前后端的分界线。我个人觉得这应该是正确的方向。感觉有点颠覆。前端正在走向工程化,将成为真正的全栈大前端。不知道现在淘宝有没有全面铺开这种结构,很期待看到效果。以上这些框架,还有淘宝的做法,毕竟都是神作。本人大三,只是学习参考,没能在实际项目中使用。低头看我现在的项目,一个前端,两个星期,完成一个完整的web项目,还是用最安全最先进的方式来吧~项目整体的基本结构不是单页的application,但是有些模块需要做成部分单页操作。对于需要逐步完成的操作,只需要部分加载子页面即可。因此,一个模块有一个html主页面,最初只有一些基本的骨架,一个同名的js文件,模块逻辑都在这个js文件里,还有一个同名的css文件,定义了模块的所有样式在这个js文件中。对于需要异步加载的子页面,比如上图中每一步的页面,我都是使用jQuery的$.load()方法来加载的。该方法可以加载页面某个容器中的内容,可以指定回调函数。使用起来很容易。对于异步加载的子页面,如_step1.html,我以_开头,以区分它们。为了保证浏览器的前进后退按钮可用,我使用hash做路由标签,页面地址如:publish.html#step2。一个缺陷是哈希不会发送到服务器,因此SEO将无用。其实使用historyAPI也可以更优雅的解决问题,但是需要考虑兼容性,还有额外的工作要做,时间因素也考虑在内,退而求其次,本项目没有需要做SEO。或者像淘宝的方案,nodejs层和浏览器层在路由上统一,SEO问题就可以轻松解决。不过显然不在自己的实力范围之内,汗——!除了用$.load异步加载的子页面外,其余部分页面使用handlebars提供的模板渲染。我用的是handlebars的预编译功能,很强大。首先,它节省了页面加载阶段所需的编译时间(编译handlebars模板),其次,编译后的模板(js文件)易于复用。接下来就是如何组织前端逻辑了,因为没有用到mv*框架,所以只能自己写一个容易开发的结构。上面说过,每个模块都有一个主js文件,文件内容结构如下:varpublish={//模块初始化入口init:function(){this.renderData(param);这个.initListeners();},//内部使用的函数renderData:function(param){//渲染数据。.},//统一绑定监听器initListeners:function(){$(document.body).delegates({'.btn':function(){//点击事件},'.btn2':function(){//点击事件2},'.checkbox':{'change':function(){//改变事件}}});}}给每个模块一个命名空间,所有的方法都挂在上面,在js文件中定义函数即可,不要立即执行任何东西,然后调用html文件中的入口方法:publish.init()。业务逻辑封装成函数,比如上面的renderData,然后从其他地方调用。页面的事件监听器都注册在body元素上,通过事件代理来完成。为了避免on、click等代码写太多,jQuery扩展了一个delegates方法,用于统一方式绑定和监听设备,如上图。让我们也发布委托定义的代码://以配置的方式委托事件$.fn.delegates=function(configs){el=$(this[0]);for(varnameinconfigs){varvalue=configs[名称];if(typeofvalue=='function'){varobj={};obj.click=值;值=对象;};for(vartypeinvalue){el.delegate(名称、类型、值[类型]);}}返回这个;}基本结构是这样的,没有新的技术,只是现有东西的组合。但工作远未结束。在实际应用中还有一些事情需要处理。下面详细说说:公共头底部的引用是一个棘手的问题。一般一般的header和bottom都会放一些公共的代码,比如页面外部结构的html代码,站点使用的jQuery,handlebars等库,还有站点通用的js和css文件。传统开发中,通常是单独写一个head.html等文件,在其他页面使用include语句等后端代码导入,实现复用。现在前后端分离了,不能靠后端给你渲染,只能在前端做。既然用了handlebars,很容易想到把公共部分写成模板,然后预编译,生成header.js文件,然后在其他页面引用。但是在实际操作中发现了一个问题。handlebars是静态模板,编译后生成的字符串通过innerHTML插入到页面中。这在一般模板中是没有问题的。现在有个问题就是header里面有一些标签就可以正常加载了。于是使用每个页面头部的代码就变成了这样:
