1. 從前的請(qǐng)求 —— 服務(wù)端渲染(SSR:Server Side Render)
在最開始施禾,在瀏覽器中向服務(wù)端發(fā)送一個(gè)請(qǐng)求召锈,服務(wù)端會(huì)把 html 字符串生成好,再發(fā)送給客戶端识颊,客戶端直接渲染即可。
并不是說(shuō)一個(gè) html 就只請(qǐng)求了一次服務(wù)端。一個(gè) html 里可能會(huì)引用一些 js、css 文件,這些靜態(tài)資源都是放在服務(wù)端的。當(dāng)瀏覽器渲染服務(wù)端返回的 html 字符串小作,讀到諸如
<script src="js/jquery.js"></script>
時(shí)會(huì)再次向服務(wù)端發(fā)送一條請(qǐng)求去獲取 jquery.js 資源的,獲取回來(lái)之后在瀏覽器中加載一遍稼钩。
缺點(diǎn):在后管系統(tǒng)中顾稀,當(dāng)從“用戶管理”菜單跳轉(zhuǎn)到“角色管理”菜單時(shí),往往變化的只是表格的字段以及數(shù)據(jù)坝撑,但在服務(wù)端渲染中每次都需要全部請(qǐng)求一遍静秆。請(qǐng)求頭部橫條,請(qǐng)求左側(cè)菜單欄巡李,重新加載 jquery.js 等等抚笔。我們希望不需要改變的東東就別動(dòng)了,修改變局部侨拦。
2. Ajax 出現(xiàn)實(shí)現(xiàn)局部刷新
ajax 的出現(xiàn)解決了上面每次請(qǐng)求頁(yè)面都全部刷新的問(wèn)題殊橙,只是請(qǐng)求數(shù)據(jù),然后通過(guò) Dom 操作修改 html 中表格相關(guān)的東東狱从。
缺點(diǎn):使用 ajax 異步請(qǐng)求之后膨蛮,點(diǎn)擊瀏覽器的回退上一個(gè)頁(yè)面會(huì)發(fā)現(xiàn)沒效果,因?yàn)檫@次請(qǐng)求僅僅是數(shù)據(jù)的請(qǐng)求季研,并沒有重新刷新頁(yè)面敞葛,也就沒有加入到瀏覽器歷史棧中。
3. 單頁(yè)應(yīng)用(SPA:Single Pagination Application)
單頁(yè)應(yīng)用的出現(xiàn)解決了服務(wù)端渲染和 ajax 存在的問(wèn)題训貌。
解決服務(wù)端渲染的問(wèn)題:
人們開始想,從“用戶管理”菜單跳轉(zhuǎn)到“角色管理”菜單時(shí),需要請(qǐng)求服務(wù)端獲取 html 之后在瀏覽器中渲染递沪。這個(gè)渲染的過(guò)程包括創(chuàng)建一個(gè)個(gè) dom 元素豺鼻,如 div dom 元素,p dom 元素款慨。他們會(huì)認(rèn)為這一過(guò)程很消耗性能儒飒,事實(shí)也是如此,可以試著在瀏覽器調(diào)試臺(tái)中輸入:document.createElement('div') 會(huì)發(fā)現(xiàn)僅僅創(chuàng)建一個(gè) div 元素附帶創(chuàng)建了很多屬性和方法檩奠,那些東東你可能壓根就用不上桩了。
單頁(yè)應(yīng)用的解決方式是,在頁(yè)面第一次加載的時(shí)候就把“用戶管理”的表格 dom 元素和“角色管理”的表格 dom 元素都加載進(jìn)來(lái)埠戳,當(dāng)再次從“用戶管理”切換到“角色管理”時(shí)井誉,只需要渲染
“角色管理” 的表格 dom,而不是像服務(wù)端渲染那樣將整個(gè)頁(yè)面都渲染一遍整胃,渲染的少了颗圣,性能上無(wú)疑得到了提升。這一點(diǎn)和 ajax 有點(diǎn)相似屁使。
單頁(yè)應(yīng)用與 ajax 的區(qū)別:
ajax 是先請(qǐng)求數(shù)據(jù)在岂,然后在瀏覽器通過(guò) js 創(chuàng)建 dom 進(jìn)行替換。單頁(yè)應(yīng)用在第一次加載應(yīng)用的時(shí)候?qū)?dom 都加載到瀏覽器中蛮寂,只不過(guò)先用了“用戶管理”表格 dom蔽午,“角色管理”表格 dom 也加載進(jìn)來(lái)了,放在那暫時(shí)不用酬蹋。這種通過(guò)點(diǎn)擊不同菜單及老,自動(dòng)替換不同的 dom 的方式就叫做路由。
還有一點(diǎn)區(qū)別就是單頁(yè)應(yīng)用解決了 ajax 的問(wèn)題:?jiǎn)雾?yè)應(yīng)用通過(guò) hashHistory 和 browerHistory 可以將每次的路由都加入到瀏覽器的歷史棧除嘹,這樣點(diǎn)擊回退按鈕就可以回到上一個(gè)頁(yè)面写半。
說(shuō)起來(lái) ajax 也可以通過(guò) hashHistory 和 browerHistory 將路由添加到歷史棧,只不過(guò)歷史大環(huán)境下決定它還是功能專一點(diǎn)尉咕。
當(dāng)然叠蝇,單頁(yè)應(yīng)用也有個(gè)大問(wèn)題,那就是首次加載時(shí)間很長(zhǎng)年缎,造成頁(yè)面出現(xiàn)白屏悔捶。單頁(yè)應(yīng)用第一次加載時(shí)就會(huì)取請(qǐng)求服務(wù)端獲取所有 html 和 js,然后接下來(lái)的操作都在瀏覽器端進(jìn)行了单芜,
瀏覽器會(huì)去渲染這些文件蜕该,但由于東東太多了,在瀏覽器端會(huì)出現(xiàn)短暫白屏的現(xiàn)象洲鸠。
為什么服務(wù)端渲染不會(huì)有白屏現(xiàn)象:因?yàn)榉?wù)端只會(huì)返回 html字符串堂淡,瀏覽器只要渲染一下即可馋缅,渲染的東西少了,自然就快了绢淀。
4. 路由的概念
去銀行辦理業(yè)務(wù)萤悴,銀行小姐姐會(huì)詢問(wèn)你辦理什么業(yè)務(wù),然后教你不同的操作皆的。有一張表:
業(yè)務(wù) | 操作 |
---|---|
辦理銀行卡 | 先做xx覆履,再做xx |
辦理網(wǎng)上銀行業(yè)務(wù) | 先做yy,再做yy |
去上廁所费薄,如果你是女生硝全,就進(jìn)入女廁所,你是男生楞抡,就進(jìn)入男廁所伟众。有一張表:
性別 | 廁所 |
---|---|
男生 | 男廁所 |
女生 | 女廁所 |
你去你女朋友宿舍樓下找她,你告訴宿管大媽你女朋友是xx專業(yè)xx班的學(xué)生拌倍,宿管大媽告訴你去幾樓幾零幾赂鲤。有一張表:
姓名 | 宿舍 |
---|---|
張曉麗 | 503 宿舍 |
李美麗 | 301 宿舍 |
回到瀏覽器路由,在瀏覽器中輸入“l(fā)ocalost:8000/”會(huì)顯示首頁(yè)柱恤,輸入“l(fā)ocalost:8000/user”會(huì)顯示用戶頁(yè)面数初,輸入“l(fā)ocalost:8000/role”會(huì)顯示角色頁(yè)面。有一張表:
路由 | 視圖 |
---|---|
/ | 首頁(yè) |
/user | 用戶頁(yè)面 |
/role | 角色頁(yè)面 |
在瀏覽器中路由的作用就是根據(jù)你的輸入的地址梗顺,顯示不同的視圖泡孩。那就意味著首先要定義這樣一張路由表。
5. 路由/單頁(yè)應(yīng)用的實(shí)現(xiàn)原理
了解了路由表寺谤,好奇寶寶肯定想知道具體如何實(shí)現(xiàn)呢仑鸥?
單頁(yè)應(yīng)用路由的實(shí)現(xiàn)分為兩個(gè)步驟:改變?yōu)g覽器地址欄地址,視圖切換变屁。
1)HashHistory 的應(yīng)用
- 改變?yōu)g覽器地址欄地址
window.location.hash = 'bbbb' 瀏覽器地址會(huì)變成 localhost:8080/#bbbb
window.location 對(duì)象專門處理瀏覽器地址欄各種操作眼俊。
使用 window.location.hash 操作時(shí),已經(jīng)將路由 '#bbbb' 添加到了瀏覽器歷史棧中粟关,點(diǎn)擊回退按鈕可以回退到上一個(gè)頁(yè)面疮胖。 - 添加瀏覽器事件監(jiān)聽函數(shù)
window.addEventListener('hashchange', function () {})
此時(shí)如果 hash 變化了,就會(huì)觸發(fā)該事件闷板,在這個(gè)事件里就可以實(shí)現(xiàn)替換局部 dom 的實(shí)現(xiàn)了澎灸。
2)Browser History (h5 history)的出現(xiàn)
使用 HashHistory 產(chǎn)生的路由 localhost:8080/#bbbb 會(huì)有個(gè)井號(hào) #,很多人認(rèn)為這很不美觀遮晚,但一直也無(wú)計(jì)可施性昭,直到 h5 的出現(xiàn),帶來(lái)了新的 API —— window.history 的更新县遣。
- 改變?yōu)g覽器的地址欄地址
var stateObj = { foo: "bar" };
history.pushState(stateObj, "page 2", "user");
可以看到路由變成了 localhost:8000/user糜颠,沒有了那不美觀的 #汹族。關(guān)于history api 具體用法,參考:http://blog.csdn.net/tianyitianyi1/article/details/user - 添加瀏覽器事件監(jiān)聽函數(shù)
通過(guò) history api 改變?yōu)g覽器地址欄地址同樣會(huì)觸發(fā)一個(gè)事件
window.onpopstate = function () { }
3)手寫 js 實(shí)現(xiàn)一個(gè)簡(jiǎn)單的路由功能:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Hash</title>
</head>
<body>
<ul class="cs">
<li class="li">
<a href="#/" class="button button-primary">首頁(yè)</a>
</li>
<li class="li">
<a href="#/nav" class="button button-highlight">菜單</a>
</li>
<li class="li">
<a href="#/subpage" class="button button-royal">子頁(yè)</a>
</li>
</ul>
<div id="app" style="border:2px solid #f00;height:30px;">
Hello
</div>
<script>
function Router(){
//有個(gè)名字其兴,用做一件事情
this.routes = [];
this.currentUrl = "";
this.init();//先讓他初始化監(jiān)聽事件鞠抑,然后才調(diào)用路由
}
Router.prototype = {
constructor:Router,
router:function(path,callback){
this.routes[path] = callback || function(){};
},
refresh:function(){
this.currentUrl = location.hash.slice(1) || '/';
this.routes[this.currentUrl]();//通過(guò)路由名字去調(diào)用相對(duì)應(yīng)的方法
},
init:function(){//監(jiān)聽頁(yè)面:加載的時(shí)候、監(jiān)聽改變的時(shí)候
window.addEventListener("load",this.refresh.bind(this),false);
window.addEventListener("hashchange",this.refresh.bind(this),false);
}
}
var appObj = document.getElementById("app");
function changeText(text){
appObj.innerHTML = text;
}
var router = new Router();
router.router("/",function(){
changeText("首頁(yè)");
});
router.router("/nav",function(){
changeText("菜單");
});
router.router("/subpage",function(){
changeText("子頁(yè)");
});
</script>
</body>
</html>
6. 最終
目前由于單頁(yè)應(yīng)用操作起來(lái)更快速忌警,除了首屏加載尷尬的問(wèn)題,人們提出的方案是使用服務(wù)端渲染首屏秒梳,之后其它操作都進(jìn)行單頁(yè)應(yīng)用的操作來(lái)規(guī)避首屏加載的問(wèn)題法绵。
最終,當(dāng)硬件與軟件發(fā)展到一定程度酪碘,網(wǎng)速加速到一定程度朋譬,單頁(yè)應(yīng)用首屏加載雖然需要請(qǐng)求很多東東,但由于網(wǎng)速的變快一瞬間就加載下來(lái)兴垦,服務(wù)端渲染可能就會(huì)慢慢退出歷史舞臺(tái)徙赢,這也是一種技術(shù)的發(fā)展趨勢(shì)。