构建单页Web应用
让我们先来看几个站点:
coding(https://coding.net/)
teambition(https://www.teambition.com/)
cloud9(https://c9.io/)
注意这几个站点的相同点。那就是在浏览器中,做了原先“应当”在client做的事情。
它们的界面切换非常流畅,响应非常迅速,跟传统的网页明显不一样,它们是什么呢?这就是单页Web应用。
所谓单页应用。指的是在一个页面上集成多种功能。甚至整个系统就仅仅有一个页面,全部的业务功能都是它的子模块,通过特定的方式挂接到主界面上。
它是AJAX技术的进一步升华。把AJAX的无刷新机制发挥到极致,因此能造就与桌面程序媲美的流畅用户体验。
事实上单页应用我们并不陌生,非常多人写过ExtJS的项目。用它实现的系统。非常天然的就已经是单页的了。也有人用jQuery或者其它框架实现过相似的东西。
用各种JS框架。甚至不用框架。都是能够实现单页应用的。它仅仅是一种理念。有些框架适用于开发这种系统。假设使用它们,能够得到非常多便利。
开发框架
ExtJS能够称为第一代单页应用框架的典型,它封装了各种UI组件。用户主要使用JavaScript来完毕整个前端部分,甚至包含布局。
随着功能逐渐添加,ExtJS的体积也逐渐增大。即使用于内部系统的开发。有时候也显得笨重了,更不用说开发以上这类执行在互联网上的系统。
jQuery由于偏重DOM操作。它的插件体系又比較松散。所以比ExtJS这个体系更适合开发在公网执行的单页系统,整个解决方式会相对照较轻量、灵活。
但由于jQuery主要面向上层操作,它对代码的组织是缺乏约束的。如何在代码急剧膨胀的情况下控制每一个模块的内聚性。而且适当在模块之间产生数据传递与共享。就成为了一种有挑战的事情。
为了解决单页应用规模增大时候的代码逻辑问题,出现了不少MV*框架。他们的基本思路都是在JS层创建模块分层和通信机制。
有的是MVC,有的是MVP。有的是MVVM。而且,它们差点儿都在这些模式上产生了变异。以适应前端开发的特点。
这类框架包含Backbone。Knockout,AngularJS,Avalon等。
组件化
这些在前端做分层的框架推动了代码的组件化,所谓组件化,在传统的Web产品中,很多其它的指UI组件,但事实上组件是一个广泛概念。传统Web产品中UI组件占比高的原因是它的厚度不足,随着client代码比例的添加,相当一部分的业务逻辑也前端化,由此催生了非常多非界面型组件的出现。
分层带来的一个优势是,每层的职责更专一了,由此,能够对其作单元測试的覆盖,以保证其质量。
传统UI层測试最头疼的问题是UI层和逻辑混杂在一起,比方往往会在远程请求的回调中更改DOM,当引入分层之后。这些东西都能够分别被測试,然后再通过场景測试来保证总体流程。
代码隔离
与开发传统页面型站点相比,实现单页应用的过程中,有一些比較值得特别关注的点。
从单页应用的特点来看。它比页面型站点更加依赖于JavaScript,而由于页面的单页化,各种子功能的JavaScript代码聚集到了同一个作用域,所以代码的隔离、模块化变得非常重要。
在单页应用中。页面模板的使用是非常普遍的。非常多框架内置了特定的模板,也有的框架须要引入第三方的模板。这种模板是界面片段,我们能够把它们类比成JavaScript模块,它们是还有一种类型的组件。
模板也一样有隔离的须要。不隔离模板,会造成什么问题呢?模板间的冲突主要存在于id属性上,假设一个模板中包含固定的id。当它被批量渲染的时候。会造成同一个页面的作用域中出现多个相同id的元素。产生不可预測的后果。因此。我们须要在模板中避免使用id,假设有对DOM的訪问需求。应当通过其它选择器来完毕。假设一个单页应用的组件化程度非常高,非常可能整个应用中都没有元素id的使用。
代码合并与载入策略
人们对于单页系统的载入时间容忍度与Web页面不同,假设说他们愿意为购物页面的载入等待3秒,有可能会愿意为单页应用的首次载入等待5-10秒,但在此之后,各种功能的使用应当都比較流畅,全部子功能页面尽量要在1-2秒时间内切换成功,否则他们就会感觉这个系统非常慢。
从这些特点来看,我们能够把很多其它的公共功能放到首次载入,以减小每次载入的载入量,有一些站点甚至把全部的界面和逻辑全部放到首页载入,每次业务界面切换的时候,仅仅产生数据请求,因此它的响应是非常迅速的。比方青云的控制台就是这么做的。
通常在单页应用中,无需像站点型产品一样,为了防止文件载入堵塞渲染,把js放到html后面载入。由于它的界面基本都是动态生成的。
当切换功能的时候,除了产生数据请求。还须要渲染界面,这个新渲染的界面部件通常是界面模板,它从哪里来呢?来源无非是两种,一种是即时请求,像请求数据那样通过AJAX获取过来。还有一种是内置于主界面的某些位置。比方script标签或者不可见的textarea中,后者在切换功能的时候速度有优势,可是加重了主页面的负担。
在传统的页面型站点中,页面之间是互相隔离的,因此。假设在页面间存在可复用的代码,通常是提取成单独的文件,而且可能会须要依照每一个页面的需求去进行合并。单页应用中,假设总的代码量不大,能够总体打包一次在首页载入。假设大到一定规模,再作执行时载入,载入的粒度能够搞得比較大。不同的块之间没有反复部分。
路由与状态的管理
我们最開始看到的几个在线应用,有的是对路由作了管理的,有的没有。
管理路由的目的是什么呢?是为了能降低用户的导航成本。比方说我们有一个功能。经历过多次导航菜单的点击,才呈现出来。
假设用户想要把这个功能地址分享给别人,他怎么才干做到呢?
传统的页面型产品是不存在这个问题的。由于它就是以页面为单位的。也有的时候,服务端路由处理了这一切。可是在单页应用中,这成为了问题,由于我们仅仅有一个页面,界面上的各种功能区块是动态生成的。所以我们要通过对路由的管理,来实现这种功能。
详细的做法就是把产品功能划分为若干状态,每一个状态映射到相应的路由,然后通过pushState这种机制,动态解析路由,使之与功能界面匹配。
有了路由之后,我们的单页面产品就能够前进后退,就像是在不同页面之间一样。
事实上在Web产品之外,早就有了管理路由的技术方案。Adobe Flex中。就会把比方TabNavigator。甚至下拉框的选中状态相应到url上。由于它也是单“页面”的产品模式。须要面对相同的问题。
当产品状态复杂到一定程度的时候,路由又变得非常难应用了,由于状态的管理极其麻烦,比方開始的时候我们演示的c9.io在线IDE,它就没法把状态相应到url上。
缓存与本地存储
在单页应用的运作机制中。缓存是一个非常重要的环节。
由于这类系统的前端部分差点儿全是静态文件。所以它能够有机会利用浏览器的缓存机制,而比方动态载入的界面模板,也全然能够做一些自己定义的缓存机制。在非首次的请求中直接取缓存的版本号,以加快载入速度。
甚至,也出现了一些方案,在动态载入JavaScript代码的同一时候,把它们也缓存起来。比方Addy Osmani的这个basket.js。就利用了HTML5 localStorage作了js和css文件的缓存。
在单页产品中,业务代码也经常会须要跟本地存储打交道,存储一些暂时数据。能够使用localStorage或者localStorageDB来简化自己的业务代码。
服务端通信
传统的Web产品通常使用JSONP或者AJAX这种方式与服务端通信,但在单页Web应用中。有非常大一部分採用WebSocket这种实时通讯方式。
WebSocket与传统基于HTTP的通信机制相比,有非常大的优势。
它能够让服务端非常便利地使用反向推送,前端仅仅响应确实产生业务数据的事件。降低一遍又一遍无意义的AJAX轮询。
由于WebSocket仅仅在比較先进的浏览器上被支持,有一些库提供了在不同浏览器中的兼容方案。比方socket.io,它在不支持WebSocket的浏览器上会降级成使用AJAX或JSONP等方式,对业务代码全然透明、兼容。
内存管理
传统的Web页面通常是不须要考虑内存的管理的。由于用户的停留时间相对少,即使出现内存泄漏。可能非常快就被刷新页面之类的操作冲掉了,但单页应用是不同的。它的用户非常可能会把它开一整天。因此。我们须要对当中的DOM操作、网络连接等部分格外小心。
样式的规划
在单页应用中。由于页面的集成度高。全部页面聚集到同一作用域,样式的规划也变得重要了。
样式规划主要是几个方面:
基准样式的分离
这里面主要包含浏览器样式的重设、全局字体的设置、布局的基本约定和响应式支持。
组件样式的划分
这里面是两个层面的规划,首先是各种界面组件及其子元素的样式。其次是一些修饰样式。组件样式应当尽量降低互相依赖,各组件的样式同意冗余。
堆叠次序的管理
传统Web页面的特点是元素多。可是层次少,单页应用会有些不同。
在单页应用中,须要提前为各种UI组件规划堆叠次序。也就是z-index,比方说,我们可能会有各种弹出对话框,浮动层,它们可能组合成各种堆叠状态。新的对话框的z-index须要比旧的高,才干确保盖在它上面。诸如此类,都须要我们对这些可能的遮盖作规划。那么,如何去规划呢?
了解通信知识的人,应当会知道,不同的频率段被划分给不同的通信方式使用,在一些国家,领空的使用也是有划分的。我们也能够用相同的方式来预先分段,不同类型的组件的z-index落到各自的区间。以避免它们的冲突。
单页应用的产品形态
我们在開始的时候提到,存在着非常多新型Web产品,使用单页应用的方式构建,但实际上,这类产品不仅仅存在于Web上。
点开Chrome商店,我们会发现非常多离线应用。这些产品都能够算是单页应用的体现。
除了各种浏览器插件。借助node-webkit这种外壳平台。我们能够使用Web技术来构建本地应用,产品的主要部分仍然是我们熟悉的单页应用。
单页应用的流行程度正在逐渐添加。大家假设关注了一些初创型互联网企业。会发现当中非常大一部分的产品模式是单页化的。这种模式能带给用户流畅的体验,在开发阶段。对JavaScript技能水平要求较高。
单页应用开发过程中,前后端是天然分离的,两方以API为分界。前端作为服务的消费者,后端作为服务的提供者。在此模式下,前端将会推动后端的服务化。
当后端不再承担模板渲染、输出页面这样工作的情况下。它能够更专注于所提供的API的实现,而在这种情况下。Web前端与各种移动终端的地位对等,也逐渐使得后端API不必再为每一个端作差异化设计了。
部署模式的改变
在如今这个时代,我们已经能够看到一种产品的出现了。那就是“无后端”的Web应用。
这是一种什么东西呢?基于这种理念。你的产品非常可能仅仅须要自己编写静态Web页面,在某种BaaS(Backend as a Service)云平台上定制服务端API和云存储,集成这个平台提供的SDK,通过AJAX等方式与之打交道,实现注冊认证、社交、消息推送、实时通信、云存储等功能。
我们观察一下这种模式,会发现前后端的部署已经全然分离了,前端代码全然静态化,这意味着能够把它们放置到CDN上,訪问将大大地加速。而服务端托管在BaaS云上。开发人员也不必去关注一些部署方面的繁琐细节。
假设你是一名创业者。正在做的是一种实时协同的单页产品,能够在云平台上,高速定制后端服务。把绝大部分宝贵的时间花在开发产品本身上。
单页应用的缺陷
单页应用最根本的缺陷就是不利于SEO,由于界面的绝大部分都是动态生成的,所以搜索引擎非常不easy索引它。
产品单页化带来的挑战
一个产品想要单页化。首先是它必须适合单页的形态。
其次,在这个过程中。对开发模式会产生一些变更,对开发技能也会有一些要求。
开发人员的JavaScript技能必须过关,同一时候须要对组件化、设计模式有所认识,他所面对的不再是一个简单的页面,而是一个执行在浏览器环境中的桌面软件。