😁深入JS(五): 一文让你完全理解 hash 与 history 路由,手写前端路由

1、前端路由

在 Web 前端单页面应用 SPA中,路由是描述 URLUI 之间的映射关系,这种映射是单向的,即 URL 的改变会引起 UI 更新,无需刷新页面

2、两种前端路由

实现前端路由,需要解决两个核心问题

  1. 如何改变 URL 却不引起页面刷新?
  2. 如何监测 URL 变化?

在前端路由的实现模式有两种模式,hashhistory 模式

2.1、hash 模式

  1. hash 是 url 中 hash(#) 及后面的部分,改变url中的hash部分不会引起页面的刷新
  2. 通过 hashchange 事件监听 URL 的改变。 3. 改变 URL 的方式 : 浏览器导航栏的前进后退、通过<a>标签、通过window.location 并触发hashchange事件

2.2、history模式

  • history 提供了 pushStatereplaceState 两个方法,这两个方法改变 URL 的 path 部分不会引起页面刷新

  • 通过 popchange 事件监听 URL 的改变。

  • 只在导航栏的前进后退改变 URL 时会触发popstate事件

  • 通过<a>标签和pushState/replaceState不会触发popstate方法。

但我们可以拦截<a>标签的点击事件和pushState/replaceState的调用来检测 URL 变化,也是可以达到监听 URL 的变化,相对hashchange显得略微复杂

3.实现前端路由

3.1、基于 hash 实现

js 复制代码
  <ul>
        <!-- 定义路由 -->
        <li><a href="#/home">home</a></li>
        <li><a href="#/about">about</a></li>
        <!-- 渲染路由对应的 UI -->
        <div id="routeView"></div>
    </ul>
    <script>
        // 页面加载完不会触发 hashchange,这里主动触发一次 hashchange 事件
        window.addEventListener('DOMContentLoaded', onHashChange)
        // 监听路由变化
        window.addEventListener('hashchange', onHashChange)
        // 路由视图
        let routerView = document.querySelector('#routeView')
        // 路由变化时,根据路由渲染对应 UI
        function onHashChange() {
            switch (location.hash) {
                case '#/home':
                    routerView.innerHTML = 'Home'
                    return
                case '#/about':
                    routerView.innerHTML = 'About'
                    return
                default:
                    routerView.innerHTML = 'Not Found'; return;
            }
        }
    </script>

3.2、基与history实现

history 模式

  1. history 提供了 pushStatereplaceState 两个方法,这两个方法改变 URL 的 path 部分不会引起页面刷新
  2. 通过 popstate 事件监听 URL 的改变。
  3. 只有浏览器导航栏的前进后退改变 URL 时会触发popstate事件

History.pushState()History.replaceState()

  • object:是一个对象,通过 pushState 方法可以将该对象内容传递到新页面中。在
  • title:不用
  • url:新的网址,必须与当前页面处在同一个域。
js 复制代码
history.pushState({ foo: 'bar' }, '', '2.html'); 
console.log(history.state) // {foo: "bar"}
//https://www.douyin.com/?recommend=1
//https://www.douyin.com/2.html

popstate 每当 history 对象出现变化时,就会触发 popstate 事件。

js 复制代码
window.addEventListener('popstate', function(e) {
    //e.state 相当于 history.state
    console.log('state: ' + JSON.stringify(e.state));
    console.log(history.state);
});

基于 history 实现

因为 history 模式下,<a>标签和pushState/replaceState不会触发popstate方法,我们需要对<a>的跳转和pushState/replaceState做特殊处理。

  • <a>作点击事件,禁用默认行为,调用pushState方法并手动触发popstate的监听事件
  • pushState/replaceState可以重写 history 的方法并通过派发事件能够监听对应事件

重写pushState

js 复制代码
      var _wr = function (type) {
        var orig = history[type];
        return function () {
          var e = new Event(type);
          e.arguments = arguments;
          var rv = orig.apply(this, arguments);
          window.dispatchEvent(e);
          return rv;
        };
      };
      history.pushState = _wr("pushState");
      //每次调用`history.pushState`时,都会触发一个名为`pushState`的自定义事件。
      window.addEventListener("pushState", function (e) {
        onPopState();
      });

重写a

js 复制代码
   var linkList = document.querySelectorAll("a[href]");
      linkList.forEach((el) =>
        el.addEventListener("click", function (e) {
          e.preventDefault();
          history.pushState(null, "", el.getAttribute("href"));
        })
      );
    }
js 复制代码
<!DOCTYPE html>
<html>
 <head>
   <title>Parcel Sandbox</title>
   <meta charset="UTF-8" />
   <script>
     var _wr = function (type) {
       var orig = history[type];
       return function () {
         var e = new Event(type);
         e.arguments = arguments;
         var rv = orig.apply(this, arguments);
         window.dispatchEvent(e);
         return rv;
       };
     };
     history.pushState = _wr("pushState");
   </script>
 </head>

 <body>
   <ul>
     <!-- 定义路由 -->
     <li><a href="/home">home</a></li>
     <li><a href="/about">about</a></li>

     <!-- 渲染路由对应的 UI -->
     <div id="routeView"></div>
   </ul>

   <script>
     var routeView = null;

     function onLoad() {
       routerView = document.querySelector("#routeView");
       onPopState();

       // 拦截 <a> 标签点击事件默认行为
       // 点击时使用 pushState 修改 URL并更新手动 UI,从而实现点击链接更新 URL 和 UI 的效果。
       var linkList = document.querySelectorAll("a[href]");
       linkList.forEach((el) =>
         el.addEventListener("click", function (e) {
           e.preventDefault();
           history.pushState(null, "", el.getAttribute("href"));
         })
       );
     }
     // Use it like this:
     window.addEventListener("pushState", function (e) {
       onPopState();
     });
     // 页面加载完不会触发 hashchange,这里主动触发一次 hashchange 事件,处理默认hash
     window.addEventListener("DOMContentLoaded", onLoad);
     // 监听路由变化
     window.addEventListener("popstate", onPopState);
     // 路由变化时,根据路由渲染对应 UI
     function onPopState() {
       switch (location.pathname) {
         case "/home":
           routerView.innerHTML = "This is Home";
           return;
         case "/about":
           routerView.innerHTML = "This is About";
           return;
         case "/list":
           routerView.innerHTML = "This is List";
           return;
         default:
           routerView.innerHTML = "Not Found";
           return;
       }
     }
   </script>
 </body>
</html>
相关推荐
一斤代码1 小时前
vue3 下载图片(标签内容可转图)
前端·javascript·vue
中微子1 小时前
React Router 源码深度剖析解决面试中的深层次问题
前端·react.js
光影少年1 小时前
从前端转go开发的学习路线
前端·学习·golang
中微子1 小时前
React Router 面试指南:从基础到实战
前端·react.js·前端框架
3Katrina1 小时前
深入理解 useLayoutEffect:解决 UI "闪烁"问题的利器
前端·javascript·面试
前端_学习之路2 小时前
React--Fiber 架构
前端·react.js·架构
伍哥的传说3 小时前
React 实现五子棋人机对战小游戏
前端·javascript·react.js·前端框架·node.js·ecmascript·js
qq_424409193 小时前
uniapp的app项目,某个页面长时间无操作,返回首页
前端·vue.js·uni-app
我在北京coding3 小时前
element el-table渲染二维对象数组
前端·javascript·vue.js
布兰妮甜3 小时前
Vue+ElementUI聊天室开发指南
前端·javascript·vue.js·elementui