前端猿一天不学习就没饭吃了,后端猿三天不学习仍旧有白米饭摆于桌前。IT行业的快速发展一直在推进着前端技术栈在不断地更新换代,前端的发展成了互联网时代的一个缩影。而单页面应用的发展给前端猿分了一杯羹。javascript
最先单页面的应用无从知晓,在2004年,google的Gmail就使用了单页面。到了2010年,随着Backbone的问世以后,此概念才慢慢热了起来。随着后来React、Angular、Vue的兴起,单页面应用才成了前端圈里人人皆知的架构模式。接下来小生将经过对比传统页面应用和单页面应用来讲明SPA具体是什么。css
早期web应用的先后端交互模式是这样的,每一个html做为一个功能元件,经过刷新、超连接、表单提交等方式,将页面组织起来后给用户提供交互。
后期不少流行的框架都是基于此模式进行设计的,好比 Ruby on Rails,Spring MVC,Express 等等
传统的web应用中,浏览器只是做为展现层,路由、服务调用、页面跳转都是服务端来处理的。也就是MVC的架构都是放在后端的,只有V这一层,将页面经过网络发送到浏览器端,渲染给用户。html
传统的模式具备如下特色:前端
和传统应用相比较,单页面应用就是将MVC个架构搬到了前端来实现java
如此看来单页面应用很像移动客户端,后端的精力就是提供高质量的、可复用的Rest API服务。web
世间万物皆有裂痕,哪又怎样?裂痕,那是光照进来的地方。ajax
单页面应用的出现依然存在着争议性,咱们该如何看待他的两面性呢?接下来小生给你们总结一下他的优缺点。编程
单页面应用的优点:json
单页面应用的劣势:后端
随着SPA的流行,目前主流的框架都实现了SPA模式,包括咱们夏洛克产品里面用到的Angular和Vue。可是做为一家爱折腾公司里面爱折腾的前端团队里面爱折腾的人,咱们总想跟本身较劲来试试本身去实现简单的模式,此次小生也简单地实现了一把,因而将其分享于诸位,目前只是简单的模型,不能用于生产(主流框架都有,干吗用个人?学习一下思想便可),除非你愿意折腾。在此以前须要介绍几个核心点:
关于H5 History API在此须要介绍一下,他是HTML5引入的操做浏览器路由历史堆栈的内容,其中两个主要的方法为history.pushState(stateObj, title, URL) 和 history.replaceState(stateObj, title, URL) 方法,它们分别能够添加和修改历史记录条目。这些方法一般与window.onpopstate 配合使用。三个参数分别为:
小生结合window.onpopstate事件来监听浏览器前进和后退的动做来从新请求数据服务,更新视图。
每当处于激活状态的历史记录条目发生变化时, popstate事件就会在对应window对象上触发。 若是当前处于激活状态的历史记录条目是由history.pushState()方法建立, 或者由history.replaceState()方法修改过的, 则popstate事件对象的state属性包含了这个历史记录条目的state对象的一个拷贝。
调用history.pushState()或者history.replaceState()不会触发popstate事件. popstate事件只会在浏览器某些行为下触发, 好比点击后退、前进按钮(或者在JavaScript中调用history.back()、history.forward()、history.go()方法)。
-- data -- auto.json -- contact.json -- home.json -- platform.json -- sharplook.json -- ajax.js -- index.js -- index.html -- index.css
data文件下是模拟的后端数据,数据的结构都与下面同样,好比home.json
{ "content": "上海擎创信息技术有限公司是专业服务于企业级客户的ITOA智能运营大数据分析解决方案提供商,专一于将人工智能技术赋予IT运维管理,创造具有分析和思考能力的IT管理软件,让每家企业都拥有本身的IT运维专家。" }
ajax.js的代码以下:
function ajax() { const ajaxData = { type: arguments[0].type || 'GET', url: arguments[0].url || '', async: arguments[0].async || 'true', data: arguments[0].data || null, dataType: arguments[0].dataType || 'text', contentType: arguments[0].contentType || 'application/x-www-form-urlencoded', beforeSend: arguments[0].beforeSend || function () {}, success: arguments[0].success || function () {}, error: arguments[0].error || function () {} } ajaxData.beforeSend() const xhr = _createxmlHttpRequest(); xhr.responseType = ajaxData.dataType; xhr.open(ajaxData.type, ajaxData.url, ajaxData.async); xhr.setRequestHeader('Content-Type', ajaxData.contentType); xhr.send(_convertData(ajaxData.data)); xhr.onreadystatechange = function () { if (xhr.readyState == 4) { if (xhr.status == 200) { ajaxData.success(xhr.response); } else { ajaxData.error(); } } } } function _createxmlHttpRequest() { if (window.ActiveXObject) { return new ActiveXObject('Microsoft.XMLHTTP'); } else if (window.XMLHttpRequest) { return new XMLHttpRequest(); } } function _convertData(data) { if (typeof data === 'object') { let convertResult = ''; for (let c in data) { convertResult += `${c}=${data[c]}&`; } convertResult = convertResult.substring(0, convertResult.length - 1); return convertResult; } else { return data; } }
index.html的代码以下:
<!DOCTYPE html> <html> <meta charset="utf-8"> <head> <title>SPA</title> <link href="./index.css" rel="stylesheet" type="text/css" /> <script src="./ajax.js"></script> <script src="./index.js"></script> </head> <body> <main class="container"> <header class="bar"> <span class="title">SHARPLOOK 大数据运维监控平台</span> </header> <section class="body"> <ul id="nav"> <li><a href="/home">首页</a></li> <li><a href="/sharplook">夏洛克</a></li> <li><a href="/platform">自管理平台</a></li> <li><a href="/auto">自动化安装</a></li> <li><a href="/contact">联系咱们</a></li> </ul> <div id="content-main"> <div id="content"> <p id="p"></p> </div> </div> </section> </main> </body> <script type="text/javascript"> const spa = new SPA(); spa.init(); </script> </html>
index.js的代码以下:
class SPA { constructor () { this.elment = void 0; this.menu = Array.from(document.getElementsByTagName('a')); } getCurrentHash() { return window.history.state ? window.history.state.hash : '/home'; } isSupportH5History() { return !!(window.history && window.history.pushState); } setElement(hash) { if (!hash) { // 默认为根路由 ‘/’ this.elment = this.menu[0]; } else { this.menu.forEach(item => { if(item.getAttribute('href') === hash) { this.elment = item; } }); } } renderData() { const contentElement = document.getElementById('p'); this.loadData(contentElement, this.elment.getAttribute('href').split('/')[1]); } addHistory(hash, isReplace) { const stateObj = { hash }; if(isReplace) { window.history.replaceState(stateObj, null, hash); } else { window.history.pushState(stateObj, null, hash); } } loadData(contentElement, type) { ajax({ type: 'get', url: `/data/${type}.json`, dataType: 'json', success: function(msg) { console.log(msg); contentElement.innerText = msg.content; }, error: function() { console.log('error') } }) }; popStateHandler(linkHash, isPopState = false) { if(!linkHash) {// 刷新界面时候,默认获取刷新以前的路由信息 this.addHistory(this.getCurrentHash(), true); } else { if(!isPopState) this.addHistory(linkHash, false); } this.setElement(this.getCurrentHash()); this.renderData(); this.addActiveClass(); } bindLiClick() { const list = document.getElementsByTagName('li'); Array.from(list).forEach(item => { item.onclick = (event) => { const linkHash = item.childNodes[0].getAttribute('href'); this.popStateHandler(linkHash); } }); } addActiveClass() { this.menu.forEach(item => { item.parentNode.classList.remove('active'); }) this.elment.parentNode.classList.add('active'); } init() { if(!this.isSupportH5History()) throw new Error('对不起!不支持 H5 History API!'); this.bindLiClick(); window.onpopstate = (event) => { this.popStateHandler(event.state.hash, true); } // 首次默认首次进入页面 this.popStateHandler(); } }
index.css的代码以下:
* { margin: 0; padding: 0; } html, body { height: 100%; } .container { height: 100%; display: flex; flex-direction: column; } .bar { background-color: #213442; color: white; height: 60px; display: flex; align-items: center; font-size: 20px; } .body { display: flex; height: calc(100% - 60px); border-top: 1px solid #ccc; } #content { border: 1px solid #ccc; border-radius: 3px; padding: 10px; width: 600px; box-shadow: 5px 5px 15px 0 #bbb; } #nav { background-color: #213442; width: 120px; text-align: center; } a { color: white; text-decoration: none; pointer-events: none; } #content-main { flex: 1; display: flex; justify-content: center; align-items: center; } .title { padding-left: 20px; } li { margin: 10px 0; line-height: 30px; cursor: pointer; } li:hover { background-color: #00A5D5; } .active { background-color: #00A5D5; }
代码的具体逻辑不作过多介绍,特别须要注意的是请将代码部署到web服务器上查看效果,由于history api须要在同域里面才能使用,不然报错,爱学习的小伙伴请自行学习。 完整代码
小生给你们介绍了目前web开发的SPA模式,但愿诸君在使用主流框架时能进一步了解其原理,你我共勉。小生基于H5的History实现了一个简单的SPA模式,仅供学习之用,最后小生想说,身为后端转为前端的前端猿,感受前端的技术栈应是最有活力的,由于一旦你不想动了,就如温水里的青蛙,距离另外一个世界也就近了,祝君能像前端的发展势头同样,活力四射,不断进步。