技术知识文章集合TECHNICAL ARCHIVE · 457 DOCUMENTS

显示模式

登录
ARCHIVE DOCUMENTVUE

浅谈前端路由原理:Hash 和 History

所属馆藏
Vue
文件格式
Markdown
原始路径
Vue/86-浅谈前端路由原理hash和history
本文目录10 个章节
  1. 一、什么是前端路由
  2. 二、URL 的组成
  3. 三、Hash 模式
  4. 四、History 模式
  5. 五、Hash 和 History 对比
  6. 六、Vue Router 的当前用法
  7. 七、项目中如何选择
  8. 八、手写一个最小路由器
  9. 总结
  10. 参考资料

浅谈前端路由原理:Hash 和 History

原文从 SPA、hash、History API 和 Vue Router 两种模式展开。本文保留原有讲解顺序,修正 location 属性拼写、popstate 触发条件、History 模式与 SEO 的关系,并补充 Vue Router 3/4/5 和 Nuxt 4 的当前用法。

一、什么是前端路由

传统多页面应用中,浏览器访问一个 URL,服务器根据路径返回一份新的 HTML 文档。SPA(Single Page Application)通常先加载一个 HTML 和 JavaScript 应用,之后由客户端根据 URL 决定显示哪个组件,不必每次导航都重新加载整份文档。

前端路由主要负责三件事:

  1. 读取当前 URL 并匹配路由记录;
  2. 在客户端导航时更新 URL 和当前页面状态;
  3. 监听浏览器前进、后退,把历史记录变化同步回应用。

Vue Router 是 Vue 生态中的路由库;React 项目可以使用 React Router 等其他方案。Hash 和 History 是浏览器 URL/历史记录的两种实现方式,不是 Vue 专属概念。

二、URL 的组成

以这个 URL 为例:

http://127.0.0.1:8001/01-hash.html?a=100&b=20#/aaa/bbb
属性正确写法示例含义
协议location.protocolhttp:通信协议
主机名location.hostname127.0.0.1不包含端口的主机名
主机location.host127.0.0.1:8001主机名和端口
端口location.port8001端口号
路径location.pathname/01-hash.html当前文档路径
查询字符串location.search?a=100&b=20? 后的查询部分
哈希片段location.hash#/aaa/bbb# 及其后的片段

示例:

const url = new URL(
  'http://127.0.0.1:8001/01-hash.html?a=100&b=20#/aaa/bbb',
)

console.log(url.protocol) // 'http:'
console.log(url.hostname) // '127.0.0.1'
console.log(url.host) // '127.0.0.1:8001'
console.log(url.port) // '8001'
console.log(url.pathname) // '/01-hash.html'
console.log(url.search) // '?a=100&b=20'
console.log(url.hash) // '#/aaa/bbb'

原文中的 protocalpatchnameserachlocaltion 都是拼写错误;浏览器标准属性分别是 protocolpathnamesearchlocation

三、Hash 模式

1. 工作原理

Hash 模式把前端路由放在 URL 的 # 后面:

https://example.com/index.html#/users/42

修改 fragment 通常不会请求新的 HTML 文档,而会触发 hashchange。应用可以读取 location.hash,去掉开头的 # 后匹配路由:

function getHashPath() {
  return window.location.hash.slice(1) || '/'
}

function renderByHash() {
  const path = getHashPath()
  console.log('渲染路由:', path)
}

window.addEventListener('hashchange', renderByHash)
renderByHash()

浏览器会为 hash 导航记录历史,因此用户可以使用前进、后退;但 hash 片段不会作为 HTTP 请求路径发送给服务器。

2. Hash 模式的特点

  • URL 中有 #,路由片段通常位于当前文档之后;
  • hash 改变通常不触发新的文档请求;
  • 浏览器会触发 hashchange,应用据此更新视图;
  • 服务器收到请求时看不到 # 后面的片段;
  • 通常不需要服务器为每个前端路径配置 fallback;
  • URL 美观度和 SEO 通常不如 History 模式。

“hash 不能用于前后端分离”是不准确的。Hash 可以用于前后端分离 SPA,只是服务器无法直接根据 hash 访问后端资源,且如果需要搜索引擎理解可索引路径,hash 通常不是理想选择。

Hash 也不是“改变 URL 但没有历史记录”:改变 hash 通常会产生可前进/后退的 session history entry;是否新增记录取决于具体赋值和浏览器历史行为。

四、History 模式

1. History API

HTML5 History API 可以在同一源下改变地址栏中的路径,而不重新加载当前文档:

history.pushState({ page: 2 }, '', '/users/2')
history.replaceState({ page: 2 }, '', '/users/2')

两个方法的区别:

  • pushState(state, unused, url):新增一条历史记录;
  • replaceState(state, unused, url):替换当前历史记录;
  • state 必须是可序列化的数据,浏览器可能对序列化大小有限制;
  • url 通常必须与当前文档同源,不能借此跳转到任意外站;
  • title 参数目前大多数浏览器仍会忽略,通常传空字符串。

2. popstate 的正确触发时机

原文说 window.onpopstate “响应 pushStatereplaceState 的调用”,这不准确。单独调用 pushState()replaceState() 不会自动触发 popstate。路由库通常在调用它们之后主动渲染一次;当用户点击前进/后退,或调用 history.back()history.forward()history.go() 激活历史记录时,浏览器才会触发 popstate

function render(path) {
  console.log('渲染:', path)
}

function navigate(path, { replace = false } = {}) {
  const method = replace ? 'replaceState' : 'pushState'
  history[method]({ path }, '', path)
  render(path) // pushState 本身不会触发 popstate,所以要主动更新
}

