天天看點

路由的兩種模式:hash模式和 history模式

為什麼要使用路由

現在的網絡應用程式越來越多的使用AJAX異步請求完成頁面的無縫重新整理,導緻浏覽器的URL不會發生任何變化而完成了請求,進而破換了使用者浏覽體驗。同時本次浏覽的頁面内容在使用者下次使用URL通路時将無法重新呈現,使用路由可以很好地解決這個問題。

單頁面應用利用了JavaScript動态變換網頁内容,避免了頁面重載;路由則提供了浏覽器位址變化,網頁内容也跟随變化,兩者結合起來則為我們提供了體驗良好的單頁面web應用。

前端路由實作方式

路由需要實作三個功能:

  1. 當浏覽器位址變化時,切換頁面;
  2. 點選浏覽器【後退】、【前進】按鈕,網頁内容跟随變化;
  3. 重新整理浏覽器,網頁加載目前路由對應内容;

在單頁面web網頁中, 單純的浏覽器位址改變, 網頁不會重載,如單純的hash網址改變網頁不會變化,是以我們的路由主要是通過監聽事件,并利用js實作動态改變網頁内容,有兩種實作方式:

  1. hash模式:監聽浏覽器位址hash值變化,執行相應的js切換網頁;
  2. history模式:利用history API實作url位址改變,網頁内容改變;
它們的差別最明顯的就是hash會在浏覽器位址後面增加#号,而history可以自定義位址。

hash模式

使用window.location.hash屬性及視窗的onhashchange事件,可以實作監聽浏覽器位址hash值變化,執行相應的js切換網頁。下面具體介紹幾個使用過程中必須了解的要點:

  • hash指的是位址中#号以及後面的字元,也稱為散列值。hash也稱作錨點,本身是用來做頁面跳轉定位的。如http://localhost/index.html#abc,這裡的#abc就是hash;
  • 散列值是不會随請求發送到伺服器端的,是以改變hash,不會重新加載頁面;
  • 監聽 window 的 hashchange 事件,當散列值改變時,可以通過 location.hash 來擷取和設定hash值;
  • location.hash值的變化會直接反應到浏覽器位址欄;

觸發hashchange事件的幾種情況

浏覽器位址欄散列值的變化(包括浏覽器的前進、後退)會觸發window.location.hash值的變化,進而觸發onhashchange事件;

當浏覽器位址欄中URL包含哈希如 www.baidu.com/#home,這時按下輸入,浏覽器發送www.baidu.com/,請求至伺服器,請求完畢之後設定散列值為#home,進而觸發onhashchange事件。

當隻改變浏覽器位址欄URL的哈希部分,這時按下回車,浏覽器不會發送任何請求至伺服器,這時發生的隻是設定散列值新修改的哈希值,并觸發onhashchange事件;

html中<a>标簽的屬性 href 可以設定為頁面的元素ID如 #top,當點選該連結時頁面跳轉至該id元素所在區域,同時浏覽器自動設定 window.location.hash 屬性,位址欄中的哈希值也會發生改變,并觸發onhashchange事件;

//設定 url 的 hash,會在目前url後加上'#abc'
window.location.hash='abc';
let hash = window.location.hash //'#abc'

window.addEventListener('hashchange',function(){
    //監聽hash變化,點選浏覽器的前進後退會觸發
})

           

history模式

概述

window.history 屬性指向 History 對象,它表示目前視窗的浏覽曆史。當發生改變時,隻會改變頁面的路徑,不會重新整理頁面。 History 對象儲存了目前視窗通路過的所有頁面網址。通過 history.length 可以得出目前視窗一共通路過幾個網址。 由于安全原因,浏覽器不允許腳本讀取這些位址,但是允許在位址之間導航。 浏覽器工具欄的“前進”和“後退”按鈕,其實就是對 History 對象進行操作。

屬性

History 對象主要有兩個屬性。

  • History.length:目前視窗通路過的網址數量(包括目前網頁)
  • History.state:History 堆棧最上層的狀态值(詳見下文)
// 目前視窗通路過多少個網頁
history.length // 1

// History 對象的目前狀态
// 通常是 undefined,即未設定

history.state // undefined
           

方法

