• 原生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属性???

                 

    北栀女孩儿
  • 相关阅读:
    keepalived排错
    shell脚本记录
    mysql(mariadb)主从配置
    添加硬盘设备
    天数
    centos7网络配置总结
    Keepalibed监控nginx
    zabbix安装
    基于JWT的web api身份验证及跨域调用
    C# webapi 权限验证
  • 原文地址:https://www.cnblogs.com/wxh0929/p/15049790.html
Copyright © 2020-2023  润新知