前端路由的兩種主流方法的實現原理
早期的路由都是後端實現的,根據url來reload頁面,隨著頁面越來越複雜給後端造成了巨大壓力,為了減輕後臺壓力以及前端使用者體驗,出現了局部重新整理檢視的方法ajax,同時給前端路由奠定了基礎。通過記錄url的變化來記錄ajax的變化(ajax的標籤化),從而實現前端路由。
1、H5新增的History API
重點是history.pushState( )和history.replaceState( )這兩個API
這兩個 API 都接收三個引數,分別是:
狀態物件(state object) — 一個JavaScript物件,與用pushState()方法建立的新歷史記錄條目關聯。無論何時使用者
標題(title) — FireFox瀏覽器目前會忽略該引數,雖然以後可能會用上。考慮到未來可能會對該方法進行修改,傳一個空字串會比較安全。或者,你也可以傳入一個簡短的標題,標明將要進入的狀態。
地址(URL) — 新的歷史記錄條目的地址。瀏覽器不會在呼叫pushState()方法後加載該地址,但之後,可能會試圖載入,例如使用者重啟瀏覽器。新的URL不一定是絕對路徑;如果是相對路徑,它將以當前URL為基準;傳入的URL與當前URL應該是同源的,否則,pushState()會丟擲異常。該引數是
相同之處是兩個 API 都會操作瀏覽器的歷史記錄,而不會引起頁面的重新整理。
不同之處在於,pushState會增加一條新的歷史記錄,而replaceState則會替換當前的歷史記錄。
我們拿大百度的控制檯舉例子(具體說是我的瀏覽器在百度首頁開啟控制檯。。。)
我們在控制檯輸入
window.history.pushState(null, null, "https://www.baidu.com/?name=orange");
好,我們觀察此時的 url 變成了這樣
我們這裡不一一測試,直接給出其它用法,大家自行嘗試
window.history.pushState(null , null, "https://www.baidu.com/name/orange");
//url: https://www.baidu.com/name/orange
window.history.pushState(null, null, "?name=orange");
//url: https://www.baidu.com?name=orange
window.history.pushState(null, null, "name=orange");
//url: https://www.baidu.com/name=orange
window.history.pushState(null, null, "/name/orange");
//url: https://www.baidu.com/name/orange
window.history.pushState(null, null, "name/orange");
//url: https://www.baidu.com/name/orange
注意:這裡的 url 不支援跨域,當我們把 www.baidu.com 換成 baidu.com 時就會報錯。
回到上面例子中,每次改變 url 頁面並沒有重新整理,根據上文所述瀏覽器會產生歷史記錄
這就是實現頁面無重新整理情況下改變 url 的前提,下面我們說下第一個引數 狀態物件。如果執行 history.pushState()
方法,歷史棧對應的紀錄就會存入 狀態物件,我們可以隨時主動呼叫歷史條目。此處引用 mozilla 的例子
<!DOCTYPE HTML>
<!-- this starts off as http://example.com/line?x=5 -->
<title>Line Game - 5</title>
<p>You are at coordinate <span id="coord">5</span> on the line.</p>
<p>
<a href="?x=6" onclick="go(1); return false;">Advance to 6</a> or
<a href="?x=4" onclick="go(-1); return false;">retreat to 4</a>?
</p>
<script>
var currentPage = 5; // prefilled by server!!!!
function go(d) {
setupPage(currentPage + d);
history.pushState(currentPage, document.title, '?x=' + currentPage);
}
onpopstate = function(event) {
setupPage(event.state);
}
function setupPage(page) {
currentPage = page;
document.title = 'Line Game - ' + currentPage;
document.getElementById('coord').textContent = currentPage;
document.links[0].href = '?x=' + (currentPage+1);
document.links[0].textContent = 'Advance to ' + (currentPage+1);
document.links[1].href = '?x=' + (currentPage-1);
document.links[1].textContent = 'retreat to ' + (currentPage-1);
}
</script>
我們點選 Advance to ?
對應的 url 與模版都會 +1,反之點選 retreat to ?
就會都 -1,這就滿足了 url 與模版檢視同時變化的需求
實際當中我們不需要去模擬 onpopstate 事件,官方文件提供了 popstate 事件,當我們在歷史記錄中切換時就會產生 popstate 事件。對於觸發 popstate 事件的方式,各瀏覽器實現也有差異,我們可以根據不同瀏覽器做相容處理。
2、hash
我們經常在 url 中看到 #,這個 # 有兩種情況,一個是我們所謂的錨點,比如典型的回到頂部按鈕原理、Github 上各個標題之間的跳轉等,路由裡的 # 不叫錨點,我們稱之為 hash,大型框架的路由系統大多都是雜湊實現的。
同樣我們需要一個根據監聽雜湊變化觸發的事件 —— hashchange 事件
我們用 window.location
處理雜湊的改變時不會重新渲染頁面,而是當作新頁面加到歷史記錄中,這樣我們跳轉頁面就可以在 hashchange 事件中註冊 ajax 從而改變頁面內容。
hashchange 在低版本 IE 需要通過輪詢監聽 url 變化來實現,我們可以模擬如下:
(function(window) {
// 如果瀏覽器不支援原生實現的事件,則開始模擬,否則退出。
if ( "onhashchange" in window.document.body ) { return; }
var location = window.location,
oldURL = location.href,
oldHash = location.hash;
// 每隔100ms檢查hash是否發生變化
setInterval(function() {
var newURL = location.href,
newHash = location.hash;
// hash發生變化且全域性註冊有onhashchange方法(這個名字是為了和模擬的事件名保持統一);
if ( newHash != oldHash && typeof window.onhashchange === "function" ) {
// 執行方法
window.onhashchange({
type: "hashchange",
oldURL: oldURL,
newURL: newURL
});
oldURL = newURL;
oldHash = newHash;
}
}, 100);
})(window);
大型框架的路由當然不會這麼簡單,angular 1.x 的路由對雜湊、模版、處理器進行關聯,大致如下:
app.config(['$routeProvider', '$locationProvider', function ($routeProvider, $locationProvider) {
$routeProvider
.when('/article', {
templateUrl: '/article.html',
controller: 'ArticleController'
}).otherwise({
redirectTo: '/index'
});
$locationProvider.html5Mode(true);
}])
這套路由方案預設是以 # 開頭的雜湊方式,如果不考慮低版本瀏覽器,就可以直接呼叫 $locationProvider.html5Mode(true)
利用 H5 的方案而不用雜湊方案。
兩種方案推薦 hash 方案,因為照顧到低階瀏覽器,就是不美觀(多了一個 #),兩者兼顧也不是不可,只能判斷瀏覽器給出對應方案啦,不過也只支援 IE8+,更低版本相容見上文!