window.addEventListener('popstate', event => {
  render(event.state?.path || location.pathname)
})

render(location.pathname)
navigate('/about')

popstate 发生时,location 通常已经反映新 URL;浏览器是否在初次加载时发送 popstate 也存在环境差异,路由库一般会主动执行初始导航,不依赖这个事件。

3. History 模式的特点

  • URL 可以是正常的路径,例如 /users/42
  • 客户端导航可以不重新请求文档;
  • push/replace 可以携带可序列化的 history.state
  • 前进、后退通过 popstate 通知应用;
  • 直接刷新或从外部打开深层路径时,浏览器会向服务器请求这个路径;
  • 服务器必须把未知前端路径回退到 SPA 的 index.html,否则可能返回 404。

五、Hash 和 History 对比

对比项HashHistory
URL/#/users/users
监听hashchangepopstate,push/replace 后需主动渲染
hash 是否发给服务器不会pathname/search 会发送
深层链接刷新通常不需要额外 fallback需要 server fallback
URL 美观度较弱更自然
SEO通常不利URL 更适合,但 SPA 本身仍需 SSR/SSG 才更利于 SEO
部署成本需要服务器或托管平台配置
适合场景静态托管、无法改服务器、内部工具可配置服务器、需要正常路径的应用

History 模式并不会自动让 SPA 变成 SEO 友好的服务端渲染站点;如果 SEO 是核心需求,还应考虑 SSR、SSG、预渲染、正确的 meta 和可抓取内容。Nuxt 4 默认使用文件路由和 SSR/预渲染能力,通常不需要手动在页面中实现 hash/history 路由。

六、Vue Router 的当前用法

Vue Router 3(Vue 2 项目)

import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router)

const router = new Router({
  mode: 'history', // 默认是 hash
  routes: [
    { path: '/', component: Home },
    { path: '/users/:id', component: User },
  ],
})

Vue Router 3 的 mode 仍然是 hashhistoryabstract 等历史写法。Vue 2.7 项目升级时需要结合现有 Vue Router 3 版本确认 API。

Vue Router 4/5(Vue 3 项目)

Vue Router 4/5 使用 history 实例:

import {
  createRouter,
  createWebHashHistory,
  createWebHistory,
} from 'vue-router'

const router = createRouter({
  // 正常路径,需要服务器 fallback
  history: createWebHistory(),
  // 如果无法配置服务器,可改为:
  // history: createWebHashHistory(),
  routes: [
    { path: '/', component: Home },
    { path: '/users/:id', component: User },
  ],
})

Vue Router 文档推荐 HTML5 History 模式,但明确要求服务器在找不到静态文件时返回 index.html。例如 nginx:

location / {
  try_files $uri $uri/ /index.html;
}

生产环境还要根据部署子目录设置 router base,并让静态资源路径与 base 一致。应用内部应配置兜底路由展示自己的 404 页面,避免服务器把所有不存在路径都当成成功的 index.html

七、项目中如何选择

选择 Hash

  • 使用 GitHub Pages、对象存储等难以配置 fallback 的静态托管;
  • 项目是内部工具,对 URL 美观度和 SEO 要求不高;
  • 希望直接刷新深层路径也能回到同一个 HTML。

选择 History

  • 可以控制 nginx、Node、CDN 或托管平台的 rewrite;
  • 希望使用正常的 /users/42 路径;
  • 需要与后端路由、分析平台或 SEO 体系配合。

无论选择哪种模式,都要测试:首次打开、刷新、前进、后退、复制深层链接、404、部署子路径、资源加载和服务端权限。

八、手写一个最小路由器

下面的代码只演示浏览器 API,不包含参数匹配、守卫、异步组件、滚动恢复和 SSR:

const routes = {
  '/': '<h1>首页</h1>',
  '/about': '<h1>关于</h1>',
}

const app = document.querySelector('#app')

function render(path = location.pathname) {
  app.textContent = ''
  app.insertAdjacentHTML('afterbegin', routes[path] || '<h1>404</h1>')
}

function go(path) {
  if (path === location.pathname) return
  history.pushState({ path }, '', path)
  render(path)
}

window.addEventListener('popstate', () => {
  render(location.pathname)
})

render()

如果把用户可控内容通过 innerHTML 写入页面,必须先进行安全处理;示例中的路由内容是固定常量,实际应用应使用模板/组件渲染,避免 XSS。

总结

  • Hash 和 History 都可以用于前后端分离 SPA;
  • hash 变化通常触发 hashchange,片段不会发送给服务器;
  • pushState/replaceState 不会自动触发 popstate,路由器需要主动更新视图;
  • History 模式刷新深层路径会请求服务器,需要 fallback 到 index.html
  • History URL 更自然,但不会自动解决 SPA 的 SEO,SSR/SSG 仍然重要;
  • Vue Router 3 使用 mode,Vue Router 4/5 使用 createWebHistorycreateWebHashHistory
  • Nuxt 4 通常由文件路由和 Nuxt 的服务端/静态生成流程管理路由,不要把本文的手写代码直接放进 Nuxt 页面。

参考资料

原文作者:周一同学 Zelina。原文关于 SPA、hash、History API、刷新 404 和模式选择的主线予以保留;无关表情/推广、拼写错误、popstate 触发条件错误、History 与 SEO 的绝对化结论已整理或修正。原文图片为表情素材,未作为技术图片保留。

457 DOCUMENTS · 10 COLLECTIONS
ARCHIVE SEARCH457 篇文章

SEARCH GUIDE

输入关键词开始搜索

支持搜索文章标题、所属分类和原始文档路径。

按分类浏览

10 COLLECTIONS