单页应用原理分析与底层实现
2020-06-29李占仓
李占仓
(天津职业大学,天津 300410)
引言
在Web 应用开发领域,目前有两种主流的构建方式,一种是采用多页面的传统多页应用,另一种是采用单页面的单页应用[1]。多页应用一般由多个页面组成,页面间的跳转往往需要后端路由的支持,每次的页面跳转,都需要向服务端重新请求整个新的页面;单页应用只有一个页面,视图的切换由前端路由负责,本质上只是不同的DOM 元素的显示与隐藏,不需要再次向服务器请求新的完整页面,可以通过AJAX向服务端异步请求数据,并渲染到指定的DOM元素[2]。本文从实现原理,实现方法两方面,对两种应用进行了全面的对比。特别是对于单页Web 应用,详细分析了其实现原理,并介绍了具体的实现方式,为Web应用开发者提供了参考。
一、单页应用
单页应用一般是指一种单页的Web 应用,是相对于传统的多页应用而说的,可以通过与多页应用对比,理解单页应用的概念。
(一)多页应用
多页应用(Mutiple Page Application),简称MPA,是指一个Web 应用由多个完整的Html 页面组成,每个Html 页面都有自己的CSS 和JavaScript文件。在多个页面间进行切换时,通常依赖于服务端的路由技术,或者使用超连接标签的href 属性,或者通过JavaScript 控制location 对象的href 属性,向服务器请求整个Html文件。
由于MPA 应用在使用的过程中,需要不断的页面切换,进而导致不断的重新请求页面,浏览器需要不停的创建完整的DOM 树,删除完整的DOM 树,不但造成不必要的网络传输,而且增加了客户端的负担,严重影响了用户的体验[3]。多页Web应用示意图,如图1所示:
图1 多页应用示意图
在图1中,Web应用由多个页面,在浏览器端触发的每次页面跳转操作,都会请求一个完整的Html页面,并在浏览器对整个页面进行渲染。
(二)单页应用
单页应用(Single Page Web Application),简称SPA,是指整个Web 应用只有一个html 页面组成,该页面在初始化时一次性加载相应的Html、JavaScript和Css,一旦加载完成,在浏览器运行期间不再重新加载,之后所有的交互操作都在这一个页面上完成。其原理是借助JavaScript 响应用户的操作,动态的隐藏或显示相应的内容,并通过Ajax 请求服务端的数据,使用JavaScript 对数据进行页面渲染。
由于单页应用避免了页面的重新加载,最大程度的减少了不必要的网络传输,并且浏览器不需要重新创建和销毁完整的DOM 树,大大减轻了客户端的负担,因此SPA 可以提供较为流畅的用户体验[4]。单页应用示意图,如图2所示:
图2 单页应用示意图
在图2中,Web应用只有一个单页面组成,在浏览器首次访问应用时,一次性加载单页以及单页包含的所有内容,包括CSS、JavaScript、图片等。单页面中往往包含多个模块,所谓的视图切换,就是使用JavaScript控制多个DOM模块的显示与隐藏。图2 中的“当前模块”为当前显示的模块,其他模块为隐藏的模块。之后的视图切换操作不会再次请求服务端的页面。可以以AJAX的方式异步请求服务端的数据,并渲染到单页面的不同的模块中。
二、Web中的路由
单页应用和多页应用在实现原理上,最大的区别体现在采用的路由机制不同,单页应用采用前端路由,多页应用一般采用后端路由。
(一)路由
从功能上解释,Web 应用中的路由是指URL与浏览器显示的内容之间的映射,即随着URL 的变化,页面中显示不同的内容;从原理上解释,路由是指URL 到处理函数之间的映射,即由不同的函数来处理不同的URL,从而实现界面的切换。
(二)后端路由
后端路由也称服务器端路由[5],由服务器来实现URL 与处理函数之间的映射。实现过程是:当服务器接收到客户端的HTTP请求时,会根据请求对象中的URL,找到服务器端对应的处理函数并执行,然后将函数的返回值响应给客户端。在后端路由中,根据URL 请求的内容不同,处理函数主要有三种不同的处理方式:
1.对于静态资源的请求,函数的功能就是对服务端静态资源的读取,如html 页面、图片等,将这些静态资源直接返回,不进行加工处理。
2.对于动态资源的请求,函数的功能就是首先执行动态资源中的脚本,其中可能包括读取数据库,处理相应的业务,再使用相应的模板将数据渲染到页面,生成对应的静态页面并返回。
3.对于数据的请求,函数的功能就是读取数据库,将数据以某种格式响应给客户端,如JSON、XML等,本质上数据也是一种动态资源。
后端路由应用项目有如下特点:静态资源和动态资源在一个项目中,部署在一起;所有的页面请求都由服务器端处理,然后响应给客户端,优缺点如下:
优点:安全性能好、SEO 表现好、首屏时间(浏览器显示第一屏页面所消耗的时间)短,前端处理压力小等。
缺点:服务器端压力大,频繁的页面请求浪费不必要的网络资源,客户端渲染任务重,用户体验差等。
(三)前端路由
前端路由就是由客户端来实现URL 与处理函数之间的映射,处理函数由JavaScript 实现,处理方式就是进行一些DOM的显示和隐藏[6]。在处理路由的过程中,不再向服务端请求页面,极大的提升了用户的体验。随着前后端分离式开发的流行和单页应用的普及,前端路由得到了广泛的应用。前端路由Web 应用项目有如下特点:应用的前后端的开发可以彻底分开,一个Web 项目可以由一个前端项目和一个后端项目组成,后端项目只为前端项目提供API接口,其余的工作均由前端项目完成,部署时,前后端也可以分开部署。前端路由优缺点如下:
优点:路由速度快,用户体验好,项目前后端耦合度小,分工更明确,开发更专业。
缺点:首屏时间长;SEO表现差,不利于搜索引擎收录;容易造成CSS命名冲突;使用浏览器的前进,后退键时,会重新发送请求,重新通过AJAX获取数据,不能合理的利用缓存等。
三、单页应用原理分析
从实现原理上讲,单页应用有两种实现方式,一种是基于hash 的前端路由,另一种是基于H5新增的history API的前端路由。
(一)基于Hash的实现原理
Hash是BOM中location对象的一个属性,它表示URL 的锚部分,比如URL 为:http://localhost/index.html#student,则location.hash 的 值 为“#student”,hash值从#号开始[7]。
通过Hash实现前端路由思路如下:
1.创建路由按钮和初始视图DOM。
2.通过点击路由按钮改变URL中的Hash值。
3.当Hash 发生变化时,触发window 对象的onhashchange事件。
明确中国有构建国际商事法庭的必要性之后,在设计国际商事法庭的具体规则之前,我们需要考虑的是中国国际商事法庭的定位问题,或者说是中国国际商事法庭的管辖标准问题。该问题直接决定了中国国际商事法庭如何建设。
4.在onhashchange 事件处理函数中实现DOM的更新。
具体代码如下:
代码执行步骤和说明如下:
1.创建超连接表示路由按钮,创建id为view的div表示初始视图。
2.定义一个Router类,成员如下:
routes 对象属性:表示路由集合,一个路由由一个路由标识和一个与之对应的处理方法组成,属性名表示路由标识,属性值表示对应的处理方法。
currentUrl字符串属性:表示当前的路由标识。
route 方法:用于构造routes,向routes 集合中添加路由。
refresh 方法:hashchange 事件的响应函数,会根据不同的hash,调用不同的处理方法。
Init 方法:用于初始化Router 对象,为window对象的load事件和hashchange事件绑定响应方法。
3.实例化Router 对象router 并调用init 方法,完成初始化,为window 的load 事件和hashchange事件绑定处理方法refresh。
4.调用router对象的route方法向路由集合中添加两个路由。
5.当点击超路由按钮时,由于改变了url 中的hash值,refresh方法被执行。
6.refresh 方法获取当前的hash 值,去掉#之后转换为路由标识,再调用与路由标识对应的处理方法,更新DOM。
(二)基于History的实现原理
history 对象是window 对象的一部分,它包含用户访问过的URL。在H5中,history 添加了push-State()和replaceState()两个方法,可以向历史栈中添加记录,从而模拟浏览历史和前进后退,实现前端路由[8]。
History实现前端路由思路如下:
1.创建路由按钮和初始视图DOM。
2.为路由按钮绑定处理方法。
3.当点击路由按钮时,触发处理方法,通过pushState 向历史栈中添加URL 记录,同时传递路由标识参数信息,并更新视图。
4.当执行前进后退操作时,触发window 的popstate事件,在popstate事件的处理函数中,接收popstate 事件参数,从中取出路由标识,再根据路由标识,更新视图实现代码如下。
代码执行步骤和说明如下:
1.定义一个Router类,成员如下:
routes 对象属性:表示路由集合,属性名表示路由标识,属性值表示对应的处理方法。
route 方法:用于构造routes,向routes 集合中添加路由。
refresh 方法:popstate 事件的响应函数,从参数中获取路由标识,根据路由标识,调用不同的处理方法更新视图。
Init方法:用于Router对象的初始化。
2.实例化Router对象router。
3.调用router 的init 方法,完成初始化,内容包括:为路由按钮添加点击事件处理方法,为window对象的hashchange事件绑定响应方法。
4.调用router对象的route方法向路由集合中添加两个路由。
5.当点击路由按钮时,执行路由按钮的响应方法,从路由按钮中获取路由标识,通过history 的pushState 向历史栈中添加记录,同时传递路由标识参数{name:link},然后调用与路由标识对应的路由处理方法,更新视图。
6.当点击浏览器的前进后退按钮时,触发hashchange 事件处理方法,从参数中获取路由标识,根据路由标识,调用对应的处理方法,更新视图。
四、结语
单页应用以其近似原生应用的用户体验、前后端分离开发方式等优势,在Web 应用开发领域愈发流行。当前流行的三大MVVM 框架Vue.js,React,angularJS,都支持单页应用开发,实现单页应用的核心技术在于路由的处理,三大框架都提供了完备的路由机制。对于开发者而言,在没有充分理解前端路由原理的情况下,直接使用框架提供的路由方案进行开发,往往知其然而不知其所以然,在出现问题时,也不容易排错。文章通过与多页应用的对比,详细介绍了单页应用的原理,通过与后端路由对比,介绍了前端路由的实现原理,最后,通过Hash 和Location 两种技术,从低层对单页应用进行了实现。为开发者提供了技术参考,在理解原理的情况,使用框架技术进行前端开发,会显得更加得心应手。