前端路由大揭秘:Hash vs History

序言

在前端世界中,我们经常听到关于路由的讨论,而服务器端的大佬们似乎早已熟知"路径"的奥秘,用其来描述路径。不过,在前端领域,我们借用了这个名词,并用它来描述 URL组件的那种特殊的映射关系。今天,让我们揭开前端路由的神秘面纱,看看如何在浏览器中实现路由的魔法变换。

实现路由需要解决的问题

在解决问题之前,我们首先要了解问题是什么。实现路由,其实就是在不引起页面刷新的情况下,巧妙地改变 URL,并且需要有一种方法来知道 URL 发生了变化从而可以将特定的组件渲染到相应的页面中。 听起来容易,但细细琢磨,里面还是有点门道的。

实现路由的"大问题":

  1. 如何修改URL,还不引起页面的刷新

  2. 如何知道URL变化了

如何解决这两个大问题?

Hash

你有没有注意到,有些网址最后面会跟着一个 # 加上一串字符?这个字符其实是 URL 的"小尾巴",也就是 hash

不同于引起页面刷新的完整 URL 改变,只修改 hash 部分是不会刷新页面的。 这就好比是在 URL 后面加了一条小小的船尾,船在航行,但船身却依然安稳。

html 复制代码
<ul>
   <li><a href="#/home">首页</a></li>
   <li><a href="#/about">关于</a></li>
</ul>

如果我们在页面中放两个超链接,并且在路径前加一个#会发生什么呢?

当我们点击这两个超链接时,页面的URL会发生改变但是不会并刷新页面。

我们解决了第一个问题,但是我们如何知道页面的URL发生了变化呢?

欸,有朋友可能会说了,既然我们是因为点击触发的页面跳转,那我们监听点击事件不就行了?

其实呢,也不是不行,但是!!! 如果我们页面中有一百多个类似于a标签这样的跳转链接,你是不是一个个加逻辑处理?

没有其他办法了吗? 有!

在原生浏览器中有一个事件叫做hashchange:当页面的 hash 发生变化时,浏览器会触发 hashchange 事件,从而允许我们添加相关的操作。

到这里我们就通过Hash解决了这两个问题。

History

而另一位帮手则是 history。这位大神提供了一个 pushState 方法,可以轻松修改 URL 而不引起页面刷新。有点像是在 URL 上粘贴一张贴纸,页面依然是原来的页面,只是 URL 发生了点小改变。

另外,history 还贴心地提供了一个 popState 事件,专门用于捕捉浏览器前进和后退的时候,告诉我们 URL 又发生了变化。这就好比是浏览器在推一下,告诉你:"快,看看 URL 变啦!"

Hash vs History

在上面我们已经了解到解决问题的两种实现方法,接下来让我们开启实战,从下面这两段代码中分别深入了解 Hash vs History他们是如何实现路由的,并细致分析。

用Hash实现路由

html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Hash</title>
</head>

<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: 'about page'
            }
        ]

        const routeView = document.getElementById('routeView')

        window.addEventListener('DOMContentLoaded', onHashChange)
        window.addEventListener('hashchange', onHashChange)

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

        
    </script>
</body>

</html>

页面效果如下

解释一下代码: 类似于VUE中的路由,我们在JS里定义了一个数组routes,并在数组中添加多个对象,存放多个页面的URL和组件内容,这里我们简单一点就给个字符串。所以我们现在要做的就是捕获页面发生跳转同时将相应的组件映射到页面中去。

1. 首先我们获取到页面div容器的DOM结构

js 复制代码
const routeView = document.getElementById('routeView')

2. 当页面初次加载或者URL发生改变时将对应的URL映射的组件添加到页面中

js 复制代码
window.addEventListener('DOMContentLoaded', onHashChange)
window.addEventListener('hashchange', onHashChange)
  • DOMContentLoaded事件表示文档已经完全加载和解析,不包括外部资源如图片和样式表。我们这行代码添加了一个事件监听器,当整个HTML文档加载完成时触发DOMContentLoaded事件,然后调用onHashChange函数。
  • hashchange事件表示浏览器地址栏中的哈希部分发生变化。这行代码添加了另一个事件监听器,当浏览器的URL中的哈希部分(即#后面的部分)发生变化时触发hashchange事件,同样调用onHashChange函数。

onHashChange函数

js 复制代码
function onHashChange() {
            console.log(location.hash);
            routes.forEach( item => {
                if (item.path === location.hash) {
                    routeView.innerHTML = item.component
                }
            })
        }

首先我们遍历route拿到里面每一项,我们在浏览器里面可以看到每一项里面有一个属性叫做path就代表其URL ,欸这不就是我们需要用到的吗,所以我们就拿出这个属性与页面的URL对比,如果相同则将这一item中的component添加到容器中就OK啦~

用History实现路由

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>History</title>
</head>
<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: '<h2>首页页面内容</h2>'
      },
      {
        path: '/about',
        component: '<h3>about page</h3>'
      }
    ]

    const routeView = document.getElementById('routeView')

    window.addEventListener('DOMContentLoaded', onLoad)
    window.addEventListener('popstate', onPopState)
    
    function onLoad() {
      onPopState()
      const links = document.querySelectorAll('li a')
      links.forEach(a => {
        a.addEventListener('click', (e) =>{
          e.preventDefault()  // 阻止了a标签的默认跳转行为
          // 添加一个可以修改url又不造成页面刷新
          history.pushState(null, '', a.getAttribute('href'))
          // 映射对应的dom
          onPopState()
        })
      })
    }

    function onPopState() {
      console.log(location.pathname);
      routes.forEach(item => {
        if (item.path === location.pathname) {
          routeView.innerHTML = item.component
        }
      })
    }

  </script>
