原生js實現前端路由


一、前端路由有兩種實現方式

  • hash模式的實現原理

1、早期的前端路由的實現就是基於  window.location.hash 來實現的,其實現的原理十分簡單,window.location.hash 的值就是 URL 中的 # 后面的值,例如:http://localhost:3000/#/orange 這個路由的 window.location.hash 為  #/orange;

    • hash路由
      /**
       * hash路由:通過監聽url中的hash變化來進行路由跳轉
       * 當hash發生變化時,瀏覽器更新視圖並不會重新發起請求,
       * 而是會觸發 hashchange 事件
       * 優點:瀏覽器兼容性較好,連 IE8 都支持
       * 缺點:路徑在井號 # 的后面,比較丑,問題在於url中一直存在#不夠美觀
       * 而且hash路由更像是Hack而非標准,相信隨着發展更加標准化的History API
       * 會逐步蠶食掉hash路由的市場
       */
    • 實現主要基於以下幾個方面的特性
      1、URL 中的 hash 值只是客戶端的一種狀態,也就是說當向服務器發出請求時,hash 部分不會被發送;
      2、hash 值的改變,都會在瀏覽器的訪問歷史中增加一個記錄,因此我們能通過瀏覽器的回退,前進按鈕控制 hash 的切換;
      3、可以通過設置a標簽,並通過設置 href 屬性,例如href = ‘#/blue’,當點擊標簽的時候,url的 hash 值會發生改變,在當前url的后面增加上’#/blue’,
      同時觸發hashchange,再回調函數中進行處理;
      4、前進后退的時候,可以直接通過js來對 location.hash 進行賦值,改變url的 hash 值,例如 location.hash = ‘#/blue’即可,此時url會改變,
      也會觸發hashchange事件。
      5、因此我們可以使用 hashchange 事件來監聽 hash 值得變化,從而對頁面進行跳轉(渲染);
    • 實現方案:
      • html代碼:

        <!DOCTYPE html>
        <html lang="en">
        <head>
          <meta charset="UTF-8">
          <meta http-equiv="X-UA-Compatible" content="IE=edge">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <title>前端路由</title>
        </head>
        <body>
          <h1>hello , Hash router!!!</h1>
          <div style="margin-bottom:100px">
            <ul>
              <li><a href="#/">turn white</a></li>
              <li><a href="#/blue">turn blue</a></li>
              <li><a href="#/green">turn green</a>
              <li><a href="#/red">turn red</a></li>
              <li><a href="#/orange">turn orange</a>
            </ul>
            <button id="btn">回退</button>
            <div id="app" style="margin-top:50px;height:100px">
              
            </div>
          </div>
          
          <script>
            function render() {
               app.innerHTML = '渲染容器'+window.location.hash;
               app.style.backgroundColor="pink"
            }
            window.addEventListener('hashchange', render)
          </script>
          <script src="./js/router.js"></script>
        </body>
        </html>
      • js代碼
        class Router {
          constructor() {
            /**
             * 以鍵值對的形式存儲路由
             */
            this.routers = new Object();
            /**
             * 當前路由的URL
             */
            this.currentUrl = "";
            /**
             * 記錄出現過的hash
             */
            this.history = [];
            /**
             * 作為指針,默認指向this.history的末尾,根據后退前進指向history中不同的hash
             */
            this.currentIndex = this.history.length - 1;
            /**
             * 默認不是后退操作
             */
            this.isBack = false;
          }
          /**
           * 都定義在原型上,后面的覆蓋前面的,這個不執行
           */
          route(path, callback) {
            console.log(1);
          }
        }
        
        /**
         * 將路由的hash以及對應的callback函數儲存
         * @param {*} path
         * @param {*} callback
         */
        Router.prototype.route = function (routes) {
          for (let route of routes) {
            this.routers[route.path] = route.callback || function () {};
          }
        };
        
        /**
         * 當頁面刷新的時候
         */
        Router.prototype.refresh = function () {
          /**
           * 獲取當前頁面中的hash路徑
           */
          this.currentUrl = window.location.hash.slice("1") || "/";
          /**
           * 不是后退才執行
           */
          if (!this.isBack) {
            if (this.currentIndex < this.history.length - 1)
              this.history = this.history.slice(0, this.currentIndex + 1);
            /**
             * 將當前hash路由推入數組儲存,指針向前移動
             */
            this.history.push(this.currentUrl);
            this.currentIndex++;
          }
          this.isBack = false;
          /**
           * 執行當前hash路徑的回調函數
           */
          this.routers[this.currentUrl]();
          console.log("refresh");
          console.log(this.history);
          console.log(this.currentIndex);
        };
        
        /**
         * 當頁面后退,回退的過程中會觸發hashchange,將hash重新放入,索引增加
         */
        Router.prototype.back = function () {
          console.log("back");
          console.log(this.history);
          console.log(this.currentIndex);
          // 后退操作設置為true
          this.isBack = true;
          /**
           * 如果指針小於0的話就不存在對應hash路由了,因此鎖定指針為0即可
           */
          this.currentIndex <= 0
            ? (this.currentIndex = 0)
            : (this.currentIndex = this.currentIndex - 1);
          /**
           * 隨着后退,location.hash也應該隨之變化
           * 並執行指針目前指向hash路由對應的callback
           */
          location.hash = `#${this.history[this.currentIndex]}`;
          this.routers[this.history[this.currentIndex]]();
        };
        
        /**
         * 初始化,監聽頁面的加載與hash只的變化
         */
        Router.prototype.init = function () {
          /**
           * 修改this指向,否則指向window
           */
          window.addEventListener("load", this.refresh.bind(this), false);
          window.addEventListener("hashchange", this.refresh.bind(this), false);
        };
        
        const route = new Router();
        /**
         * 初始化
         */
        route.init();
        const routes = [
          {
            path: "/",
            callback: function () {
              let el = document.body;
              el.style.backgroundColor = "#fff";
            },
          },
          {
            path: "/blue",
            callback: function () {
              let el = document.body;
              el.style.backgroundColor = "blue";
            },
          },
          {
            path: "/green",
            callback: function () {
              let el = document.body;
              el.style.backgroundColor = "green";
            },
          },
          {
            path: "/red",
            callback: function () {
              let el = document.body;
              el.style.backgroundColor = "red";
            },
          },
          {
            path: "/orange",
            callback: function () {
              let el = document.body;
              el.style.backgroundColor = "orange";
            },
          },
        ];
        /**
         * 將hash值與cb綁定
         */
        route.route(routes);
        window.onload = function () {
          let btn = document.getElementById("btn");
          btn.addEventListener("click", route.back.bind(route), false);
        };
  • history模式的實現原理

