专栏名称: 前端大全
分享 Web 前端相关的技术文章、工具资源、精选课程、热点资讯
目录
相关文章推荐
营养师顾中一  ·  香肠、火腿肠、火腿有什么区别? ·  昨天  
丁香生活研究所  ·  长得差不多,热量差一倍!10 ... ·  1 周前  
营养师顾中一  ·  暴饮暴食,会把胃撑大吗?还能饿小吗? ·  4 天前  
51好读  ›  专栏  ›  前端大全

面试官:谈谈前端路由的实现原理【hash&history】

前端大全  · 公众号  ·  · 2024-05-17 08:19

正文

作者:Dolphin_海豚

https://juejin.cn/post/7321049431489544229

今天我们来聊一聊前端路由。

当谈到前端路由时,指的是在前端应用中管理页面导航和URL的机制。前端路由使得单页应用(Single-Page Application,SPA)能够在用户与应用交互时动态地加载不同的视图,而无需每次都重新加载整个页面。

在前端开发中,常用的前端路由库有很多,比如React Router、Vue Router和Angular Router等。这些库提供了一组API和组件,用于定义路由规则、处理导航事件和渲染相应的视图。

简单了解前端路由后,那么前端路由实现的原理是什么呢?

请看今天的分享:


vue-router是前端路由,但是前端路由不是vue-router,这是个包含关系

路由

路由一词最早来自服务器,和前端没有关系。当你想要从服务器中读取某个盘的文件,这个文件的路径就是路由。也就是说 「路由是服务器端用来描述路径的,或者是说url和文件的映射关系」

后来因为前端的SPA单页应用,前端也借鉴了路由这个概念。浏览器的url变了需要映射到页面的某个组件,url变了需要展示某个组件。/home和Home.vue,/about和About.vue就是一一映射的关系。 「前端借鉴路由的称呼来描述url和组件的映射关系」 。这个时候你就想起来router中index.js文件中,一个path对应一个component,也就是一个路径对应一个组件

实现路由需要解决的问题

  1. 如何修改url还不引起页面的刷新
  2. 如何知道url变化了

若是能解决这两个问题就可以实现前端路由了。

哈希Hash

哈希是一种值,按照某种规则生成的一串值,用来代表一个唯一的文件,文件名后加一个哈希值,可以看到文件是否被修改过。

在浏览器中也有hash这个概念,url中接一个 # # 后的值就是哈希值,按道理url变了,页面一定会刷新,但是哈希是个特例,放个哈希值就是不会刷新页面,这样,我们就解决了第一个问题,修改url不引起页面的刷新

「在浏览器url后加个哈希值,哈希值的变更不会引起浏览器页面的刷新」

下面利用哈希模式实现路由

哈希手搓一个路由

我们新建一个hash.html文件,放两个a标签,但是a标签有个机制,就是点击必定会引起页面的刷新。但浏览器的机制是哈希值的变更不会引起页面刷新,所以地址放哈希值可以解决这一问题

<ul>
    <li><a href="#/home">首页a>li> 
    <li><a href="#/about">关于a>li>
ul>

<div id="routeView">
        

div>

现在模拟一个场景,如果点击首页, routeView 容器展示首页的内容,点击关于 routeView 容器展示关于页面的内容,如果能够实现,路由就可以实现了

自行封装一个路由,先写一个路由的映射关系

<script>
    const routes = [
        {
            path'#/home',
            component'首页内容'
        },
        {
            path'#/about',
            component'关于页面内容'
        }
    ]
script>

点击首页,展示 首页内容 ,点击关于,展示 关于首页内容

接下来的事情就是点击url,我们需要知道url的变化。我们不可能给按钮添加一个点击事件,如果项目大起来,按钮很多,每次点击一个按钮都判断一次url的变化,会非常的不优雅。

js自带一个 hashchange 事件,它可以自动监听hash值的变更。当我们点击首页的时候,下面的代码都会执行一次,因为hash值变了

window.addEventListener('hashchange', () => {
 console.log('changed')
})

这样,第二个问题我们已经解决了。非常之简单!

我们现在把监听器的回调函数写出来,拿到当前的哈希值,去对应component。

window.addEventListener('hashchange', onHashChange)