</body>
</html>

页面效果如下

我们发现我不添加#也可以实现同样的效果,那接下来让我们分析一下这份代码吧:

1. 首先我们获取到页面div容器的DOM结构

js 复制代码
const routeView = document.getElementById('routeView')

2. 当页面初次加载或者URL发生改变时触发事件监听

js 复制代码
window.addEventListener('DOMContentLoaded', onLoad)
window.addEventListener('popstate', onPopState)
  • window.addEventListener('DOMContentLoaded', onLoad):与之前的类似,在页面加载完毕时,将触发"DOMContentLoaded"事件,执行onLoad函数。

  • window.addEventListener('popstate', onPopState)history提供了一个popState事件,仅当浏览器前进后退时生效。 而在浏览器的URL发生变化时(例如用户点击了后退或前进按钮),将触发"popstate"事件,执行onPopState函数。

3. onPopState函数

js 复制代码
function onPopState() {
      console.log(location.pathname);
      routes.forEach(item => {
        if (item.path === location.pathname) {
          routeView.innerHTML = item.component
        }
      })
    }

我们首先打印console.log(location.pathname);从结果可以看出他是跳转页面的URL。

欸!这个元素不就是我们需要的吗。

所以onPopState函数遍历已定义的路由,将当前location.pathname与路由路径进行比较。当匹配到路径时,我们更新routeView的内容为对应路由的组件。

4. onLoad函数

js 复制代码
function onLoad() {
      onPopState()
      const links = document.querySelectorAll('li a')
      links.forEach(a => {
        a.addEventListener('click', (e) =>{
          e.preventDefault()  // 阻止了a标签的默认跳转行为
          // 添加一个可以修改url又不造成页面刷新
          history.pushState(null, '', a.getAttribute('href'))
          // 映射对应的dom
          onPopState()
        })
      })
    }

onLoad函数内,我们首先执行onPopState()渲染对应的页面。然后为所有的锚点(<a>)元素添加了事件监听器。因为我们不能让锚点(<a>)元素发生刷新页面的行为,所以这里我们阻止这些监听器锚点的默认行为(跳转到新页面) ,而是使用history.pushState()方法更新URL,这个方法可以修改URL且不引起页面刷新。

5. History API的使用

js 复制代码
history.pushState(null, '', a.getAttribute('href'))

通过history.pushState方法,我们能够向浏览器的会话历史添加新条目,从而改变地址栏中显示的URL,而无需触发完整的页面刷新。 不清楚的小伙伴可以去了解MDN文档中对History:pushState() 方法的说明。一言以蔽之,history 提供了一个pushState方法可以修改URL且不引起页面刷新。

结语

那么到了这里我们今天的文章就结束啦~

创作不易,如果感觉这个文章对你有帮助的话,点个赞吧♥

更多内容【AIGC】如何使用Autogen库打造智能对话体验?请看保姆级教学

ReacheMe : GitHub Gitee

相关推荐
gqkmiss24 分钟前
Chrome 浏览器 131 版本开发者工具(DevTools)更新内容
前端·chrome·浏览器·chrome devtools
Summer不秃29 分钟前
Flutter之使用mqtt进行连接和信息传输的使用案例
前端·flutter
旭日猎鹰33 分钟前
Flutter踩坑记录(二)-- GestureDetector+Expanded点击无效果
前端·javascript·flutter
Viktor_Ye40 分钟前
高效集成易快报与金蝶应付单的方案
java·前端·数据库
hummhumm42 分钟前
第 25 章 - Golang 项目结构
java·开发语言·前端·后端·python·elasticsearch·golang
乐闻x1 小时前
Vue.js 性能优化指南:掌握 keep-alive 的使用技巧
前端·vue.js·性能优化
一条晒干的咸魚1 小时前
【Web前端】创建我的第一个 Web 表单
服务器·前端·javascript·json·对象·表单
花海少爷1 小时前
第十章 JavaScript的应用课后习题
开发语言·javascript·ecmascript
Amd7941 小时前
Nuxt.js 应用中的 webpack:compiled 事件钩子
前端·webpack·开发·编译·nuxt.js·事件·钩子
生椰拿铁You2 小时前
09 —— Webpack搭建开发环境
前端·webpack·node.js