首页/文章/javascript

彻底搞懂前端路由:从 Hash 到 History 的演进与实践

2025-08-03
17316 分钟
...

一、什么是路由?

在 Web 开发中,路由(Route) 是 URL 与资源 之间的映射关系。

  • 服务端路由:传统多页应用(MPA)里,浏览器每跳转一次 URL,都会向服务器发起一次新的请求,服务器根据 URL 返回不同的 HTML 页面。
  • 前端路由:单页应用(SPA)里,浏览器只加载一次 index.html,后续 URL 变化完全在客户端完成,不触发整页刷新,通过 JS 动态渲染局部视图仅局部更新 DOM不刷新页面

二、SPA 为什么需要前端路由?

维度多页应用(MPA)单页应用(SPA)
URL 与 资源 映射一个 URL 对应一份 HTML一个 URL 对应一份组件
页面切换整页刷新局部 DOM 替换
体验白屏、状态丢失,割裂无缝、沉浸
SEO天然友好需 SSR/预渲染

结论前端路由让 SPA 既拥有“不刷新”的流畅体验,又能在浏览器地址栏正确反映当前视图状态。


三、前端路由要解决的两个核心问题

1. 如何感知 URL 变化?

浏览器原生并不会在 URL 变化时主动通知我们,需要手动监听。

  • Hash 模式监听 hashchange
  • History 模式监听 popstate

2. 如何根据 URL 找到并渲染对应组件?

需要维护一个路由表,路由表中存储了 URL 和组件的映射关系。当 URL 发生变化时,根据路由表找到对应的组件。


四、Hash 模式:最简单的前端路由

4.1 原理

URL 中 # 后面的部分称为 Hash

  • # 及之后的内容不会被发送到服务器,因此改变 Hash 不会触发页面刷新
  • 浏览器原生事件 hashchange ,用来实时监听 Hash 变化。

4.2 Hash 路由实现步骤

  1. 定义路由表
const routes = [
    {
        path: '/home',
        component: () => '<h1>首页页面</h1>'
    },
    {
        path: '/list',
        component: () => '<h1>列表页页面</h1>'
    }
]
  1. 监听 hashchange 事件,当hash值变更时,触发 hashchange 事件,通过 location 获取 # 和后面的内容。在事件处理函数中,根据 hash 值,找到对应组件,将组件渲染到页面中。
window.addEventListener('hashchange', (e) => {
    renderView(location.hash)
})

function renderView(url){ 
    
    
    const index = routes.findIndex(item => {
        return ('#' + item.path) === url
    })
    
    let routerView = document.getElementById('root')
    
    routerView.innerHTML = routes[index].component()
}

✅ 优点:实现简单,兼容性好(IE8+)。
❌ 缺点:URL 带 #,不美观;SEO 不友好;服务端无法直接拿到 Hash 部分。


五、History 模式:更优雅的现代方案

5.1 History API 简介

HTML5 新增的 window.history 提供了无刷新操作浏览器历史记录的能力。

方法作用是否触发 popstate
pushState(state, title, url)新增历史记录
replaceState(state, title, url)替换当前记录
back / forward / go(n)浏览器或 JS 导航

注意

  • 只有浏览器前进/后退才会触发 popstate
  • 代码调用 pushState/replaceState 后需手动渲染

5.2 History 路由实现步骤

  1. 定义路由表;
  2. 监听popstate事件,当 URL 发生变化时,触发popstate事件,通过 location 获取 URL 中的路径。
window.addEventListener('popstate', () => {
    renderView(location.pathname)
})
  1. 页面加载完成时,调用 onLoad方法,拦截 <a> 标签默认行为。
  • onLoad 方法:找到所有的链接,给每个链接添加点击事件;
  • click 点击事件:阻止默认事件,修改 URL,调用 renderView 方法。
window.addEventListener('DOMContentLoaded', () => {
    onLoad()
})

function onLoad() {
    
    let linkList = document.querySelectorAll('a[href]')
    
    linkList.forEach(item => {
        
        item.addEventListener('click', (e) => {
            
            e.preventDefault()
            
            history.pushState(null, '', item.getAttribute('href'))
            renderView(item.getAttribute('href'))
        })
    })
}
  1. 定义 renderView 方法渲染对应的组件。
function renderView(url) {
    let index = routes.findIndex(item => {
        return item.path === url
    })
    routerView.innerHTML = routes[index].component()
}

✅ 优点:URL 干净,支持 SEO,用户体验更好。
❌ 缺点:需要服务端配合(Nginx/Node 配置兜底),IE10+。