History.back()、History.forward()、History.go(),這三個方法用于在曆史之中移動。

  • History.back():移動到上一個網址,等同于點選浏覽器的後退鍵。對于第一個通路的網址,該方法無效果。
  • History.forward():移動到下一個網址,等同于點選浏覽器的前進鍵。對于最後一個通路的網址,該方法無效果。
  • History.go():接受一個整數作為參數,以目前網址為基準,移動到參數指定的網址。如果參數超過實際存在的網址範圍,該方法無效果;如果不指定參數,預設參數為0,相當于重新整理目前頁面。
history.back();
history.forward();
history.go(1);//相當于history.forward()
history.go(-1);//相當于history.back()
history.go(0); // 重新整理目前頁面
           

注意:移動到以前通路過的頁面時,頁面通常是從浏覽器緩存之中加載,而不是重新要求伺服器發送新的網頁。

History.pushState()

該方法用于在曆史中添加一條記錄。pushState()方法不會觸發頁面重新整理,隻是導緻 History 對象發生變化,位址欄會有變化。

文法:history.pushState(object, title, url)

該方法接受三個參數,依次為:

  • object:是一個對象,通過 pushState 方法可以将該對象内容傳遞到新頁面中。如果不需要這個對象,此處可以填 null。
  • title:名額題,幾乎沒有浏覽器支援該參數,傳一個空字元串比較安全。
  • url:新的網址,必須與目前頁面處在同一個域。不指定的話則為目前的路徑,如果設定了一個跨域網址,則會報錯。
var data = { foo: 'bar' };
history.pushState(data, '', '2.html');
console.log(history.state) // {foo: "bar"}
           

注意:如果 pushState 的 URL 參數設定了一個新的錨點值(即 hash),并不會觸發 hashchange 事件。反過來,如果 URL 的錨點值變了,則會在 History 對象建立一條浏覽記錄。

如果 pushState() 方法設定了一個跨域網址,則會報錯。

// 報錯
// 目前網址為 http://example.com
history.pushState(null, '', 'https://twitter.com/hello');
           

上面代碼中,pushState 想要插入一個跨域的網址,導緻報錯。這樣設計的目的是,防止惡意代碼讓使用者以為他們是在另一個網站上,因為這個方法不會導緻頁面跳轉。

History.replaceState()

該方法用來修改 History 對象的目前記錄,用法與 pushState() 方法一樣。

假定目前網頁是 example.com/example.htm…

history.pushState({page: 1}, '', '?page=1')
// URL 顯示為 http://example.com/example.html?page=1

history.pushState({page: 2}, '', '?page=2');
// URL 顯示為 http://example.com/example.html?page=2

history.replaceState({page: 3}, '', '?page=3');
// URL 顯示為 http://example.com/example.html?page=3

history.back()
// URL 顯示為 http://example.com/example.html?page=1

history.back()
// URL 顯示為 http://example.com/example.html

history.go(2)
// URL 顯示為 http://example.com/example.html?page=3
           

popstate 事件

每當 history 對象出現變化時,就會觸發 popstate 事件。

注意:

  • 僅僅調用pushState()方法或replaceState()方法 ,并不會觸發該事件;
  • 隻有使用者點選浏覽器倒退按鈕和前進按鈕,或者使用 JavaScript 調用History.back()、 History.forward()、History.go()方法時才會觸發。
  • 另外,該事件隻針對同一個文檔,如果浏覽曆史的切換,導緻加載不同的文檔,該事件也不會觸發。
  • 頁面第一次加載的時候,浏覽器不會觸發popstate事件。

使用的時候,可以為popstate事件指定回調函數,回調函數的參數是一個 event 事件對象,它的 state 屬性指向目前的 state 對象。

window.addEventListener('popstate', function(e) {
    //e.state 相當于 history.state
    console.log('state: ' + JSON.stringify(e.state));
    console.log(history.state);
});
           

通過history.pushState 實作頁面 tab 切換的功能。

history 緻命的缺點就是當改變頁面位址後,強制重新整理浏覽器時,(如果後端沒有做準備的話)會報錯,因為重新整理是拿目前位址去請求伺服器的,如果伺服器中沒有相應的響應,會出現 404 頁面。

繼續閱讀