天天看點

前端路由的前世今生

初識路由

  首先要了解的一點就是路由是由多個URL組成的,使用不同的URL可以相應的導航到不同的位置。在傳統的多頁面應用中,網站的每一個 URL 位址都是對應于伺服器磁盤上的一個實際實體檔案。例如,當我們通路 https://www.yousite.com/index.html 這個網址的時候,伺服器會自動把我們的請求對應到目前站點路徑下面的 index.html 檔案,然後再給予響應,将這個檔案傳回給浏覽器。當我們跳轉到别的頁面上時,毫無疑問則會再重複一遍上面的過程。因為浏覽器對頁面的通路是

無狀态

的,是以我們在切換不同的頁面時都會重新進行請求。

  而實際使用vue和vue-router開發就會明白,在切換頁面時是沒有重新進行請求的,使用起來就好像頁面是有狀态的,這是什麼原因呢?因為在

單頁面應用

中,整個項目中

隻會存在一個 html 檔案

,當使用者切換頁面時,隻是通過對這個唯一的 html 檔案進行

動态重寫

,進而達到響應使用者的請求。也就是說,從切換頁面這個角度上說,應用隻是在第一次打開時請求了伺服器(非服務端渲染的單頁應用)。因為通路的頁面是并不真實存在的,是以如何正确的在一個 html 檔案中展現出使用者想要通路的資訊就成為單頁面應用需要考慮的問題,而對于這一路由問題的解決方案,為了與我們後端傳統意義上的路由進行差別,就将此稱為

前端路由——其核心是改變視圖的同時不會向後端送出請求

什麼是單頁面應用(SPA)

  首先我們需要了解一下——

SPA(單頁面應用)

。網際網路是一步一步發展到今天的,用一個普通使用者能夠覺察到的衡量因素——上網速度。由一開始的撥号上網,到寬帶上網,再到如今的百兆光纖等等。而對于

web應用

,也朝着内容更加豐富,上網更加便捷,浏覽更加快速的方向發展,

傳統的一個頁面映射一個.html,.css,.js的模式,也逐漸有了自己的弊端

,那就是切換頁面需要從伺服器下載下傳對應的html、css和js檔案,而這些檔案大小并不算小,這就造成了頁面加載速度的緩慢,進而影響了使用者體驗。 在這個快節奏的時代,大家都在追求更快,更順滑。是以,一個新的模式出來了,那就是

元件化

。元件化模式縮小了檔案的顆粒度,

把不同的邏輯視圖塊分割為小的獨立的元件,将不同頁面視為元件的不同排列組合,html作為包裹這件組合後的元件的容器。整個頁面,隻有一個html

。當頁面切換時,url 發生改變,隻是表明頁面元件排列組合的方式變了,并不需要向後端發送請求重載頁面,這樣的應用也就叫做

單頁面應用,因為隻有一個html檔案

前端路由造成的沖突

  前端路由是不同的url對應不同的元件排列組合方式,但是

浏覽器并不知道運作的是SPA,他還預設為是傳統的web應用——不同頁面對應不同html

。是以當你的url改變時,浏覽器依然會向新的url發起請求,但這是我們不希望浏覽器做的。我們想要的是改變路由的同時不會向後端送出請求(

前端路由

)。

路由模式

實際上存在三種模式:

       Hash: 使用URL的hash值來作為路由。支援所有浏覽器。

       History: 以來HTML5 History API 和伺服器配置。參考官網中HTML5 History模式。

       Abstract: 支援所有javascript運作模式。如果發現沒有浏覽器的API,路由會自動強制進入這個模式。

vue-router中預設使用的是hash模式,也就是會出現URL中帶有#号,比如:

前端路由的前世今生

我們可以用如下代碼修改成

history模式

前端路由的前世今生

添加一個

mode屬性

就能将模式轉換。

hash模式

  1. hash —— 即位址欄 URL 的

    #

    符号(此 hash 不是密碼學裡的散列運算)。
    • #是什麼?

      # 俗稱錨點,做頁面定位。其右邊的字元,就是網頁中該位置的辨別符。比如,

      http://www.example.com/index.html/#/print

      就代表網頁 index.html 的 print 位置。浏覽器讀取這個 url 後,

      會自動将 print 位置滾動至可視區域。

    • 如何設定#?

      為網頁位置指定辨別符,有兩個方法。一是使用錨點,比如<a name="print"></a>,二是使用id屬性,

      比如

      <div id="print">

    • #的特點

      比如這個 URL:

      http://www.jianshu.com/#/article

      ,hash 的值為

      #/article

      。它的特點在于:hash 雖然出現在 URL 中,但不會被包括在 HTTP 請求中,對後端完全沒有影響,是以改變 hash 不會重新加載頁面。
    • 如何讀取#?

      使用 window.location.hash 讀取 # 值。這個屬性可讀可寫。讀取時,可以用來判斷網頁狀态是否改變;

      寫入時,則會在不重載網頁的前提下,創造一條通路曆史記錄。

      每一次改變#後的部分,都會在浏覽器的通路曆史中增加一個記錄,使用"後退"按鈕,就可以回到上一個位

      置。這對于ajax應用程式特别有用,可以用不同的#值,表示不同的通路狀态,然後向使用者給出可以通路某

      個狀态的連結。值得注意的是,上述規則對IE 6和IE 7不成立,它們不會因為#的改變而增加曆史記錄。

hash模式背後的原理是onhashchange事件,可以在window對象上監聽這個事件:

