探究前端路由hash和history的实现原理(包教包会)

news2025/2/26 5:53:56

今天我们来讲一讲前端中很重要的一个部分路由(router),想必前端小伙伴对‘路由’一词都不会感到陌生。但是如果哪天面试官问你,能大概说一说前端路由实现原理吗?

你又会如何应对呢?

今天勇宝就带着大家一起来探究一下前端路由,来揭开‘她’的神秘面纱。

另外勇宝的网站已经可以正常访问了:www.iyongbao.cn

文章目录

  • 一、概念
  • 二、路由存在的意义
  • 三、路由的模式(hash和history)
    • 3.1 Hash模式
      • 3.1.1 代码实现
      • 3.1.2 效果展示
      • 3.1.3 小结
    • 3.2 History模式
      • 3.2.1 概念
      • 3.2.2 pushState和popstate
      • 3.2.3 代码实现
      • 3.2.4 小结
  • 四、总结

一、概念

路由这一概念起源于后端(服务器),就比如说我们前端经常会去访问服务器上的一些静态资源(图片、视频)就是我们所说的路由。

其实路由就是用来描述文件路径的一个概念

像前端框架Vue单页应用(SPA)就是借鉴了路由,呈现的效果就是当我们的url地址栏发生变化展示相应的内容。

就好像键值对的key=value,让url组件一一映射。

二、路由存在的意义

那么路由的好处是什么呢?

  • 通过使用路由可以实现:当我们的url发生变化不会引起页面的刷新

    在学习HTML的时候,我们知道点击a标签引起页面的刷新

三、路由的模式(hash和history)

前端的路由跳转模式有两种:hashhistory,是不是想起Vue和#字符了。
下面我们就来探讨一下

3.1 Hash模式

Hash中文名叫(哈希),好像在密码学中总是听到这个词。想起来了,这是一种算法,哈哈。具体原理我不太懂,只知道它会生成一串很长很长的值。比如我们的视频也是有哈希值的。

然后就是我们的浏览器,就拿Vue的router来说,当我们使用hash模式时,#号后面的任意值就是我们的哈希值。

http://www.iyongbao.cn/#/blog

回到刚才的问题:通过修改url的哈希值,并且不会引起页面的刷新。

下面我们就来一起实现一下前端路由Hash模式,这是主要使用到了onhashchange事件来监听url变化

3.1.1 代码实现

  1. 新建index.html
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>前端路由</title>
</head>
<body>
    <!-- 导航 -->
    <div id="nav">
        <a href="#/">首页</a>
        <a href="#/blog">博客</a>
        <a href="#/about">关于</a>
    </div>
    <!-- 内容展示 -->
    <div id="routerView"></div>
</body>
</html>

GIF 2024-2-26 21-03-43.gif

  1. 我们来模拟配置一下每个url的内容
const routes = [
    {
        path: '/',
        component: '我是首页'
    },
    {
        path: '/blog',
        component: '我是博客页'
    },
    {
        path: '/about',
        component: '我是关于页'
    }
];
  1. 下面开始编写逻辑部分

通过onhashchange来监听url的变化,DOM有个location对象,它有一个hash属性,这个值就是我们a标签中的href

window.addEventListener('hashchange', loadPage);

function loadPage () {
    console.log(location.hash);
}

GIF 2024-2-26 21-06-39.gif

  1. 通过routesloadPage修改routerView的内容。
// 通过id获取div元素
const routerView = document.getElementById('routerView');

const routes = [
    {
        path: '/',
        component: '我是首页'
    },
    {
        path: '/blog',
        component: '我是博客页'
    },
    {
        path: '/about',
        component: '我是关于页'
    }
];

window.addEventListener('hashchange', loadPage);

function loadPage() {
    // 1. 获取要跳转的路由 默认值是#/
    let hashVal = location.hash || '#/';
    // 2. 查找符合的路由
    routes.forEach(route => {
        if (`#${route.path}` === hashVal) {
            // 3. 元素显示相应内容
            routerView.innerHTML = route.component;
        }
    });
}
  1. 我们发现手动刷新的时候显示的内容异常,又变成了我是首页。所以我们需要当文档流加载完成后触发一次loadPage方法。
window.addEventListener('DOMContentLoaded', loadPage);

3.1.2 效果展示

GIF 2024-2-26 21-08-25.gif

3.1.3 小结

hash模式最主要的就是使用到onhashchange事件和url后带#

3.2 History模式

