1. 程式人生 > >前端路由的兩種主流方法的實現原理

前端路由的兩種主流方法的實現原理

早期的路由都是後端實現的,根據url來reload頁面,隨著頁面越來越複雜給後端造成了巨大壓力,為了減輕後臺壓力以及前端使用者體驗,出現了局部重新整理檢視的方法ajax,同時給前端路由奠定了基礎。通過記錄url的變化來記錄ajax的變化(ajax的標籤化),從而實現前端路由。

1、H5新增的History API

重點是history.pushState( )和history.replaceState( )這兩個API

這兩個 API 都接收三個引數,分別是:
狀態物件(state object) — 一個JavaScript物件,與用pushState()方法建立的新歷史記錄條目關聯。無論何時使用者

導航到新建立的狀態,popstate事件都會被觸發,並且事件物件的state屬性都包含歷史記錄條目的狀態物件的拷貝。
標題(title) — FireFox瀏覽器目前會忽略該引數,雖然以後可能會用上。考慮到未來可能會對該方法進行修改,傳一個空字串會比較安全。或者,你也可以傳入一個簡短的標題,標明將要進入的狀態。
地址(URL)新的歷史記錄條目的地址。瀏覽器不會在呼叫pushState()方法後加載該地址,但之後,可能會試圖載入,例如使用者重啟瀏覽器。新的URL不一定是絕對路徑;如果是相對路徑,它將以當前URL為基準;傳入的URL與當前URL應該是同源的,否則,pushState()會丟擲異常。該引數是
可選的不指定的話則為文件當前URL
相同之處是兩個 API 都會操作瀏覽器的歷史記錄,而不會引起頁面的重新整理

不同之處在於,pushState會增加一條新的歷史記錄,而replaceState則會替換當前的歷史記錄。

我們拿大百度的控制檯舉例子(具體說是我的瀏覽器在百度首頁開啟控制檯。。。)

我們在控制檯輸入

window.history.pushState(nullnull"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+,更低版本相容見上文!