前端路由原理之 hash 模式和 history 模式

努力掙錢的小鑫發表於2021-09-10

什麼是路由?

個人理解路由就是瀏覽器 URL 和頁面內容的一種對映關係。

比如你看到我這篇部落格,部落格的連結是一個 URL,而 URL 對應的就是我這篇部落格的網頁內容,這二者之間的對映關係就是路由。

其中路由又分為前端路由和後端路由,由於目前是大部門開發模式都是前後端分離開發模式,其大部分應用都是 SPA(simple Page Application,單頁面應用),個人的工作也是由後端提供API,我前端來進行整個頁面的渲染和路由控制。

前端路由目前實現的原理主要是:URL 的 hashHTML5 的 History

hash 模式

瀏覽器提供了一些 api 可以讓我們獲取到URL中帶“#”的標識。比如 URL.hash、location.hash。

同時我們可以通過 hashchange 事件來監聽hash值的改變,這樣就能通過事件監聽 url 中 hash 的改變從而改變特定頁面元素的顯示內容,從而實現前端路由。簡單實現程式碼如下:
image

<div id="app">
	<a href="#/home">home</a>
	<a href="#/about">about</a>

	<div class="router-view"></div>
</div>
// 1.獲取路由顯示元素
const routerViewEl = document.querySelector('.router-view');

// 2.監聽 hashchange 事件
window.addEventListener('hashchange', () => {
  // 3.判斷 hash 的改變值,修改路由顯示元素的 innerHTMl
  switch (location.hash) {
    case '#/home':
      routerViewEl.innerHTML = 'Home';
      break;
    case '#/about':
      routerViewEl.innerHTML = 'about';
      break;
    default:
      routerViewEl.innerHTML = 'default';
  }
});

History 模式

history 介面是 HTML5 新增的, 它有六種模式改變 URL 而不重新整理頁面。

  • pushState:使用新的路徑;
  • replaceState:替換原來的路徑;
  • popState:路徑的回退;
  • go:向前或向後改變路徑;
  • forward:向前改變路徑;
  • back:向後改變路徑;

其中比較重要的兩個 api 是 pushState 和 replaceState 是比較重要的,是實現 history 模式的重要 api。

首先我們用 pushState 來簡單實現下,程式碼如下:
image

<div id="app">
	<a href="/home">home</a>
	<a href="/about">about</a>

	<div class="router-view"></div>
</div>
// 1.獲取路由顯示元素
const routerViewEl = document.querySelector('.router-view');

// 2.獲取所有路由跳轉元素
const aEls = document.getElementsByTagName('a');
// 3.遍歷所有 a 元素,註冊事件監聽點選
for (let aEl of aEls) {
  aEl.addEventListener('click', (e) => {
    // 4.阻止預設跳轉
    e.preventDefault();
    // 5.獲取 href 屬性
    const href = aEl.getAttribute('href');
    // 6.執行 history.pushState
    history.pushState({}, '', href);
    // 7.判斷 pathname 路徑的改變
    switch (location.pathname) {
      case '/home':
        routerViewEl.innerHTML = 'Home';
        break;
      case '/about':
        routerViewEl.innerHTML = 'about';
        break;
      default:
        routerViewEl.innerHTML = 'default';
    }
  });
}

相同之處是兩個 API 都會操作瀏覽器的歷史記錄,而不會引起頁面的重新整理。

不同之處在於,pushState 會增加一條新的歷史記錄,而 replaceState 則會替換當前的歷史記錄。其內部原理是 pushState 每次前進一次頁面都是入棧操作,每次返回又是出棧,因此使用 pushState ,瀏覽器可以執行本身的前進後退操作,其內部就是一個入棧出棧;而 replaceState 就不可,它是把每次棧底的資料替換,而不是入棧。

pushState 棧:[]->點選 home 入棧->[home]->點選 about 入棧->[home,about]->瀏覽器回退,about 出棧->[home]
replaceState 棧:[]->點選 home 入棧->[home]->點選 about 替換棧底->[about]

如果把程式碼改成 replaceState 實現。那麼就不能操作瀏覽器上面的前進後退操作。

image

// 1.獲取路由顯示元素
const routerViewEl = document.querySelector('.router-view');

// 2.獲取所有路由跳轉元素
const aEls = document.getElementsByTagName('a');
// 3.遍歷所有 a 元素,註冊事件監聽點選
for (let aEl of aEls) {
  aEl.addEventListener('click', (e) => {
    // 4.阻止預設跳轉
    e.preventDefault();
    // 5.獲取 href 屬性
    const href = aEl.getAttribute('href');
    // 6.執行 history.replaceState
    // history.pushState({}, '', href);
    history.replaceState({}, '', href);
    // 7.判斷 pathname 路徑的改變
    switch (location.pathname) {
      case '/home':
        routerViewEl.innerHTML = 'Home';
        break;
      case '/about':
        routerViewEl.innerHTML = 'about';
        break;
      default:
        routerViewEl.innerHTML = 'default';
    }
  });
}

參考文章

前端路由的兩種實現原理

相關文章