histroy和hash不太一样,他就不能通过#字符了,那它是怎么让页面不刷新进行跳转的呢?

注意:hash和history唯一的区别就是一个有#,知识美观上有所影响,估计这个回答大家耳朵也都听的起茧子了。

3.2.1 概念

我查阅了一下MDN对history的解释:

History接口允许操作浏览器的曾经在标签页或者框架里访问的会话历史记录。

这里我们主要使用到pushState方法和popstate监听事件。

3.2.2 pushState和popstate

在 HTML 文档中,history.pushState() 方法向浏览器的会话历史栈增加了一个条目。该方法是异步的。为 popstate 事件增加监听器,以确定导航何时完成。state 参数将在其中可用。

官方说了一大堆,我看不懂,我就说一说的理解:就是浏览器有一个‘仓库’,它可以存储我们访问的路径,通过这个‘仓库’,我们可以正确的进行页面的前进和后退。通过监听popstate可以获取到url的变量值。

3.2.3 代码实现

  1. 还用刚才那个index.html,节省空间哈哈哈。
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>前端路由</title>
</head>
<body>
    <!-- 导航 -->
    <div id="nav">
        <a href="/">首页</a>
        <a href="/blog">博客</a>
        <a href="/about">关于</a>
    </div>
    <!-- 内容展示 -->
    <div id="routerView">我是首页</div>
</body>
</html>

此时注意:我们a标签href是正常的跳转地址,这样会一个问题a标签有默认事件,会进行跳转。我们先解决这个问题。

// 1. 获取a标签集合
const links = document.querySelectorAll('a');

links.forEach(a => {
    a.addEventListener('click', (e) => {
        // 2. 阻止默认事件
        e.preventDefault();
    })
});
  1. pushState参数详解
pushState(state, unused)
pushState(state, unused, url)
  • state: state 对象是一个 JavaScript 对象,其与通过 pushState() 创建的新历史条目相关联。每当用户导航到新的 state,都会触发 popstate 事件,并且该事件的 state 属性包含历史条目 state 对象的副本。
  • unused: 由于历史原因,该参数存在且不能忽略;传递一个空字符串是安全的,以防将来对该方法进行更改。
  • url: 新历史条目的 URL。

请注意,浏览器不会在调用 pushState() 之后尝试加载该 URL,但是它可能会在以后尝试加载该 URL,例如,在用户重启浏览器之后。新 URL 可以不是绝对路径;如果它是相对的,它将相对于当前的 URL 进行解析。新的 URL 必须与当前 URL 同源;否则,pushState() 将抛出异常。如果该参数没有指定,则将其设置为当前文档的 URL。

以上都是MDN的官方解释

  1. 当我们点击a标签后我们需要把href的值通过pushstate存起来。
// 通过getAttribute方法获取a标签href的值
const url = a.getAttribute('href');
  1. push到浏览器‘仓库’中。
pushState(null, '', a.getAttribute('href'));
  1. 我们把上边的代码封装为一个函数
function load () {
    let url = location.pathname

    routes.forEach(route => {
        if (location.pathname === route.path) {
            routerView.innerHTML = route.component;
        }
    })  
}
  1. 文档流加载完成后,初始化我们的a标签
window.addEventListener('DOMContentLoaded', load);
  1. 最后我们来监听popstate
const routes = [
    {
        path: '/',
        component: '我是首页'
    },
    {
        path: '/blog',
        component: '我是博客页'
    },
    {
        path: '/about',
        component: '我是关于页'
    }
];

function render () {
    let url = location.pathname
  
    routes.forEach(route => {
        if (location.pathname === route.path) {
            routerView.innerHTML = route.component;
        }
    })
}

window.addEventListener('popstate', render)

完整代码

<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>前端路由</title>
</head>
<body>
    <!-- 导航 -->
    <div id="nav">
        <a href="/">首页</a>
        <a href="/blog">博客</a>
        <a href="/about">关于</a>
    </div>
    <!-- 内容展示 -->
    <div id="routerView">我是首页</div>
    
    <script>
        const routes = [
            {
                path: '/',
                component: '我是首页'
            },
            {
                path: '/blog',
                component: '我是博客页'
            },
            {
                path: '/about',
                component: '我是关于页'
            }
        ];
        
        const routerView = document.getElementById('routerView');
        
        window.addEventListener('DOMContentLoaded', load);
        window.addEventListener('popstate', render);
        
        function load () {
            let url = location.pathname

            routes.forEach(route => {
                if (location.pathname === route.path) {
                    routerView.innerHTML = route.component;
                }
            })  
        }
        
        function render () {
            let url = location.pathname

            routes.forEach(route => {
                if (location.pathname === route.path) {
                    routerView.innerHTML = route.component;
                }
            })
        }
    </script>
