前端路由的兩種實現原理

圖片描述

早期的路由都是後端實現的,直接根據 url 來 reload 頁面,頁面變得愈來愈複雜服務器端壓力變大,隨着 ajax 的出現,頁面實現非 reload 就能刷新數據,也給前端路由的出現奠基了基礎。咱們能夠經過記錄 url 來記錄 ajax 的變化,從而實現前端路由。html

本文主要講兩種主流方式實現前端路由。前端

History API

這裏不細說每個 API 的用法,你們能夠看 MDN 的文檔:https://developer.mozilla.org...html5

重點說其中的兩個新增的API history.pushStatehistory.replaceStategit

這兩個 API 都接收三個參數,分別是github

  • 狀態對象(state object) — 一個JavaScript對象,與用pushState()方法建立的新歷史記錄條目關聯。不管什麼時候用戶導航到新建立的狀態,popstate事件都會被觸發,而且事件對象的state屬性都包含歷史記錄條目的狀態對象的拷貝。
  • 標題(title) — FireFox瀏覽器目前會忽略該參數,雖然之後可能會用上。考慮到將來可能會對該方法進行修改,傳一個空字符串會比較安全。或者,你也能夠傳入一個簡短的標題,標明將要進入的狀態。
  • 地址(URL) — 新的歷史記錄條目的地址。瀏覽器不會在調用pushState()方法後加載該地址,但以後,可能會試圖加載,例如用戶重啓瀏覽器。新的URL不必定是絕對路徑;若是是相對路徑,它將以當前URL爲基準;傳入的URL與當前URL應該是同源的,不然,pushState()會拋出異常。該參數是可選的;不指定的話則爲文檔當前URL。

相同之處是兩個 API 都會操做瀏覽器的歷史記錄,而不會引發頁面的刷新。ajax

不一樣之處在於,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 時就會報錯。
Uncaught DOMException: Failed to execute 'pushState' on 'History': A history state object with URL 'https://baidu.com/?name=orange' cannot be created in a document with origin 'https://www.baidu.com' and URL 'https://www.baidu.com/?name=orange'.

回到上面例子中,每次改變 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 事件的方式,各瀏覽器實現也有差別,咱們能夠根據不一樣瀏覽器作兼容處理。

hash

咱們常常在 url 中看到 #,這個 # 有兩種狀況,一個是咱們所謂的錨點,好比典型的回到頂部按鈕原理、Github 上各個標題之間的跳轉等,路由裏的 # 不叫錨點,咱們稱之爲 hash,大型框架的路由系統大多都是哈希實現的。

一樣咱們須要一個根據監聽哈希變化觸發的事件 —— hashchange 事件

咱們用 window.location 處理哈希的改變時不會從新渲染頁面,而是看成新頁面加到歷史記錄中,這樣咱們跳轉頁面就能夠在 hashchange 事件中註冊 ajax 從而改變頁面內容。

這裏我在 codepen 上模擬了一下原理: http://codepen.io/orangexc/pe...

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+,更低版本兼容見上文!

這個連接的 demo 含有判斷方法:http://sandbox.runjs.cn/show/... 。同時給出 Github 倉庫地址: minrouter,推薦你們讀下源碼,僅僅 117 行,精闢!

若是在上面連接測試時你的 url 裏多了一個 #,說明你的瀏覽器該更新啦。

文章出自 orange 的 我的博客 http://orangexc.xyz/
相關文章
相關標籤/搜索