架构设计:前后端分离之Web前端架构设计
在前面的文章里我谈到了前后端分离的一些看法,这个看法是从宏观的角度来思考的,没有具体的落地实现,今天我将延续上篇文章的主题,从纯前端的架构设计角度谈谈前后端分离的一种具体实现方案,该方案和我原来设想有了很大的变化,但是核心思想没变,就是控制层是属于Web前端的。
在以前文章里我说道前后端分离的核心在于把mvc的控制层归为前端的一部分,原方案的构想在实际的生产开发里很难做到,我觉得核心还是控制层和视图层的技术异构性,这样后果使得系统改造牵涉面太大,导致在项目团队里,沟通、协调以及管理成本相对较高,随着前端技术的发展,前端开发的工程量是越来越大,难度也是越来越高,因此前端工程的项目化,工程化和独立性越来越被人重视了,所以出现了大量的javascript MVC的富应用。如果javascript也能做到MVC模式,那么前端框架就可以抛弃异构语言的控制层,做到真正的独立。
要把传统的MVC的C层从前端剥离掉,我们首先要理解下MVC的C层即控制层到底做了什么样的事情,控制层的作用是模型层和视图层沟通的纽带,模型层进一步具体点就是数据层,视图层具体点就是数据展示给用户的方式,下面我们看看java的Web应用里,控制层和视图层是如何耦合的呢?做过java的web开发的人第一个反应就是页面里回嵌入大量java代码或者使用jsp的标签或者使用velocity,freemark这样的模板语言,这些东西大家很自然的把它们归为服务端的东西,但是它们却出现在了视图层,所以视图层和控制层没法解耦,其实除了这个还有一个大家很熟悉但又很少把它归为是视图层和控制层的耦合因素,这个因素就是页面的跳转,用ajax的角度描述这个因素就是页面的同步提交,这两个因素也就是构成了控制层的核心应用(是应用数据和页面展示的桥梁)。
要将前端独立起来,控制层归为前端是不可改变的定律,如果服务端的人太傲慢无礼,不肯放掉控制层,那么前端就必须自己来做控制层,那么前端做控制层的难点就是解决前端技术如何做到解决服务端数据展示和有时无法避免的同步提交问题,解决服务端数据展示问题就是要在javascript语言里找到替换java代码、jsp标签以及velcity这样的模板语言的技术,很幸运在javascript的确拥有这样的技术,这就是微软公司贡献的jQuery的模板语言jquery-tmpl,它的访问地址是:
https://github.com/BorisMoore/jquery-tmpl
百度搜索的地址:
有了javascript的模板技术,我们就可以不用在页面再写入服务端的任何东西,这样就达到异构语言的控制层和前端的分离,从而摒弃服务端的控制层。而模板语言需要的数据就可以通过ajax请求发送到页面,ajax接收到数据后传输到javascript的模板语言里最终就可以达到服务端数据在页面上的展示,使用这种方式展示数据好处不仅仅局限于控制层和视图层的解耦,同时还会提升页面的响应效率,因为通过这种方式,数据和服务端的交换不再需要视图展示要素即服务端直接发送个页面,或者是页面的片段,然后操作dom使得这些视图性的东西展示到页面上的行为,而现在服务端只需要传输需要传输的数据就行,这样一个http请求的数据传输量会大大减少,减少http请求的数据大小是提升网站加载效率的重要指标之一,同时如果传输只需要最必要的数据,那么服务端和前端的交互就可以做到统一的报文格式,使用统一的报文规范,这样会对项目管理,系统运维和维护带来质的飞越。
让控制层彻底归为web前端的第二步就是要让web应用的同步提交操作彻底死翘翘,而传统的同步提交只要承担整个web应用的入口的功能即可。谈到这里我想如果对web前端开发有过经验的人看到上面这句话就很容易联想到现在很火的单页面开发,没错我的确在讲单页面开发,其实javascript MVC的最高境界就是单页面模式。
虽然时下的web应用是ajax的天下,但是如果我们想彻底的抛弃同步提交请求的想法真的应用到实践中,开发人员会发现它常常会变成一个吃力不讨好的事情,为什么说它是一件吃力不讨好的事情,我想主要体现在两个方面:
第一方面:ajax请求往往是作为纯数据的传输,那么页面效果的显示就需要开发人员自己操作DOM,使用各种javascript开发技巧,这就大大增加页面开发难度和复杂度,对于一个要投入市场的web应用,其成本和风险是可想而知的。
另一方面:同步提交页面会让用户享受一种很顶级的用户体验,这就是浏览器的前进和后退体验,如果让ajax做前进和后退,特别是用户和网站交互量很大的网站,这个操作可能会成为一件不可能完成的任务。
这里我首先讲如何解决前进和后退的问题,在浏览器的请求url地址有一个很重要的特性就是hash属性,例如我们写页面时候常常会写到这样的语句:
1
|
< a href=”#” onclick=”ftn()” id=”btn”>btn</ a > |
当用户点击这个链接时候,会促发click事件,可能很多人没有留心到此时网页请求的url后面会添加一个#号,例如:www.cnblogs.com/#,如果我们把这个链接改下,如下:
1
|
< a href=”#sharpxiajun” onclick=”ftn()” id=”btn”>btn</ a > |
再点击这个链接,我们会发现链接变成了www.cnblogs.com/#sharpxiajun,前面的#sharpxiajun就是url的hash,url的hash是不会发送给服务端的,不过在浏览器里有专门的事件可以监听到它,这个事件就是hashchange事件,它是一个window的事件,浏览器的前进与后退支持url的hash改变,同时window可以监听到该事件,因此我们可以通过改变url的hash再加上ajax请求就可以模拟页面的同步提交了,同时该请求是可以使用浏览器的前进和后退操作。
使用url的hash属性模拟同步的url,那么我们就可以将页面的url改成一个带hash的url地址,例如传统网站的注册页面地址应该是:www.cnblogs.com/register.html,现在可以改为www.cnblogs.com/#!/register,如果注册页面的上游页面是www.cnblogs.com/,那么我们在注册页面点击回退按钮时候页面就会跳转到www.cnblogs.com/。
如果我们web前端拥有的以上我所讲述的技术,那么一个web应用的控制层可以完全平移到了web前端,而web前端可以做到真正的项目独立,到时web前端只要和服务端建立合适的报文规范,使用时下流行的json数据格式,就可以完成Web应用的开发,这样的web应用就做到前后端的真正分离。
使用我讲到的技术开发网站,浏览器的通信都将是ajax,这个ajax按应用场景可以分为两种类型:一种类型是模拟同步提交的ajax,这个请求时获取视图,也就是页面,这个功能可以当做一个路由功能,这是控制层控制视图层的操作,另一个ajax就是获取数据,ajax获取到数据后,通过javascript模板技术进行转化,最后控制层将转化的数据和视图层结合到一起,最终将完整的页面呈现给网站的用户。
Javascript做控制层其实是通过url的hash完成的,核心是使用window的hashchange事件,这里就有一个web前端开发最头疼的问题,是不是所有浏览器都支持hashchange事件了?答案是:新浏览器都支持,最可恶的ie,在8以上包括8都支持,那么我们想让所有浏览器都可以使用hashchange怎么办来了?jquery有个插件可以让低版本的浏览器支持hashchange事件,有兴趣的人可以百度一下,不过如果是在移动设备上开发web应用时完全不用担心兼容问题。
前面我用到一个带hash的url:www.cnblogs.com/#!/register,#!/xxx是我推荐给大伙的书写形式,理由是:我们做一个网站都需要给搜索引擎示好,但是搜索引擎的网络爬虫都是抓取静态页面,对于ajax请求的静态页面往往无能为力,因此我们要让搜索引擎的网络爬虫能找到我们的页面所以在#后面加上!/,很多高级的搜索引擎会抓取到我们网页上的内容,这里主要是指google,百度的是不是有类似的能力,俺就不清楚,这个就得问问度娘了。
我之前在我的博客里给大伙分享了我自己写的一个javascript框架,当时我使用一个文件,一个库来完成我的框架,这样的框架其实只能算是把一个Web开发里能通用的东西做了一个抽取和汇总,换种说法就是以前的框架是一个工具类大集合,但是到了我今天讲的web前端的javascript框架,这样的工具类是不能满足我们的需求,原因是控制层被移到了web前端,这样web应用的数据模型及模型层和控制层对视图层的路由功能同时也迁移到了web前端,那么某一个页面对应的数据模型以及对应的视图页面,我们应该让它和别的数据模型和对应页面有一个明显的界线,具体实现里就是一个视图及一个页面对应的javascript代码应该要模块化,所以我们最好一个页面对应一个javascript文件,这样我们就要对javascript代码进行模块化管理,要引入requieJS或者国产的seajs工具,具体的使用有兴趣的童鞋可以百度或者google一下。
如果我们不会用requieJS和seajs怎么办了?大型网站的javascript代码和css代码在生产上都会尽量的合并成少量的文件,因此我这里建议大家要用面向对象的方式组织自己的javascript代码,这里我推荐一个借鉴与jQuery架构类似的javascript框架模板:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
( function ($,params){ function MyObj = { return this ._init.apply( this ,arguments); } myObj.fn = myobj.prototype; myObj.fn = function (){ return { _init: function (){ // 初始化方法 this .setting = arguments; return this ; }, _bindEvt: function (){ // 事件绑定 代码… return this ; }, _pageLoad: function (){ _bindEvt(); 代码… return this ; } } }; // 给类添加属性或方法,相当于静态变量 myObj.extend = function (obj){ var extended = obj. extended; for ( var i in obj){ myObj.fn[i] = obj[i]; } if (extended) extend(myObj); }; myObj.load = function (param){ return new myObj(params)._pageLoad(); } return myObj; })(jQuery,params,undefined) |
该框架的load方法相当于java的main函数,这里我们使用javascript对象的技术构建对象,那么该对象对外就很容易扩展,每个原型prototype方法都会返回this指针这样就可以达到jQuery里方法连缀的写法。
其实这样的结构最后也能应用到requieJS和seajs里面,这样写的javascript代码的结构性和层次性更好,扩展性更强。
好了文章写完了,本篇文章一气呵成,难免有遗漏错误地方,到时还请认真的童鞋及时指出。