function onHashChange({
 console.log(location)
}

location代表window窗口的url,我们运行打印这个location看看

1.png

看到没有,里面刚好有个hash值,我们可以把这个拿出来去对应component!

这个时候直接去数组中匹配就可以,forEach遍历

function onHashChange() {
console.log(location)
routes.forEach((item, index) => {
if(item.path === location.hash) {
routeView.innerHTML = item.component
}
})
}

当然,记得拿到routeView的dom结构

这样写会有个问题,就是页面刚加载完毕的时候不会去加载当前的路由,想要hashchange在页面初次加载的时候触发一次,那就给一个监听dom结构的事件,dom一出来就会执行,也就是说页面加载完毕就调用一次hashchange

window.addEventListener('DOMContentLoaded', onHashChange)

好了,最终的hash.html如下

<body>
    
    <ul>
        <li><a href="#/home">首页a>li> 
        <li><a href="#/about">关于a>li>
        
    ul>

    <div id="routeView">
        

    div>
    <script>
        const routes = [
            {
                path'#/home',
                component'首  容'
            },
            {
                path'#/about',
                component'关于页面内容'
            }
        ]
        
        const routeView = document.getElementById('routeView')
        window.addEventListener('DOMContentLoaded', onHashChange) // 与vue的声明周期一个道理,dom一加载完毕就触发
        window.addEventListener('hashchange', onHashChange)
        
        function onHashChange({
            console.log(location) // url详情,里面就有个hash值  liveserver可以帮你把html跑成服务器
            routes.forEach((item, index) => {
                if(item.path === location.hash) {
                    routeView.innerHTML = item.component
                }
            })
        }
    
script>
body>

其实这就是vue-router中两种模式之一哈希模式,哈希模式就是这样是实现的。

修改地址栏

  1. a标签
  2. 浏览器前进后退
  3. window.location

以上方式导致url变更都会触发hashchange事件。

那问题来了,history模式没有哈希是如何实现的呢?没有哈希值a标签一定会引起页面的刷新,如何解决?我们继续看下去

history用得更多,二者没有本质区别,仅仅是因为哈希模式的url多了个#很丑,所以用的少

history手搓一个路由

我们先看下history在mdn中的介绍

History - Web API 接口参考 | MDN (mozilla.org)(https://developer.mozilla.org/zh-CN/docs/Web/API/History)

文档中介绍:history接口允许操作浏览器的曾经在标签页或者框架里访问的会话历史记录

我们重点看一个history自带的方法 pushState


它可以修改url且不引起页面的刷新

浏览器中有个 会话历史栈 ,它可以维护你的访问路径,有了这个你返回就可以按照栈的顺序进行前进回退。

pushState 提到了 popState ,他是靠 popState 监听url的改变的,并且仅当浏览器前进后退时生效

既然如此,我们现在开始手搓

同样是上面的情景,两个a标签,一个首页,一个关于页面。

<ul>
    <li><a href="/home">首页a>li> 
    <li><a href="/about">关于a>li>
ul>

<div id="routeView">

给个url和组件的对应关系数组,已经不用哈希了

<script>
    const routes = [
        {
            path'/home',
            component'首页内容'
        },
        {
            path'/about',
            component'

关于页面内容

'

        }
 ]
script>

a标签有个默认的页面跳转效果,既然现在不用哈希,我们就需要自己把a标签的页面跳转刷新效果干掉

先拿到所有的a标签

const links = document.querySelectorAll('li a')

再去禁用掉默认的跳转行为,它跳转一定会带来刷新,要干掉它

links.forEach(a => {
 a.addEventListener('click', (e) => {
  console.log(e)
  e.preventDefault() // 阻止a的跳转行为
 })
})

我们可以打印看看这个事件参数,顺着原型链找到event对象,里面有个preventDefault,这个就是禁用a标签默认的跳转行为


接下来添加一个可以修改url又不引起页面刷新的方法,就是pushState,具体用法查看mdn

他有三个参数,第一个参数是JavaScript对象,一般不需要,给个null就好,第二个参数由于历史原因,写个空字符,不写会有问题,第三个参数是新的url

新的url肯定是点了什么放什么url,所以我需要读取到a标签的href值

a.getAttribute('href')

以上方法是核心,这里已经实现了哈希一样的效果,并且没有难看的 # pushState 的核心原理就是它会往浏览器的历史栈中塞一个值进去,让浏览器显示新的值,并且不引起页面的刷新

接下来就是要去感知到url的变化,去一一对应组件的展示

我们写一个函数,来实现这个功能。还是一样的,先拿到当前的浏览器地址

location.pathname

然后再进行遍历,去添加组件







请到「今天看啥」查看全文