深度剖析:前端路由原理

前言

前端三大框架 AngularReactVue ,它們的路由解決方案 angular/routerreact-routervue-router 都是基於前端路由原理進行封裝實現的,所以將前端路由原理進行了解和掌握是頗有必要的,由於咱們再使用的過程當中也不免會遇到一些坑,一旦咱們掌握了它的實現原理,那麼就能在開發中對路由的使用更加遊刃有餘。javascript

1、什麼是路由?

​ 路由的概念起源於服務端,在之前先後端不分離的時候,由後端來控制路由,當接收到客戶端發來的 HTTP 請求,就會根據所請求的相應 URL,來找到相應的映射函數,而後執行該函數,並將函數的返回值發送給客戶端。對於最簡單的靜態資源服務器,能夠認爲,全部 URL 的映射函數就是一個文件讀取操做。對於動態資源,映射函數多是一個數據庫讀取操做,也多是進行一些數據的處理等等。而後根據這些讀取的數據,在服務器端就使用相應的模板來對頁面進行渲染後,再返回渲染完畢的頁面。它的好處與缺點很是明顯:前端

  • 好處:安全性好,SEO 好;
  • 缺點:加大服務器的壓力,不利於用戶體驗,代碼冗合很差維護;

​ 也正是因爲後端路由還存在着本身的不足,前端路由纔有了本身的發展空間。對於前端路由來講,路由的映射函數一般是進行一些 DOM 的顯示和隱藏操做。這樣,當訪問不一樣的路徑的時候,會顯示不一樣的頁面組件。前端路由主要有如下兩種實現方案:vue

  • Hash
  • History

固然,前端路由也存在缺陷:使用瀏覽器的前進,後退鍵時會從新發送請求,來獲取數據,沒有合理地利用緩存。但總的來講,如今前端路由已是實現路由的主要方式了,前端三大框架 AngularReactVue ,它們的路由解決方案 angular/routerreact-routervue-router 都是基於前端路由進行開發的,所以將前端路由進行了解和 掌握是頗有必要的,下面咱們分別對兩種常見的前端路由模式 HashHistory 進行講解。java

2、前端路由的兩種實現

2.一、Hash 模式

2.1.一、原理

早期的前端路由的實現就是基於 location.hash 來實現的。其實現原理也很簡單,location.hash 的值就是 URL 中 # 後面的內容。好比下面這個網站,它的 location.hash 的值爲 '#search'react

https://www.word.com#search
複製代碼

此外,hash 也存在下面幾個特性:git

  • URLhash 值只是客戶端的一種狀態,也就是說當向服務器端發出請求時,hash 部分不會被髮送。
  • hash 值的改變,都會在瀏覽器的訪問歷史中增長一個記錄。所以咱們能經過瀏覽器的回退、前進按鈕控制hash 的切換。
  • 咱們可使用 hashchange 事件來監聽 hash 的變化。

咱們能夠經過兩種方式觸發 hash 變化,一種是經過 a 標籤,並設置 href 屬性,當用戶點擊這個標籤後,URL 就會發生改變,也就會觸發 hashchange 事件了:github

<a href="#search">search</a>
複製代碼

還有一種方式就是直接使用 JavaScript來對 loaction.hash 進行賦值,從而改變 URL,觸發 hashchange 事件:vue-router

location.hash="#search"
複製代碼

如下實現咱們採用第2種方式來實現。數據庫

2.1.二、實現

咱們先定義一個父類 BaseRouter,用於實現 Hash 路由和 History 路由的一些共有方法;segmentfault

export class BaseRouter {
  // list 表示路由表
  constructor(list) {
    this.list = list;
  }
  // 頁面渲染函數
  render(state) {
    let ele = this.list.find(ele => ele.path === state);
    ele = ele ? ele : this.list.find(ele => ele.path === '*');
    ELEMENT.innerText = ele.component;
  }
}
複製代碼

咱們簡單實現了 push 壓入功能、go 前進/後退功能,相關代碼的註釋都已經標上,簡單易懂,就不在一 一介紹,參見以下:

