摘要:單頁面應(yīng)用的出現(xiàn)依然存在著爭議性,我們該如何看待他的兩面性呢接下來小生給大家總結(jié)一下他的優(yōu)缺點(diǎn)。單頁面應(yīng)用的優(yōu)勢無刷新體驗(yàn)沒有了令人詬病的頁面頻繁刷新,同時(shí)節(jié)約瀏覽器資源,路由響應(yīng)比較及時(shí),提升了用戶的體驗(yàn)。
認(rèn)識SPA前端猿一天不學(xué)習(xí)就沒飯吃了,后端猿三天不學(xué)習(xí)仍舊有白米飯擺于桌前。IT行業(yè)的快速發(fā)展一直在推動著前端技術(shù)棧在不斷地更新?lián)Q代,前端的發(fā)展成了互聯(lián)網(wǎng)時(shí)代的一個(gè)縮影。而單頁面應(yīng)用的發(fā)展給前端猿分了一杯羹。
最早單頁面的應(yīng)用無從知曉,在2004年,google的Gmail就使用了單頁面。到了2010年,隨著Backbone的問世之后,此概念才慢慢熱了起來。隨著后來React、Angular、Vue的興起,單頁面應(yīng)用才成了前端圈里人人皆知的架構(gòu)模式。接下來小生將通過對比傳統(tǒng)頁面應(yīng)用和單頁面應(yīng)用來說明SPA具體是什么。
傳統(tǒng)的頁面應(yīng)用早期web應(yīng)用的前后端交互模式是這樣的,每個(gè)html作為一個(gè)功能元件,通過刷新、超鏈接、表單提交等方式,將頁面組織起來后給用戶提供交互。
后期很多流行的框架都是基于此模式進(jìn)行設(shè)計(jì)的,比如 Ruby on Rails,Spring MVC,Express 等等
傳統(tǒng)的web應(yīng)用中,瀏覽器只是作為展示層,路由、服務(wù)調(diào)用、頁面跳轉(zhuǎn)都是服務(wù)端來處理的。也就是MVC的架構(gòu)都是放在后端的,只有V這一層,將頁面通過網(wǎng)絡(luò)發(fā)送到瀏覽器端,渲染給用戶。
傳統(tǒng)的模式具有以下特點(diǎn):
重服務(wù)端:瀏覽器只作為展示層,將MVC全置于后端,加重了服務(wù)端的體量,開發(fā)中主要以后端為主。
頻繁刷新:頁面展示依賴于不同的功能元件,所以必須依靠刷新頁面,或者跳轉(zhuǎn)路由來實(shí)現(xiàn)功能塊的切換,這種方式嚴(yán)重耗費(fèi)資源,同時(shí)用戶體驗(yàn)很差。
單頁面應(yīng)用和傳統(tǒng)應(yīng)用相比較,單頁面應(yīng)用就是將MVC個(gè)架構(gòu)搬到了前端來實(shí)現(xiàn)
控制器:將處理路由的功能放在前端,當(dāng)瀏覽器的路由發(fā)生變化時(shí),由控制器來響應(yīng)其變化,指向其對應(yīng)的處理邏輯(組件),最終將頁面展現(xiàn)給用戶。
視圖:這一層就是功能元件,也就是單個(gè)的組件,當(dāng)路由發(fā)生變化的時(shí)候由組件來處理,只處理變化的那部分,最后組織成頁面。
數(shù)據(jù)層:單頁面應(yīng)用有自己的數(shù)據(jù)層定義,簡化了后端服務(wù)的復(fù)雜度,后端只要提供公共的數(shù)據(jù)接口即可,而數(shù)據(jù)層會對數(shù)據(jù)服務(wù)API進(jìn)行進(jìn)一步的封裝,然后提供數(shù)據(jù)給視圖層。
如此看來單頁面應(yīng)用很像移動客戶端,后端的精力就是提供高質(zhì)量的、可復(fù)用的Rest API服務(wù)。
世間萬物皆有裂痕,哪又怎樣?裂痕,那是光照進(jìn)來的地方。
單頁面應(yīng)用的出現(xiàn)依然存在著爭議性,我們該如何看待他的兩面性呢?接下來小生給大家總結(jié)一下他的優(yōu)缺點(diǎn)。
單頁面應(yīng)用的優(yōu)勢:
無刷新體驗(yàn):沒有了令人詬病的頁面頻繁刷新,同時(shí)節(jié)約瀏覽器資源,路由響應(yīng)比較及時(shí),提升了用戶的體驗(yàn)。
共享組件:前端組件化是將獨(dú)立完整的功能模塊封裝到一個(gè)組件中,代碼結(jié)構(gòu)更加規(guī)范,便于代碼維護(hù),同時(shí)模塊化后的組件可以在不同的場景中進(jìn)行復(fù)用,極大地加快了迭代開發(fā)的速度。這也是為什么主流的前端框架都提倡組件化編程的原因。
共享API:給后端減負(fù),前端加碼的好處就是,前端能有一點(diǎn)口糧吃了(開玩笑,前端那么牛怎么能沒飯吃呢?),前端擔(dān)起家務(wù)的事,后端就可以安心地處理業(yè)務(wù)邏輯了,于是才能寫出高質(zhì)量并可共享的API,供自己或者其他的合作伙伴使用。一個(gè)優(yōu)秀的產(chǎn)品背后,一定有一群出色的前端(小生臉皮太厚)。
單頁面應(yīng)用的劣勢:
抬高了前端門檻:SPA模式的流行,引領(lǐng)了前端技術(shù)的飛速發(fā)展,與此同時(shí)對前端人員在學(xué)習(xí)和使用上的能力就有了更高的要求,同時(shí)工作量也增加了,前端想活的更好就要付出的更多,所以不要再以為前端就是切切圖,畫畫頁面這么簡單。too young, too naive。
首次加載大量資源:既然只有一個(gè)頁面顯示,那許多功能元件(組件)所依賴的靜態(tài)資源就需要在初次時(shí)進(jìn)行加載,加載時(shí)間相對比較長。
不利于SEO:單頁面應(yīng)用,數(shù)據(jù)都是在前端進(jìn)行渲染的,所以就影響了SEO。
徒手實(shí)現(xiàn)SPA隨著SPA的流行,目前主流的框架都實(shí)現(xiàn)了SPA模式,包括我們夏洛克產(chǎn)品里面用到的Angular和Vue。但是作為一家愛折騰公司里面愛折騰的前端團(tuán)隊(duì)里面愛折騰的人,我們總想跟自己較勁來試試自己去實(shí)現(xiàn)簡單的模式,這次小生也簡單地實(shí)現(xiàn)了一把,于是將其分享于諸位,目前只是簡單的模型,不能用于生產(chǎn)(主流框架都有,干嘛用我的?學(xué)習(xí)一下思想即可),除非你愿意折騰。在此之前需要介紹幾個(gè)核心點(diǎn):
路由:小生使用H5中的History API來管理路由的更新(地址欄URL更新、前進(jìn)、后退)。
視圖:小生還是使用原生的Document來操作,目前渲染的內(nèi)容比較簡單。
數(shù)據(jù)層:小生使用XMLHttpRequest寫了一個(gè)Ajax服務(wù),幫助請求后端數(shù)據(jù)(此服務(wù)較簡單,不適用生產(chǎn)環(huán)境)。
H5 History API關(guān)于H5 History API在此需要介紹一下,他是HTML5引入的操作瀏覽器路由歷史堆棧的內(nèi)容,其中兩個(gè)主要的方法為history.pushState(stateObj, title, URL) 和 history.replaceState(stateObj, title, URL) 方法,它們分別可以添加和修改歷史記錄條目。這些方法通常與window.onpopstate 配合使用。三個(gè)參數(shù)分別為:
狀態(tài)對象 — 狀態(tài)對象是一個(gè)JavaScript對象,通過pushState () 創(chuàng)建新的歷史記錄條目。無論什么時(shí)候用戶導(dǎo)航到新的狀態(tài),popstate事件就會被觸發(fā),且該事件的state屬性包含該歷史記錄條目狀態(tài)對象的副本。
title — Firefox 目前忽略這個(gè)參數(shù),但未來可能會用到。
URL — 該參數(shù)定義了新的歷史URL記錄。注意,調(diào)用 pushState() 后瀏覽器并不會立即加載這個(gè)URL,但可能會在稍后某些情況下加載這個(gè)URL,比如在用戶重新打開瀏覽器時(shí)。新URL不必須為絕對路徑。如果新URL是相對路徑,那么它將被作為相對于當(dāng)前URL處理。新URL必須與當(dāng)前URL同源,否則 pushState() 會拋出一個(gè)異常。該參數(shù)是可選的,缺省為當(dāng)前URL。
小生結(jié)合window.onpopstate事件來監(jiān)聽瀏覽器前進(jìn)和后退的動作來重新請求數(shù)據(jù)服務(wù),更新視圖。
每當(dāng)處于激活狀態(tài)的歷史記錄條目發(fā)生變化時(shí), popstate事件就會在對應(yīng)window對象上觸發(fā)。 如果當(dāng)前處于激活狀態(tài)的歷史記錄條目是由history.pushState()方法創(chuàng)建, 或者由history.replaceState()方法修改過的, 則popstate事件對象的state屬性包含了這個(gè)歷史記錄條目的state對象的一個(gè)拷貝。
調(diào)用history.pushState()或者h(yuǎn)istory.replaceState()不會觸發(fā)popstate事件. popstate事件只會在瀏覽器某些行為下觸發(fā), 比如點(diǎn)擊后退、前進(jìn)按鈕(或者在JavaScript中調(diào)用history.back()、history.forward()、history.go()方法)。
目錄結(jié)構(gòu)-- data -- auto.json -- contact.json -- home.json -- platform.json -- sharplook.json -- ajax.js -- index.js -- index.html -- index.css源碼分享
data文件下是模擬的后端數(shù)據(jù),數(shù)據(jù)的結(jié)構(gòu)都與下面一樣,比如home.json
{ "content": "上海擎創(chuàng)信息技術(shù)有限公司是專業(yè)服務(wù)于企業(yè)級客戶的ITOA智能運(yùn)營大數(shù)據(jù)分析解決方案提供商,專注于將人工智能技術(shù)賦予IT運(yùn)維管理,創(chuàng)造具備分析和思考能力的IT管理軟件,讓每家企業(yè)都擁有自己的IT運(yùn)維專家。" }
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的代碼如下:
SPA SHARPLOOK 大數(shù)據(jù)運(yùn)維監(jiān)控平臺
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) { // 默認(rèn)為根路由 ‘/’ 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) {// 刷新界面時(shí)候,默認(rèn)獲取刷新之前的路由信息 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); } // 首次默認(rèn)首次進(jìn)入頁面 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服務(wù)器上查看效果,因?yàn)閔istory api需要在同域里面才能使用,否則報(bào)錯(cuò),愛學(xué)習(xí)的小伙伴請自行學(xué)習(xí)。 完整代碼
效果圖 小結(jié)小生給大家介紹了目前web開發(fā)的SPA模式,希望諸君在使用主流框架時(shí)能進(jìn)一步了解其原理,你我共勉。小生基于H5的History實(shí)現(xiàn)了一個(gè)簡單的SPA模式,僅供學(xué)習(xí)之用,最后小生想說,身為后端轉(zhuǎn)為前端的前端猿,感覺前端的技術(shù)棧應(yīng)是最有活力的,因?yàn)橐坏┠悴幌雱恿耍腿鐪厮锏那嗤埽嚯x另一個(gè)世界也就近了,祝君能像前端的發(fā)展勢頭一樣,活力四射,不斷進(jìn)步。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/88620.html
摘要:單頁面應(yīng)用的出現(xiàn)依然存在著爭議性,我們該如何看待他的兩面性呢接下來小生給大家總結(jié)一下他的優(yōu)缺點(diǎn)。單頁面應(yīng)用的優(yōu)勢無刷新體驗(yàn)沒有了令人詬病的頁面頻繁刷新,同時(shí)節(jié)約瀏覽器資源,路由響應(yīng)比較及時(shí),提升了用戶的體驗(yàn)。 前端猿一天不學(xué)習(xí)就沒飯吃了,后端猿三天不學(xué)習(xí)仍舊有白米飯擺于桌前。IT行業(yè)的快速發(fā)展一直在推動著前端技術(shù)棧在不斷地更新?lián)Q代,前端的發(fā)展成了互聯(lián)網(wǎng)時(shí)代的一個(gè)縮影。而單頁面應(yīng)用的發(fā)展...
摘要:單頁面應(yīng)用的出現(xiàn)依然存在著爭議性,我們該如何看待他的兩面性呢接下來小生給大家總結(jié)一下他的優(yōu)缺點(diǎn)。單頁面應(yīng)用的優(yōu)勢無刷新體驗(yàn)沒有了令人詬病的頁面頻繁刷新,同時(shí)節(jié)約瀏覽器資源,路由響應(yīng)比較及時(shí),提升了用戶的體驗(yàn)。 前端猿一天不學(xué)習(xí)就沒飯吃了,后端猿三天不學(xué)習(xí)仍舊有白米飯擺于桌前。IT行業(yè)的快速發(fā)展一直在推動著前端技術(shù)棧在不斷地更新?lián)Q代,前端的發(fā)展成了互聯(lián)網(wǎng)時(shí)代的一個(gè)縮影。而單頁面應(yīng)用的發(fā)展...
摘要:解決方法如果使用頁面數(shù)據(jù)不超過一屏禁止?jié)L動,那么即使變成了頁面也不會有什么變化。 作者:@micky思 @wupq @yewq 在H5的開發(fā)中,個(gè)人的制作頁面布局習(xí)性不同,多多少少會產(chǎn)生在真機(jī)上input的光標(biāo)和鍵盤的彈出會出現(xiàn)的各種BUG,文中整理了部分遇到的問題,歡迎新增 ios移動端輸入框上浮導(dǎo)致輸入位置偏移 問題原因:遮罩層定位為fixed,當(dāng)鍵盤彈起時(shí),ios11以及以下...
摘要:解決方法如果使用頁面數(shù)據(jù)不超過一屏禁止?jié)L動,那么即使變成了頁面也不會有什么變化。 作者:@micky思 @wupq @yewq 在H5的開發(fā)中,個(gè)人的制作頁面布局習(xí)性不同,多多少少會產(chǎn)生在真機(jī)上input的光標(biāo)和鍵盤的彈出會出現(xiàn)的各種BUG,文中整理了部分遇到的問題,歡迎新增 ios移動端輸入框上浮導(dǎo)致輸入位置偏移 問題原因:遮罩層定位為fixed,當(dāng)鍵盤彈起時(shí),ios11以及以下...
閱讀 1654·2019-08-30 13:04
閱讀 2205·2019-08-30 12:59
閱讀 1764·2019-08-29 18:34
閱讀 1857·2019-08-29 17:31
閱讀 1255·2019-08-29 15:42
閱讀 3530·2019-08-29 15:37
閱讀 2857·2019-08-29 13:45
閱讀 2771·2019-08-26 13:57