六、框架路由源码级对比

特性Vue RouterReact RouterAngular Router
模式hash / history / abstracthash / history / memoryhash / history
懒加载() => import()React.lazyloadChildren
守卫/钩子全局 / 路由 / 组件自定义 HooksGuards & Resolvers
嵌套路由<router-view><Outlet><router-outlet>
数据获取beforeEnter, asyncDataloaderresolve

七、总结

维度HashHistory
URL 美观度❌ 带 #✅ 干净
浏览器兼容✅ IE8+⚠️ IE10+
服务端配置不需要需要兜底
SEO✅ 好(可 SSR)
实现复杂度

如果您觉得这篇文章有帮助,请点个赞吧~

分享文章

相关文章

更多文章 →
javascript2025-09-18
JavaScript 的多线程能力:Worker
如果你写过一些计算量稍大的 JavaScript 代码,比如图像处理、大量数据排序或者复杂的算法,你几乎肯定遇到过浏览器“卡死”的现象。点击页面没反应,动画也停了,就像整个世界都静止了。 这就是主线程被阻塞的典型后果。因为主线程既要负责执行 JavaScript,又要负责渲染页面、响应用户操作,一旦它被繁重的计算任务占满,就无暇顾及其他,用户体验便直线下降。 这个问题的根源,正是“主线程是单线程的”。那么,如何解决呢? 答案很简单:把这...
学习面试
javascript2025-09-10
手把手教你实现一个EventEmitter
你是不是也遇到过这种情况:代码里各种事件监听和触发,回调函数套了一层又一层,最后自己都理不清哪个事件先触发、哪个后触发了? 别担心,今天我就带你从零开始实现一个自己的事件触发器(EventEmitter),让你彻底掌握事件驱动的精髓,代码清晰度直接提升200%! 为什么要懂EventEmitter? 先说说为什么我们要关心这个。Node.js的核心就是事件驱动,比如文件读写、网络请求,都是通过事件来处理的。不会EventEmitter,...
学习面试
javascript2025-08-09
Web Worker 多线程魔法
你是否曾遇到过网页因为复杂计算而卡顿的情况?今天,让我们一起探索Web Worker的神奇世界,看看如何用多线程技术解决性能瓶颈,并实现流畅的图片压缩功能! 为什么需要Web Worker? 想象一下,你在一个繁忙的餐厅里,只有一位服务员(主线程)负责点餐、上菜、结账所有工作。当客人突然暴增时,这位服务员就会手忙脚乱,其他客人只能干等着… 这就是JavaScript的现状—— 单线程 !所有任务都在一个线程中排队执行,一旦遇到复杂计算(...
学习面试
javascript2025-08-07
🌐AST抽象语法树
AST 是前端开发中一个非常重要的概念,它就像是代码的“编译器”,能够帮助我们理解和操作代码。本文将深入探讨 AST 的原理和应用,带你从零开始了解 AST。 一、什么是 AST? AST(抽象语法树)是一种树形结构,用于表示程序代码的语法结构。它将代码分解为一系列的节点,每个节点代表代码中的一个元素,如变量、函数、表达式等。AST 是编译器和解释器的基础,也是代码分析和转换的关键工具。 1\. AST 的结构 AST 的结构通常包括以...
学习面试
javascript2025-08-05
打字机效果是如何实现的
分析效果 使用 AI 对话的时候,文字是一个字一个字蹦出来的?它不是等所有内容生成完毕后一次性展示,而是以一种流畅、实时的“打字机”效果呈现,这种体验的背后,隐藏着一种高效的Web技术。它就是 服务器推送事件(Server Sent Events,简称 SSE) 什么是 SSE 从根本上说,SSE 是一种允许服务器 单向 、 持续 地向客户端(浏览器)推送数据的Web技术。 可以把它想象成一个 电台广播 : 服务器 是广播电台,持续不断...
学习面试
javascript2025-08-04
Promise 从原理到实现
前言 是 异步编程的核心概念之一,它为我们提供了一种更优雅的方式来处理异步操作。 本文将全面剖析 的工作原理,并逐步实现一个符合 规范的 。 正文 一、Promise 基础概念 代表一个异步操作的最终完成(或失败)及其结果值。它有 种状态: 1. pending : ,既不是成功,也不是失败状态 2. fulfilled :意味着操作 完成 3. rejected :意味着操作 状态一旦改变就不能再变,只能从 变为 ,或从 变为 。 二...
学习面试

评论

请登录后发表评论

去登录
加载评论中...

目录