export class HashRouter extends BaseRouter {
  constructor(list) {
    super(list);
    this.handler();
    // 監聽 hashchange 事件
    window.addEventListener('hashchange', e => {
      this.handler();
    });
  }
  // hash 改變時,從新渲染頁面
  handler() {
    this.render(this.getState());
  }
  // 獲取 hash 值
  getState() {
    const hash = window.location.hash;
    return hash ? hash.slice(1) : '/';
  }
  // push 新的頁面
  push(path) {
    window.location.hash = path;
  }
  // 獲取 默認頁 url
  getUrl(path) {
    const href = window.location.href;
    const i = href.indexOf('#');
    const base = i >= 0 ? href.slice(0, i) : href;
    return base +'#'+ path;
  }
  // 替換頁面
  replace(path) {
    window.location.replace(this.getUrl(path));
  }
  // 前進 or 後退瀏覽歷史
  go(n) {
    window.history.go(n);
  }
}
複製代碼

2.1.三、效果圖

Hash 模式的路由實現例子的效果圖以下所示:

1.png

2.二、History 模式

2.2.一、原理

前面的 hash 雖然也很不錯,但使用時都須要加上 #,並非很美觀。所以到了 HTML5,又提供了 History API 來實現 URL 的變化。其中作最主要的 API 有如下兩個:history.pushState()history.repalceState()。這兩個 API能夠在不進行刷新的狀況下,操做瀏覽器的歷史紀錄。惟一不一樣的是,前者是新增一個歷史記錄,後者是直接替換當前的歷史記錄,以下所示:

window.history.pushState(null, null, path);
window.history.replaceState(null, null, path);
複製代碼

此外,history 存在下面幾個特性:

  • pushStaterepalceState 的標題(title):通常瀏覽器會忽略,最好傳入 null
  • 咱們可使用 popstate  事件來監聽 url 的變化;
  • history.pushState()history.replaceState() 不會觸發 popstate 事件,這時咱們須要手動觸發頁面渲染;

2.2.二、實現

咱們一樣簡單實現了 push 壓入功能、go 前進/後退功能,相關代碼的註釋都已經標上,簡單易懂,就不在一 一介紹,參見以下:

export class HistoryRouter extends BaseRouter {
  constructor(list) {
    super(list);
    this.handler();
    // 監聽 popstate 事件
    window.addEventListener('popstate', e => {
      console.log('觸發 popstate。。。');
      this.handler();
    });
  }
  // 渲染頁面
  handler() {
    this.render(this.getState());
  }
  // 獲取 url 
  getState() {
    const path = window.location.pathname;
    return path ? path : '/';
  }
  // push 頁面
  push(path) {
    history.pushState(null, null, path);
    this.handler();
  }
  // replace 頁面
  replace(path) {
    history.replaceState(null, null, path);
    this.handler();
  }
   // 前進 or 後退瀏覽歷史
  go(n) {
    window.history.go(n);
  }
}
複製代碼

2.2.三、效果圖

History 模式的路由實現例子的效果圖以下所示:

1.png

2.三、兩種路由模式的對比

對比點 Hash 模式 History 模式
美觀性 帶着 # 字符,較醜 簡潔美觀
兼容性 >= ie 8,其它主流瀏覽器 >= ie 10,其它主流瀏覽器
實用性 不須要對服務端作改動 須要服務端對路由進行相應配合設置

3、總結

本文咱們大體介紹了什麼路由、前端路由的源起、以及分析了兩種前端路由:Hash 模式和 History 模式的原理以及簡單功能實現,文中例子的代碼實現已經放到 github 上面:github.com/fengshi123/… 。經過本文對前端路由原理的掌握,這時你就能夠基礎原理基礎去閱讀 vue-routerreact-router 的源碼實現了。

github地址爲:github.com/fengshi123/… ,上面彙總了做者全部的博客文章,若是喜歡或者有所啓發,請幫忙給個 star ~,對做者也是一種鼓勵。

參考文獻

一、淺談前端路由:www.jianshu.com/p/d2aa8fb95…

二、前端路由:segmentfault.com/a/119000001…

相關文章
相關標籤/搜索