</body>
</html>

3.2.4 小结

history模式大家也可以刷新一下,发现页面报错了(Cannot GET /blog),这个原因和Vue中使用history模式页面刷新报404是一个道理,等以后有时间坐下来和大家聊一聊。

四、总结

通过本章节小伙伴们是不是对前端路由有了一个更加清晰的认识与理解,想必大家再遇到此类面试题时一定可以迎刃而解,给面试官眼前一亮。

另外再来聊一聊我的 个人博客,目前还是起步阶段能还不完善。未来的规划也不太明确,如果小伙伴们有什么好的想法或创意可以私信或者留言。我会把好的想法融入到咱们的博客中。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1477505.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

温暖呵护,宝宝吐奶小贴士

引言 新生儿是我们生命中的宝贝&#xff0c;但在成长过程中&#xff0c;他们可能会经历各种各样的健康问题。其中&#xff0c;吐奶是较为常见的一种情况&#xff0c;让父母们倍感担忧。在这篇文章中&#xff0c;我们将深入探讨新生儿吐奶的注意事项&#xff0c;并分享一些温暖…

Mask Guided Matting via Progressive Refinement network

1.introduction 输入可以是trimap&#xff0c;粗略二进制分割图&#xff0c;低质量alpha&#xff0c; 2.MG Matting 2.1 Progressive refinement network 网络以图像和粗略mask作为输入&#xff0c;并输出抠图 &#xff0c;在解码过程中&#xff0c;RPN在每个特征级别产生一个…

怎么制作文件类型二维码?文件二维码如何加密?

现在将文件转二维码图片后&#xff0c;分享生成二维码来扫码查看或者下载文件的方式&#xff0c;在很多的场景中都有应用。这个方法的优势在于&#xff0c;成本低而且安全性高&#xff0c;有利于用户快速获取内容的速度&#xff0c;有效提高用户体验&#xff0c;而且日常使用的…

《Decoupling Representation and Classifier for Long-Tailed Recognition》阅读笔记

论文标题 《Decoupling Representation and Classifier for Long-Tailed Recognition》 用于长尾识别的解耦表示和分类器 作者 Bingyi Kang、Saining Xie、Marcus Rohrbach、Zhicheng Yan、 Albert Gordo、Jiashi Feng 和 Yannis Kalantidis 来自 Facebook AI 和 新加坡国…

JVM 有哪些垃圾回收器

引言&#xff1a; 如果说垃圾收集算法是内存回收的方法论&#xff0c;那么垃圾收集器就是内存回收的具体实现。下图展示了 7种作用于不同分代的收集器&#xff0c;其中用于回收新生代的收集器包括Serial、PraNew、Parallel Scavenge&#xff0c;回收老年代的收集器包括Serial …

1906_ AMBA_高级MCU总线架构

1906_ AMBA_高级MCU总线架构 全部学习汇总&#xff1a; g_arm_cores: ARM内核的学习笔记 (gitee.com) 在看内核相关的文件的时候看到了AMBA这个缩写&#xff0c;查了一下具体的概念。这个其实是一个总线架构&#xff0c;应该是ARM设计的。我找到了相关的介绍网页&#xff1a; A…

了解GPT:ChatGPT的终极指南

在人工智能&#xff08;AI&#xff09;的世界里&#xff0c;有一颗冉冉升起的新星正在革命性地改变我们与机器的交互方式&#xff1a;ChatGPT。在本文中&#xff0c;我们将深入研究什么是ChatGPT&#xff0c;为什么底层技术GPT如此强大&#xff0c;以及它是如何实现其卓越功能的…

激光打标机在塑料行业的高精度标记

随着科技的不断发展&#xff0c;激光打标机在塑料行业中的应用越来越广泛。这种高精度的标记技术为塑料产品提供了持久、清晰、可追溯的标识&#xff0c;满足了生产过程中的各种需求。 首先&#xff0c;激光打标机具有高精度的标记能力。这种技术利用激光束在塑料表面进行刻划&…

掌握这个防水浸技巧,工作中优势真的大很多!