HTML5 提供了 History API 來實現 URL 的變化,其中最主要的 API 有以下兩個:

1、history.pushState() 新增一個歷史記錄;

2、history.replaceState() 直接替換當前歷史記錄;

相同點:可以在不進行刷新的情況下,操作瀏覽器的歷史記錄

    • history.pushState / replaceState 方法接受三個參數,依次為:
      state:一個與指定網址相關的狀態對象,popstate事件觸發時,該對象會傳入回調函數。如果不需要這個對象,此處可以填null。
      title:新頁面的標題,但是所有瀏覽器目前都忽略這個值,因此這里可以填null。
      url:新的網址,必須與當前頁面處在同一個域。瀏覽器的地址欄將顯示這個網址。

      eg:
           window.history.pushState(null, null, path);
           window.history.replaceState(null, null, path);
       
    • history路由模式的實現主要基於以下幾個方面的特性
      1、pushState / replaceState 兩個 API 來操作實現 URL 的變化;
      2、我們可以使用 popstate 事件來監聽 URL 的變化,從而對頁面進行跳轉(渲染);
      3、pushState / replaceState 或 <a> 標簽 並不會觸發 popstate 事件,只有用戶點擊瀏覽器倒退按鈕和前進按鈕,或者使用 JavaScript 調用 backforwardgo
      方法時才會觸發,好在我們可以攔截 pushState/replaceState的調用和<a>標簽的點擊事件來檢測 URL 變化 所以我們需要手動觸發頁面跳轉(渲染); 
    • history模式的問題
      1、history 路由模式雖然好看,但是這種模式要玩兒好,還需要后台配置支持,因為我們的應用是個單頁的客戶端應用,如果后台沒有正確的配置,當用戶在瀏覽器直接訪問一些沒有
      配置的路徑就會返回404,但因為沒有 # 號,所以當用戶刷新頁面之類的操作時,瀏覽器還是會給服務器發送請求。為了避免出現這種情況,所以這個實現需要服務器的支持,
      需要把所有路由都重定向到根頁面;
      2、所以呢,你要在服務端增加一個覆蓋所有情況的候選資源,如果 URL 匹配不到任何靜態資源,應該返回同一個 index.html 頁面,這個頁面就是你app依賴的頁面;
    • 參考鏈接:https://router.vuejs.org/zh/guide/essentials/history-mode.html
    • 實現方案:
      • html代碼:
        <!DOCTYPE html>
        <html lang="en">
        <head>
          <meta charset="UTF-8">
          <meta http-equiv="X-UA-Compatible" content="IE=edge">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <title>前端路由</title>
        </head>
        <body>
          <h1>hello , History router!!!</h1>
          <ul>
            <li><a href="/">turn white</a></li>
            <li><a href="http://localhost:3000/color/blue">turn blue</a></li>
            <li><a href="http://localhost:3000/color/green">turn green</a>
            <li><a href="http://localhost:3000/color/red">turn red</a></li>
            <li><a href="http://localhost:3000/color/orange">turn orange</a>
          </ul>
        
          <script src="./js/history.js"></script>
        </body>
        </html>
      • js代碼
        /**
         * history路由
         */
        class Router {
          constructor() {
            /**
             * 以鍵值對的形式存儲路由
             */
            this.routers = new Object();
          }
        }
        
        /**
         * 監聽頁面的popstate事件
         */
        Router.prototype.bindPopState = function (e) {
          const path = e.state && e.state.path;
          this.routers[path] && this.routers[path]();
        };
        
        /**
         * 將路由的path以及對應的callback函數儲存
         * @param {*} path
         * @param {*} callback
         */
        Router.prototype.route = function (routes) {
          for (let route of routes) {
            this.routers[route.path] = route.callback || function () {};
          }
        };
        
        /**
         * 初始化,直接替換當前歷史紀錄,並用狀態對象進行存儲
         */
        Router.prototype.init = function (path) {
          window.history.replaceState({ path: path }, null, path);
          this.routers[path] && this.routers[path]();
          /**
           * 加入事件監聽
           */
          window.addEventListener("popstate", this.bindPopState.bind(this), false);
        };
        
        /**
         * 更新頁面,新增一個歷史紀錄
         */
        Router.prototype.go = function (path) {
          window.history.pushState({ path: path }, null, path);
          this.routers[path] && this.routers[path]();
        };
        
        const route = new Router();
        route.init(window.location.href);
        const routes = [
          {
            path: "http://localhost:3000/",
            callback: function () {
              let el = document.body;
              el.style.backgroundColor = "#fff";
            },
          },
          {
            path: "http://localhost:3000/color/blue",
            callback: function () {
              let el = document.body;
              el.style.backgroundColor = "blue";
            },
          },
          {
            path: "http://localhost:3000/color/green",
            callback: function () {
              let el = document.body;
              el.style.backgroundColor = "green";
            },
          },
          {
            path: "http://localhost:3000/color/red",
            callback: function () {
              let el = document.body;
              el.style.backgroundColor = "red";
            },
          },
          {
            path: "http://localhost:3000/color/orange",
            callback: function () {
              let el = document.body;
              el.style.backgroundColor = "orange";
            },
          },
        ];
        /**
         * 將hash值與cb綁定
         */
        route.route(routes);
        
        /**
         * a標簽會跳轉頁面,阻止
         */
        window.addEventListener(
          "click",
          function (e) {
            var e = e || window.event;
            var target = e.target || e.srcElement;
            if ((target.tagName = "A")) {
              e.preventDefault();
              route.go(e.target.getAttribute("href"));
            }
          },
          false
        ); 

 二、vue-router出現的問題

  1. vue-router的核心是,通過 vue.use 注冊插件,在插件的 install 方法中獲取用戶配置的 router 對象,當瀏覽器的 URL 發生變化的時候,根據 router 對象匹配相應的路由,獲取組件,並將組件渲染到視圖上;
  2. 如何渲染 router-view 組件?

             

  1. 如何在 install 方法中獲取vue實例上的router屬性???

             

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM