本文源自掘金 https://juejin.im/user/5b52fd...css
經過用戶請求的url導航到具體的html頁面;每跳轉到不一樣的URL,都是從新訪問服務端,而後服務端返回頁面,頁面也能夠是服務端獲取數據,而後和模板組合,返回HTML,也能夠是直接返回模板HTML,而後由前端js再去請求數據,使用前端模板和數據進行組合,生成想要的HTMLhtml
只有一張Web頁面的應用,是一種從Web服務器加載的富客戶端,單頁面跳轉僅刷新局部資源 ,公共資源(js、css等)僅需加載一次 頁面跳轉:使用js中的append/remove或者show/hide的方式來進行頁面內容的更換; 數據傳遞:可經過全局變量或者參數傳遞,進行相關數據交互前端
使用場景: 適用於高度追求高度支持搜索引擎的應用vue
使用場景: 高要求的體驗度,追求界面流暢的應用webpack
這裏的 hash 就是指 url 後的 # 號以及後面的字符。好比說 "www.baidu.com/#hashhash" ,其中 "#hashhash" 就是咱們指望的 hash 值。web
因爲 hash 值的變化不會致使瀏覽器像服務器發送請求,並且 hash 的改變會觸發 hashchange 事件,瀏覽器的前進後退也能對其進行控制,因此在 H5 的 history 模式出現以前,基本都是使用 hash 模式來實現前端路由。ajax
// 監聽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); } } }
再來一個例子vue-cli
<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 中只能用於多頁面的跳轉:npm
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();
從零開始構建一個webpack項目
總結幾個webpack打包優化的方法
總結前端性能優化的方法
幾種常見的JS遞歸算法
搭建一個vue-cli的移動端H5開發模板
封裝一個toast和dialog組件併發布到npm
一文讀盡前端路由、後端路由、單頁面應用、多頁面應用
關於幾個移動端軟鍵盤的坑及其解決方案
淺談JavaScript的防抖與節流