經過用戶請求的url導航到具體的html頁面;每跳轉到不一樣的URL,都是從新訪問服務端,而後服務端返回頁面,頁面也能夠是服務端獲取數據,而後和模板組合,返回HTML,也能夠是直接返回模板HTML,而後由前端js再去請求數據,使用前端模板和數據進行組合,生成想要的HTMLjavascript
只有一張Web頁面的應用,是一種從Web服務器加載的富客戶端,單頁面跳轉僅刷新局部資源 ,公共資源(js、css等)僅需加載一次 頁面跳轉:使用js中的append/remove或者show/hide的方式來進行頁面內容的更換; 數據傳遞:可經過全局變量或者參數傳遞,進行相關數據交互css
使用場景: 適用於高度追求高度支持搜索引擎的應用html
邏輯清楚,各個頁面按照功能和邏輯劃分,不用擔憂業務複雜度前端
單個頁面體積較小,加載速度比較有保證vue
多頁面跳轉須要刷新全部資源,每一個公共資源(js、css等)需選擇性從新加載java
頁面跳轉:使用window.location.href = "./index.html"進行頁面間的跳轉;ajax
數據傳遞:可使用path?account="123"&password=""路徑攜帶數據傳遞的方式,或者localstorage、cookie等存儲方式後端
使用場景: 高要求的體驗度,追求界面流暢的應用瀏覽器
這裏的 hash 就是指 url 後的 # 號以及後面的字符。好比說 "www.baidu.com/#hashhash" ,其中 "#hashhash" 就是咱們指望的 hash 值。緩存
因爲 hash 值的變化不會致使瀏覽器像服務器發送請求,並且 hash 的改變會觸發 hashchange 事件,瀏覽器的前進後退也能對其進行控制,因此在 H5 的 history 模式出現以前,基本都是使用 hash 模式來實現前端路由。
// 監聽hash變化,點擊瀏覽器的前進後退會觸發
window.addEventListener('hashchange', function(event){
let newURL = event.newURL; // hash 改變後的新 url
let oldURL = event.oldURL; // hash 改變前的舊 url
},false)
複製代碼
下面實現一個路由對象
class HashRouter{
constructor(){
//用於存儲不一樣hash值對應的回調函數
this.routers = {};
window.addEventListener('hashchange',this.load.bind(this),false)
}
//用於註冊每一個視圖
register(hash,callback = function(){}){
this.routers[hash] = callback;
}
//用於註冊首頁
registerIndex(callback = function(){}){
this.routers['index'] = callback;
}
//用於處理視圖未找到的狀況
registerNotFound(callback = function(){}){
this.routers['404'] = callback;
}
//用於處理異常狀況
registerError(callback = function(){}){
this.routers['error'] = callback;
}
//用於調用不一樣視圖的回調函數
load(){
let hash = location.hash.slice(1),
handler;
//沒有hash 默認爲首頁
if(!hash){
handler = this.routers.index;
}
//未找到對應hash值
else if(!this.routers.hasOwnProperty(hash)){
handler = this.routers['404'] || function(){};
}
else{
handler = this.routers[hash]
}
//執行註冊的回調函數
try{
handler.apply(this);
}catch(e){
console.error(e);
(this.routers['error'] || function(){}).call(this,e);
}
}
}
複製代碼
再來一個例子
<body>
<div id="nav">
<a href="#/page1">page1</a>
<a href="#/page2">page2</a>
<a href="#/page3">page3</a>
<a href="#/page4">page4</a>
<a href="#/page5">page5</a>
</div>
<div id="container"></div>
</body>
複製代碼
let router = new HashRouter();
let container = document.getElementById('container');
//註冊首頁回調函數
router.registerIndex(()=> container.innerHTML = '我是首頁');
//註冊其餘視圖回到函數
router.register('/page1',()=> container.innerHTML = '我是page1');
router.register('/page2',()=> container.innerHTML = '我是page2');
router.register('/page3',()=> container.innerHTML = '我是page3');
router.register('/page4',()=> {throw new Error('拋出一個異常')});
//加載視圖
router.load();
//註冊未找到對應hash值時的回調
router.registerNotFound(()=>container.innerHTML = '頁面未找到');
//註冊出現異常時的回調
router.registerError((e)=>container.innerHTML = '頁面異常,錯誤消息:<br>' + e.message);
複製代碼
在 HTML5 以前,瀏覽器就已經有了 history 對象。但在早期的 history 中只能用於多頁面的跳轉:
history.go(-1); // 後退一頁
history.go(2); // 前進兩頁
history.forward(); // 前進一頁
history.back(); // 後退一頁
複製代碼
在 HTML5 的規範中,history 新增瞭如下幾個 API
history.pushState(); // 添加新的狀態到歷史狀態棧
history.replaceState(); // 用新的狀態代替當前狀態
history.state // 返回當前狀態對象
複製代碼
因爲 history.pushState() 和 history.replaceState() 能夠改變 url 同時,不會刷新頁面,因此在 HTML5 中的 histroy 具有了實現前端路由的能力。
對於單頁應用的 history 模式而言,url 的改變只能由下面四種方式引發:
下面實現一個路由對象
class HistoryRouter{
constructor(){
//用於存儲不一樣path值對應的回調函數
this.routers = {};
this.listenPopState();
this.listenLink();
}
//監聽popstate
listenPopState(){
window.addEventListener('popstate',(e)=>{
let state = e.state || {},
path = state.path || '';
this.dealPathHandler(path)
},false)
}
//全局監聽A連接
listenLink(){
window.addEventListener('click',(e)=>{
let dom = e.target;
if(dom.tagName.toUpperCase() === 'A' && dom.getAttribute('href')){
e.preventDefault()
this.assign(dom.getAttribute('href'));
}
},false)
}
//用於首次進入頁面時調用
load(){
let path = location.pathname;
this.dealPathHandler(path)
}
//用於註冊每一個視圖
register(path,callback = function(){}){
this.routers[path] = callback;
}
//用於註冊首頁
registerIndex(callback = function(){}){
this.routers['/'] = callback;
}
//用於處理視圖未找到的狀況
registerNotFound(callback = function(){}){
this.routers['404'] = callback;
}
//用於處理異常狀況
registerError(callback = function(){}){
this.routers['error'] = callback;
}
//跳轉到path
assign(path){
history.pushState({path},null,path);
this.dealPathHandler(path)
}
//替換爲path
replace(path){
history.replaceState({path},null,path);
this.dealPathHandler(path)
}
//通用處理 path 調用回調函數
dealPathHandler(path){
let handler;
//沒有對應path
if(!this.routers.hasOwnProperty(path)){
handler = this.routers['404'] || function(){};
}
//有對應path
else{
handler = this.routers[path];
}
try{
handler.call(this)
}catch(e){
console.error(e);
(this.routers['error'] || function(){}).call(this,e);
}
}
}
複製代碼
再來一個例子
<body>
<div id="nav">
<a href="/page1">page1</a>
<a href="/page2">page2</a>
<a href="/page3">page3</a>
<a href="/page4">page4</a>
<a href="/page5">page5</a>
<button id="btn">page2</button>
</div>
<div id="container">
</div>
</body>
複製代碼
let router = new HistoryRouter();
let container = document.getElementById('container');
//註冊首頁回調函數
router.registerIndex(() => container.innerHTML = '我是首頁');
//註冊其餘視圖回到函數
router.register('/page1', () => container.innerHTML = '我是page1');
router.register('/page2', () => container.innerHTML = '我是page2');
router.register('/page3', () => container.innerHTML = '我是page3');
router.register('/page4', () => {
throw new Error('拋出一個異常')
});
document.getElementById('btn').onclick = () => router.assign('/page2')
//註冊未找到對應path值時的回調
router.registerNotFound(() => container.innerHTML = '頁面未找到');
//註冊出現異常時的回調
router.registerError((e) => container.innerHTML = '頁面異常,錯誤消息:<br>' + e.message);
//加載頁面
router.load();
複製代碼