window.onhashchange = function(event){
    console.log(event.oldURL, event.newURL);
    let hash = location.hash.slice(1);
    document.body.style.color = hash;
}
           

上面的代碼可以通過改變hash來改變頁面字型顔色,雖然沒什麼用,但是一定程度上說明了原理。

更關鍵的一點是,因為hash發生變化的url都會被浏覽器記錄下來,進而你會發現浏覽器的前進後退都可以用了,同時點選後退時,頁面字型顔色也會發生變化。這樣一來,盡管浏覽器沒有請求伺服器,但是頁面狀态和url一一關聯起來,後來人們給它起了一個霸氣的名字叫**前端路由,成為了單頁應用标配**。

我們寫個簡單的方法來測試一下:

<!DOCTYPE html>
<html >
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <div>測試一下</div>
    <script type="text/javascript">
        window.onhashchange = function(event){
            console.log(event.oldURL,event.newURL)
            let hash = location.hash.slice(1);
            document.body.style.color = hash;
        }
    </script>
</body>
</html>
           
前端路由的前世今生

并且通過浏覽器的前進、後退頁面均可以變化。

網易雲音樂,百度網盤就采用了hash路由,看起來就是這個樣子:

http://music.163.com/#/friend

https://pan.baidu.com/disk/home#list/vmode=list

history模式

随着history api的到來,前端路由開始進化了,前面的hashchange,隻能改變#後面的url片段,而history api則給了前端完全的自由。

history —— 利用了 HTML5 History Interface 中新增的

pushState()

popState()

方法。(需要特定浏覽器支援這兩個方法應用于浏覽器的曆史記錄棧,在目前已有的

back

forward

go

的基礎之上,它們提供了對曆史記錄進行修改的功能。隻是當它們執行修改時,雖然改變了目前的 URL,但浏覽器不會立即向後端發送請求。

history api可以分為兩大部分:切換和修改

切換曆史狀态

包括back、forward、go三個方法,對應浏覽器的前進,後退,跳轉操作:

history.go(-2);//後退兩次
history.go(2);//前進兩次
history.back(); //後退
hsitory.forward(); //前進
           

修改曆史狀态

包括了pushState、replaceState兩個方法,這兩個方法接收三個參數:stateObj,title,url

history.pushState({color:'red'}, 'red', 'red')

window.onpopstate = function(event){
    console.log(event.state)
    if(event.state && event.state.color === 'red'){
        document.body.style.color = 'red';
    }
}

history.back();

history.forward();
           

通過pushstate把頁面的狀态儲存在state對象中,當頁面的url再變回這個url時,可以通過event.state取到這個state對象,進而可以對頁面狀态進行還原,這裡的頁面狀态就是頁面字型顔色,其實滾動條的位置,閱讀進度,元件的開關的這些頁面狀态都可以存儲到state的裡面。

通過history api,我們丢掉了醜陋的#,但是它也有個毛病:

不怕前進,不怕後退,就怕重新整理f5(如果後端沒有準備的話),因為重新整理是實實在在地去請求伺服器的。

  在hash模式下,前端路由修改的是**#中的資訊**,而浏覽器請求時是不會帶上#後的内容,是以沒有問題。但是在history下,你可以自由的修改path,當重新整理時,如果伺服器中沒有相應的響應或者資源,會分分鐘刷出一個404來。

hash 和 history的使用場景

一般場景下,hash 和 history 都可以,除非你更在意顔值,

#

符号夾雜在 URL 裡看起來确實有些醜陋。

如果不想要很醜的 hash,我們可以用路由的 history 模式,這種模式充分利用 history.pushState API 來完成

URL 跳轉而無須重新加載頁面。—— Vue-router 官網。

另外,根據 Mozilla Develop Network 的介紹,調用

history.pushState()

相比于直接修改

hash

,存在以下優勢:

  • pushState()

    設定的新 URL 可以是與目前 URL 同源的任意 URL;而

    hash

    隻可修改

    #

    後面的部分,是以隻能設定與目前 URL 同文檔的 URL;
  • pushState()

    設定的新 URL 可以與目前 URL 一模一樣,這樣也會把記錄添加到棧中;而

    hash

    設定的新值必須與原來不一樣才會觸發動作将記錄添加到棧中;
  • pushState()

    通過

    stateObject

    參數可以添加任意類型的資料到記錄中;而

    hash

    隻可添加短字元串;
  • pushState()

    可額外設定

    title

    屬性供後續使用。

當然啦,

history

也不是樣樣都好。SPA 雖然在浏覽器裡遊刃有餘,但真要通過 URL 向後端發起 HTTP 請求時,兩者的差異就來了。尤其在使用者手動輸入 URL 後回車,或者重新整理(重新開機)浏覽器的時候。

  1. hash

    模式下,僅

    hash

    符号之前的内容會被包含在請求中,如

    http://www.abc.com

    ,是以對于後端來說,即使沒有做到對路由的全覆寫,也不會傳回 404 錯誤。
  2. history

    模式下,前端的 URL 必須和實際向後端發起請求的 URL 一緻,如

    http://www.abc.com/book/id

    。如果後端缺少對

    /book/id

    的路由處理,将傳回 404 錯誤。Vue-Router 官網裡如此描述:“不過這種模式要玩好,還需要背景配置支援……是以呢,你要在服務端增加一個覆寫所有情況的候選資源:如果 URL 比對不到任何靜态資源,則應該傳回同一個 index.html 頁面,這個頁面就是你 app 依賴的頁面。”

路由模式的來龍去脈就這樣七七八八的差不多辣 §( ̄▽ ̄)§ §( ̄▽ ̄)§