什麼是路由?
個人理解路由就是瀏覽器 URL 和頁面內容的一種對映關係。
比如你看到我這篇部落格,部落格的連結是一個 URL,而 URL 對應的就是我這篇部落格的網頁內容,這二者之間的對映關係就是路由。
其中路由又分為前端路由和後端路由,由於目前是大部門開發模式都是前後端分離開發模式,其大部分應用都是 SPA(simple Page Application,單頁面應用),個人的工作也是由後端提供API,我前端來進行整個頁面的渲染和路由控制。
前端路由目前實現的原理主要是:URL 的 hash、HTML5 的 History
hash 模式
瀏覽器提供了一些 api 可以讓我們獲取到URL中帶“#”的標識。比如 URL.hash、location.hash。
同時我們可以通過 hashchange 事件來監聽hash值的改變,這樣就能通過事件監聽 url 中 hash 的改變從而改變特定頁面元素的顯示內容,從而實現前端路由。簡單實現程式碼如下:
<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 來簡單實現下,程式碼如下:
<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 實現。那麼就不能操作瀏覽器上面的前進後退操作。
// 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';
}
});
}