前端路由扫盲篇:Hash 模式和 History 模式到底怎么选?

如果把单页应用(SPA)比作一座功能齐备的大别墅,那么前端路由就是这座别墅里的"导览员"。当你在地址栏输入 URL 时,导览员就会根据地址,精准地把对应的页面组件搬到你的眼前。在这个分发过程中,主要有两位"导览员"可供选择:老成持重的 Hash 模式和现代时髦的 History 模式。很多初学者对它们的概念一知半解,面试时被问到区别也往往卡壳。今天,我们就来一场前端路由的扫盲,看看这两大模式各自的底牌是什么,在实际开发中又该如何做出抉择。

什么是路由

路由,本质上就是描述服务器上资源路径的一串地址。当你在浏览器里敲下一个 URL,后端会根据这个路径找到对应的资源,然后返回给你。

但到了前端,事情变得有点不一样。

前端路由:让 URL 和组件建立映射

现在的主流架构是 SPA(单页应用) ------整个应用只有一个 HTML 页面,切换"页面"时不再向服务端发起整页请求,而是由 JavaScript 动态替换当前页面的内容。

这时候问题就来了:页面没刷新,但用户看到的却是不同的"页面",那 URL 怎么办?

前端路由要解决的核心矛盾就是:

页面不刷新,但 URL 要变,而且浏览器前进后退要正常工作。

所以前端路由的本质,就是构建浏览器 URL 地址和组件之间的映射关系------URL 变了,渲染对应的组件,页面本身不刷新。

怎么实现"改 URL 但不刷新页面"

浏览器给了我们两条路。

一、Hash 模式

在浏览器眼里,URL 中 # 后面的部分叫做 hash (哈希值 / 片段标识符)。关键点在于:hash 的变更不会触发页面刷新,这是浏览器天生的行为。

例子:http://127.0.0.1:5500/hash.html#/home

这个链接中的 #/home 就是hsah值。

知道了这些内容那整个流程就清晰了:

  1. 监听 hashchange 事件
  2. 当url变更,hashchange 事件就会触发,通过location.hash获取当前hash值
  3. 拿着这个 hash 值去事先定义好的映射表里查找对应的组件,渲染出来。
javascript 复制代码
// 定义路由映射表
const routes = [
            {
                path: '/home',
                component: () => {
                    return '<h2>首页页面</h2>'
                }
            },
            {
                path: '/hot',
                component: () => {
                    return '<h3>hot page</h3>'
                }
            }
        ]
        
// 监听 hash 变化
window.addEventListener('hashchange', () => {
// 在routes中找到对应的path,并渲染对应的组件
    rederView(location.hash)
})
function rederView (hashVal){
    for(let i =0;i<routes.length;i++){
        if('#'+routes[i].path == hashVal){
            document.getElementById('root').innerHTML = routes[i].component()
           }
      }
}
        

优点:兼容性好,实现简单,不需要服务端配合。

缺点:URL 里永远带着一个 #,不够优雅。

二、History 模式

HTML5 给 window.history 对象新增了几个 API,让前端有能力直接操控浏览器历史记录,而不触发页面刷新:

scss 复制代码
pushState() 向浏览器的历史记录栈中添加一条记录
popState() 从浏览器的历史记录栈中出栈一条记录
replaceState() 替换浏览器的历史记录栈中的栈顶记录

pushStatereplaceState 都能直接修改浏览器地址栏的 URL,并且不会导致页面刷新------这正是我们需要的。

那前进后退怎么办?浏览器在用户点击前进或后退按钮时会触发 popstate 事件,我们只需要监听它,就能感知用户的历史导航行为,然后渲染对应组件。

javascript 复制代码
        const routes = [
            {
                path: '/home',
                component: () => {
                    return '<h2>history 首页页面</h2>'
                }
            },
            {
                path: '/hot',
                component: () => {
                    return '<h3>history hot page</h3>'
                }
            }
        ]

        window.addEventListener('DOMContentLoaded', () => {
            // 默认展示
            onLoad()
        })

        window.addEventListener('popstate',()=>{   // 监听浏览器的前进后退事件
            renderView(location.pathname)
        })

        function onLoad() {  // 路径变更渲染对应组件
            let linkList = document.querySelectorAll('a[href]')
            // console.log(linkList);
            linkList.forEach(el => {
                el.addEventListener('click', (e) => {
                    e.preventDefault()   // 阻止默认行为

                    history.pushState(null, '', el.getAttribute('href'))  // 进入浏览器的缓存栈

                    renderView(location.pathname)
                })
            })

        }

        function renderView(pathname) {
            routes.forEach((item) => {
                if (pathname == item.path) {
                    document.getElementById('root').innerHTML = item.component()
                }
            })
        }

优点:URL 干净,没有 #,和传统多页应用的 URL 完全一致。

缺点:需要服务端配合------把所有路径都 fallback 到 index.html,否则用户直接访问或刷新深层路径会 404

总结

Hash 模式简单粗暴,靠的是浏览器的"历史遗留特性";History 模式优雅现代,靠的是 HTML5 新增的 API。理解了这两套机制,你就掌握了前端路由的底层原理------不管是 Vue Router 还是 React Router,本质上都是在这两个模式之上做了一层封装和增强。

相关推荐
妙码生花1 小时前
从 PHP 到 AI + Golang,程序员自救转型手记(十四):眨眼小人登录页制作
前端·javascript·ai编程
妙码生花1 小时前
从 PHP 到 AI + Golang,程序员自救转型手记(十三):前端路由初始化
前端·javascript·ai编程
PBitW2 小时前
GPT训练我的第四天,被打惨了!!!😭😭😭
前端·javascript·面试
梨子同志2 小时前
CSS
前端
一tiao咸鱼2 小时前
Ai 相关 7月1日学习
前端·agent
梨子同志2 小时前
HTML
前端
ZhengEnCi2 小时前
Q06-导航按钮高级拟态玻璃效果构建完全指南
前端·css
Apifox3 小时前
Apifox 6 月更新|Apifox CLI 全面升级、导入导出优化、OAuth 2.0 支持自动刷新令牌
前端·后端·测试
CodingSpace3 小时前
TypeScript 装饰器
前端