一文讀盡前端路由、後端路由、單頁面應用、多頁面應用

本文源自掘金 https://juejin.im/user/5b52fd...css

前端路由

  • 定義:在單頁面應用,大部分頁面結構不變,只改變部份內容的使用
  • 優勢:用戶體驗好,不須要每次都從服務器所有獲取,快速展示給用戶
  • 缺點:使用瀏覽器的前進,後退鍵的時候會從新發送請求,沒有合理地利用緩存。單頁面沒法記住以前滾動的位置,沒法在前進,後退的時候記住滾動的位置

後端路由

經過用戶請求的url導航到具體的html頁面;每跳轉到不一樣的URL,都是從新訪問服務端,而後服務端返回頁面,頁面也能夠是服務端獲取數據,而後和模板組合,返回HTML,也能夠是直接返回模板HTML,而後由前端js再去請求數據,使用前端模板和數據進行組合,生成想要的HTMLhtml

先後端路由對比

  1. 從性能和用戶體驗的層面來比較的話,後端路由每次訪問一個新頁面的時候都要向服務器發送請求,而後服務器再響應請求,這個過程確定會有延遲。而前端路由在訪問一個新頁面的時候僅僅是變換了一下路徑而已,沒有了網絡延遲,對於用戶體驗來講會有至關大的提高。     
  2. 在某些場合中,用ajax請求,可讓頁面無刷新,頁面變了但Url沒有變化,用戶就不能複製到想要的地址,用前端路由作單頁面網頁就很好的解決了這個問題。可是前端路由使用瀏覽器的前進,後退鍵的時候會從新發送請求,沒有合理地利用緩存。

單頁面的優點

  • 不存在頁面切換問題,由於只在同一個頁面間切換,會更流暢,並且能夠附加各類動畫和過分效果,用戶體驗更好。
  • 能夠用到vue的路由和狀態保持,不用擔憂切換形成的數據不一樣步。
  • 打包方便,有現成的腳手架能夠用,也比較不容易出問題

單頁面的劣勢

  • 全部邏輯和業務都在一個頁面上,邏輯上不是很清楚,當業務變得複雜的時候改動起來就比較麻煩
  • 雞蛋都在一個籃子裏,只要一個地方出現錯誤,可能致使整個頁面出錯
  • 全部代碼都在一個頁面,首次加載耗時較長,頁面體積較大

只有一張Web頁面的應用,是一種從Web服務器加載的富客戶端,單頁面跳轉僅刷新局部資源 ,公共資源(js、css等)僅需加載一次 頁面跳轉:使用js中的append/remove或者show/hide的方式來進行頁面內容的更換; 數據傳遞:可經過全局變量或者參數傳遞,進行相關數據交互前端

使用場景: 適用於高度追求高度支持搜索引擎的應用vue

多頁面的優點

  • 邏輯清楚,各個頁面按照功能和邏輯劃分,不用擔憂業務複雜度
  • 單個頁面體積較小,加載速度比較有保證
  • 多頁面跳轉須要刷新全部資源,每一個公共資源(js、css等)需選擇性從新加載
  • 頁面跳轉:使用window.location.href = "./index.html"進行頁面間的跳轉;
  • 數據傳遞:可使用path?account="123"&password=""路徑攜帶數據傳遞的方式,或者localstorage、cookie等存儲方式

使用場景: 高要求的體驗度,追求界面流暢的應用webpack

多頁面的劣勢

  • 重複代碼較多
  • 頁面常常須要切換,切換效果取決於瀏覽器和網絡狀況,對用戶體驗會有必定負面影響
  • 沒法充分利用vue的路由和狀態保持,在多個頁面之間共享和同步數據狀態會成爲一個難題

hash 模式

這裏的 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);

history 模式

在 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 的改變只能由下面四種方式引發:

  • 點擊瀏覽器的前進或後退按鈕
  • 點擊 a 標籤
  • 在 JS 代碼中觸發 history.pushState 函數
  • 在 JS 代碼中觸發 history.replaceState 函數

下面實現一個路由對象

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的防抖與節流

相關文章
相關標籤/搜索