前端猿一天不學習就沒飯吃了,後端猿三天不學習仍舊有白米飯擺於桌前。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模式,僅供學習之用,最後小生想說,身爲後端轉爲前端的前端猿,感受前端的技術棧應是最有活力的,由於一旦你不想動了,就如溫水裏的青蛙,距離另外一個世界也就近了,祝君能像前端的發展勢頭同樣,活力四射,不斷進步。