随着科技的不断发展&#xff0c;水浸监控系统在各个领域中变得越来越重要。无论是工业、商业还是居住环境&#xff0c;及时发现并有效处理水浸问题对于维护设备、减少损失、确保安全至关重要。 水浸监控系统通过及时检测水浸情况&#xff0c;提供实时报警和监控&#xff0c;为用…

Python + Selenium —— 键盘操作!

Keys 类对键盘按键进行了定义&#xff0c;结合 send_keys() 方法可以向页面元素发送各种键盘按键。 比如在京东首页的搜索框&#xff0c;输入查询的内容后&#xff0c;再按一次回车。这是我们大部分人操作搜索的过程。 from selenium.webdriver.common.keys import Keys # 引…

【web APIs】6、(学习笔记)有案例!

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、正则表达式正则基本使用元字符边界符量词范围字符类 二、替换和修饰符三、正则插件change 事件判断是否有类 四、案例举例学生就业信息表用户注册界面用户登…

感受指针--初使用

程序首先定义了一个整数指针p和一个整数j&#xff0c;并给j赋值为10。 然后&#xff0c;程序将j的地址赋给p&#xff0c;使p指向j。 程序接着打印p&#xff08;即j的地址&#xff09;的十六进制表示。 接着&#xff0c;程序打印p本身的地址的十六进制表示。 最后&#xff0…

使用面向对象思想去封装实现canvas功能

前言 各种插件/库和一些常规的业务代码,最大的区别就在于编程的思路与方法。 比如我们现在想写一段业务代码,使用js实现一个矩形,那很简单,几行代码就可以了 const canvas document.getElementById(canvas)const mode canvas.getContext(2d)mode.rect(200,200,200,200)mode…

内网渗透-DC-9靶机渗透

攻击机&#xff1a;kali 192.168.236.137 目标机&#xff1a;dc-9 192.168.236.138 一、信息收集 1.使用arp-scan -l和nmap进行主机发现和端口信息收集 nmap -sS -T5 --min-rate 10000 192.168.236.138 -sC -p- 发现22端口被阻塞 2.whatweb收集一下cms指纹信息 what http…

无人驾驶决策规划技术理论(一)

本博客记录学习李柏老师所著的决策规划技术理论与实践中的关键知识&#xff0c;想以此作为自己学习的框架&#xff0c;方便自己日后回顾。 1 非结构化场景决策规划方法 场景&#xff1a;港口、停车场的泊车附近区域、偏僻道路、野外 特色&#xff1a;障碍物位置杂乱无章 决策…

unity使用Registry类将指定内容写入注册表

遇到一个新需求&#xff0c;在exe执行初期把指定内容写入注册表&#xff0c;Playerprefs固然可以写入&#xff0c;但是小白不知道怎么利用Playerprefs写入DWORD类型的数据&#xff0c;因此使用了Registry类 一. 对注册表中键的访问 注册表中共可分为五类 一般在操作时&#…

个人如何合法自建服务器?

随着互联网技术的不断发展&#xff0c;越来越多的人开始考虑自建服务器&#xff0c;以满足自己的需求。但是&#xff0c;在自建服务器之前&#xff0c;必须了解相关的法律法规和规定&#xff0c;以确保自己的行为合法合规。本文将介绍个人如何合法自建服务器&#xff0c;以供参…

【Oracle】玩转Oracle数据库(七):RMAN恢复管理器

前言 嘿&#xff0c;数据库大魔法师们&#xff01;准备好迎接新的技术大招了吗&#xff1f;今天我们要探索的是Oracle数据库中的神奇利器——RMAN恢复管理器&#xff01;&#x1f6e1;️&#x1f4be; 在这篇博文【Oracle】玩转Oracle数据库&#xff08;七&#xff09;&#xf…

单片机SWJ 调试端口(SW-DP和JTAG)、SWD下载电路

单片机下载接口是指用于将编写好的程序代码下载到单片机芯片中的接口。常见的单片机下载接口包括以下几种&#xff1a; 1. **串口下载接口**&#xff1a;通过串口&#xff08;如UART或RS-232接口&#xff09;与计算机或下载器相连&#xff0c;将程序代码通过串口传输到单片机内…

云里物里轻薄系列电子价签,如何革新零售?

云里物里的DS轻薄系列电子价签&#xff0c;凭借轻巧外观和强劲性能&#xff0c;为零售行业提供了更便捷的商品改价方案。这不仅是对纸质价标的替代&#xff0c;更以其安全性和可持续发展性&#xff0c;实现对零售行业的效率升级&#xff0c;让商家们轻松迎接数字化时代的挑战&a…