在傳統的 Web 開發中,瀏覽器根據地址欄的 URL 向服務器發送一個 HTTP 請求,服務器根據 URL 返回一個 HTML 頁面。這種狀況下,一個 URL 對應一個 HTML 頁面, 一個 Web 應用包含不少 HTML 頁面,這樣的應用就是多頁面應用;在多頁面應用中,頁面路由的控制由服務器負責,這種路由方式稱爲後端路由。javascript
在多頁面應用中,每次頁面切換都須要向服務器發送一次請求,頁面使用的靜態資源也須要從新加載,存在必定的浪費。並且,頁面的總體刷新對用戶體驗也有影響,由於不一樣頁面 間每每存在共同的部分,例如導航欄、側邊欄等,頁面總體刷新也會致使公共部分的刷新。html
有沒有一種方式讓 Web 應用只是看起來像多頁面應用,也就是說 URL 的變化能夠引發頁面內容的變化,但不會向服務器發送新的請求哪?知足這種條件的 Web 應用就是單頁面 應用(Single Page Application,簡稱 SPA)。單頁面應用雖然名爲」單頁「,但視覺上的感覺仍然是多頁面,由於 URL 發生變化,頁面上的內容也會變化,但這只是邏輯上的多頁面,實際上不管 URL 如何變化,對應的 HTML 文件都是同一個,這也是單頁面應用名字的由來。在單頁面應用中,URL 發生變化並不會向服務器發送新的請求,因此」邏輯頁面「的變化只能由前端負責,這種方式稱爲前端路由。前端
路由就是 URL 到函數的映射,這個是前端路由的原理。若是作到在 URL 發生變化的時候不向服務器發送請求,而是去執行一個控制 UI 組件的函數哪?那就不得不說說 hash 和 history 這兩種實現方案了。java
在一個 URL 的組成中,#
號包括#
號後邊的部分稱爲 hash。在瀏覽器中,能夠經過location.hash
獲取到。#
表明網頁中的一個位置,其右邊的字符,就是該位置的標識符。好比:git
// #title 是 hash
http://www.example.com/index.html#title
複製代碼
#
號是用來指導瀏覽器動做的,對服務器徹底不起做用,HTTP 請求不會帶上#
號以及它後邊的內容。單單改變#
號後邊的內容,瀏覽器只會滾動到指定的位置,不會從新加載網頁。並且改變 hash 還會改變瀏覽器的歷史記錄。咱們能夠經過onhashchange
監聽到 hash 的改變來不刷新瀏覽器觸發視圖的更新。代碼以下:github
<ul>
<li><a href="#">white</a></li>
<li><a href="#yellow">yellow</a></li>
<li><a href="#green">green</a></li>
</ul>
這是頁面的內容
複製代碼
function Router() {
this.routes = {};
this.currentUrl = "";
}
Router.prototype.route = function(path, callback) {
this.routes[path] =
callback ||
function() {
console.log("請爲路由綁定處理方法");
};
};
Router.prototype.refresh = function() {
console.log("觸發一次 hashchange,hash值爲", location.hash);
this.currentUrl = "/" + location.hash.slice(1);
// 執行當前路由綁定的方法
this.routes[this.currentUrl]();
};
Router.prototype.init = function() {
window.addEventListener("DOMContentLoaded", this.refresh.bind(this), false);
window.addEventListener("hashchange", this.refresh.bind(this), false);
};
window.Router = new Router();
window.Router.init();
var content = document.querySelector("body");
function changeBgColor(color) {
content.style.backgroundColor = color;
}
Router.route("/", function() {
changeBgColor("white");
});
Router.route("/yellow", function() {
changeBgColor("yellow");
});
Router.route("/green", function() {
changeBgColor("green");
});
複製代碼
在 HTML5 規範中,history新增了一下幾個 API:後端
history.pushState(); // 添加新的狀態到歷史狀態棧
history.replaceState(); // 用新的狀態代替當前狀態
history.state // 返回當前狀態對象
複製代碼
經過上面兩個操做狀態的 API,也可以作到:改變 url 的同時,不刷新頁面。因此 history 也具有實現路由控制的潛力。僅僅是改變 url 不刷新頁面還不夠,還要可以監聽到 url 的變化。對於 hash 來講,hash 的改變能夠出發 onhashchange 事件,history 並無這樣的事件能夠監聽。然而,對於一個應用來講,改變一個 url 只有下面三種途徑:瀏覽器
第 2 種和第 3 種途徑能夠當作是一種,由於 a 標籤的默認事件能夠被禁止,進而調用 js 方法。關鍵是第 1 種,HTML5 規範種新增了一個 onpopstate 事件,經過它即可以監聽到前進或者後退的按鈕點擊。要特別注意的是:調用history.pushState
和history.replaceState
並不會觸發 onpopstate 事件。bash
// 頁面加載完不會觸發 hashchange,這裏主動觸發一次 hashchange 事件
window.addEventListener('DOMContentLoaded', onLoad)
// 監聽路由變化
window.addEventListener('popstate', onPopState)
// 路由視圖
var routerView = null
function onLoad () {
routerView = document.querySelector('#routeView')
onPopState()
// 攔截 <a> 標籤點擊事件默認行爲, 點擊時使用 pushState 修改 URL並更新手動 UI,從而實現點擊連接更新 URL 和 UI 的效果。
var linkList = document.querySelectorAll('a[href]')
linkList.forEach(el => el.addEventListener('click', function (e) {
e.preventDefault()
history.pushState(null, '', el.getAttribute('href'))
onPopState()
}))
}
// 路由變化時,根據路由渲染對應 UI
function onPopState () {
switch (location.pathname) {
case '/home':
routerView.innerHTML = 'Home'
return
case '/about':
routerView.innerHTML = 'About'
return
default:
return
}
}
複製代碼
hash 模式下,每一個 url 都會帶有#
號,看起來可能不太友好。可是,hash 模式兼容 IE8 及其以上的瀏覽器。history 模式使用了 HTML5 裏邊新的 API,看起來會比較友好。可是,僅僅有前端的參與仍是不夠的,須要後端進行配置,前端的路由要和後端的路由要匹配起來,在刷新瀏覽器的時候會給後端發送請求,這個時候後臺須要對請求的 url,作一個捕捉,後端不存在的 url,統一返回請求根路徑時的前端環境,交由前端路由模塊處理。服務器