欢迎光临
我们一直在努力

SPA白屏卡顿痛点|1套手写Hash路由|彻底掌握前端路由底层原理

写单页应用总遇到这些糟心事:

  • 传统多页面跳转整页刷新、白屏、加载慢,移动端体验极差
  • 只会用Vue/React Router库,底层原理一问三不知,出bug无从排查
  • 分不清锚点#和前端Hash路由区别,不知道hashchange事件怎么用 在这里插入图片描述

读完本文你能收获:

  • 搞懂MPA多页应用与SPA单页应用核心差异
  • 吃透Hash路由底层运行机制、浏览器URL规则
  • 两段完整可直接运行的实战代码(锚点demo+手写路由类)
  • 开发踩坑避坑清单、Hash路由优缺点与适用场景
  • 从零手写简易路由库,不用框架也能实现页面无刷新切换
  • 全文短句分段,手机阅读无压力,所有代码复制打开浏览器就能跑。

    一、先搞懂:传统多页面MPA为什么又慢又卡

    1. 传统页面跳转流程

    以首页index.html跳转到about.html为例:

  • 点击a标签链接,浏览器读取完整URL
  • 向服务端发起全新HTTP请求
  • 服务器返回对应html静态文件
  • 浏览器清空当前页面,完整重新渲染DOM、样式、脚本
  • 浏览器历史记录新增一条
  • 2. 致命缺点

    • 网速一般时,跳转必白屏,交互割裂
    • 页面公共头部、底部重复加载,资源浪费
    • 无法局部刷新,所有内容全部重绘
    • 移动端流量消耗更高,流畅度差

    配套演示代码(多页跳转demo): index.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>MPA多页首页</title>
    </head>
    <body>
    <header>
    <nav>
    <ul>
    <li><a href="index.html">首页</a></li>
    <li><a href="about.html">关于我们</a></li>
    </ul>
    </nav>
    </header>
    <main>
    <h1>首页内容</h1>
    </main>
    <footer></footer>
    </body>
    </html>

    about.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>关于我们</title>
    </head>
    <body>
    <header>
    <nav>
    <ul>
    <li><a href="index.html">首页</a></li>
    <li><a href="about.html">关于我们</a></li>
    </ul>
    </nav>
    </header>
    <main>
    <h1>我们</h1>
    </main>
    </body>
    </html>

    测试现象

    两个文件放在同一文件夹,点击导航切换,页面会完全刷新,滚动条重置,页面闪烁白屏。

    二、SPA单页应用解决方案:Hash路由核心原理

    1. URL结构拆解

    完整URL示例:http://www.baidu.com/u/123?a=1&b=2#/page1

    • protocol:http/https 传输协议
    • host:域名/IP地址
    • path:/u/123 资源路径
    • queryString:?a=1&b=2 查询参数
    • hash:#/page1 哈希片段(#开头)

    2. Hash独一无二的特性

  • 修改hash部分,浏览器不会向服务器发送任何请求
  • hash变更会自动新增浏览器历史记录,前进后退按钮可用
  • 触发原生 hashchange 事件,JS可监听URL变化
  • hash仅作用于浏览器本地,服务端接收请求时会自动忽略#及后面内容
  • 3. 原生锚点# 和 前端Hash路由的区别

    基础锚点demo(仅页面滚动,无法切换页面内容)

    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>锚点基础Demo</title>
    </head>
    <body>
    <!– 页面锚点 –>
    <a name="top"></a>
    <a href="#bottom">去到底部</a>
    <div style="height: 200vh;background-color:yellow"></div>
    <a href="#top">回到顶部</a>
    <div style="height: 300vh;background-color:red"></div>
    <a name="bottom"></a>

    <script>
    window.addEventListener("hashchange",function(e){
    console.log("hash变了");
    console.log("新地址:", e.newURL);
    console.log("旧地址:", e.oldURL);
    })
    </script>
    </body>
    </html>

    • 原生锚点:仅控制页面滚动定位,不能切换页面内容
    • SPA Hash路由:约定 #/xxx 格式,监听hash变化,动态替换容器DOM,实现多页面切换

    4. Hash路由完整运行流程

  • 导航a标签href写 #/page1,点击后URL更新,页面不刷新
  • 浏览器触发 hashchange 事件
  • JS捕获当前hash值,切割#获取路由标识
  • 匹配提前注册好的路由回调函数
  • 替换#container容器内HTML,实现局部渲染
  • 前进/后退浏览器自动修改hash,重复执行渲染逻辑
  • 三、实战:手写完整可运行Hash路由类(无任何框架)

    完整源码,直接复制保存html打开即可运行

    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>手写HashRouter SPA</title>
    <style>
    nav ul { display: flex; gap: 20px; list-style: none; padding: 0; }
    #container { margin-top: 30px; font-size: 20px; }
    </style>
    </head>
    <body>
    <header>
    <nav>
    <ul>
    <li><a href="#/page1">页面一</a></li>
    <li><a href="#/page2">页面二</a></li>
    <li><a href="#/page3">页面三</a></li>
    </ul>
    </nav>
    </header>
    <!– 路由内容挂载容器 –>
    <div id="container"></div>

    <script>
    class HashRouter {
    constructor() {
    // 存储所有路由映射:key=路由路径 value=渲染回调
    this.routers = {};
    // 监听hash变化,bind修正this指向为路由实例
    window.addEventListener('hashchange', this.load.bind(this));
    // 页面首次加载时执行一次渲染(刷新页面不空白)
    window.addEventListener('DOMContentLoaded', this.load.bind(this));
    }

    // 注册路由:传入hash路径 + 页面渲染回调
    register(hashPath, callback) {
    this.routers[hashPath] = callback;
    }

    // 根据当前hash匹配并渲染页面
    load() {
    // location.hash格式 "#/page1",slice(1)切掉#,得到 /page1
    const currentHash = location.hash.slice(1);
    // 获取对应渲染函数
    const renderFn = this.routers[currentHash];
    // 存在路由则执行渲染,无匹配显示404
    if(renderFn) {
    renderFn.call(this);
    } else {
    document.getElementById('container').innerHTML = '<h2>404 页面不存在</h2>';
    }
    }
    }

    // 初始化路由实例
    const router = new HashRouter();
    const container = document.getElementById('container');

    // 注册三条路由规则
    router.register('/page1', () => container.innerHTML='<h2>页面一内容</h2><p>这里是首页展示信息</p>');
    router.register('/page2', () => container.innerHTML='<h2>页面二内容</h2><p>关于我们介绍页面</p>');
    router.register('/page3', () => container.innerHTML='<h2>页面三内容</h2><p>联系我们表单页面</p>');
    </script>
    </body>
    </html>

    核心代码拆解

  • constructor构造函数
    • 创建路由注册表 this.routers 存储路径与渲染函数
    • 绑定 hashchange 监听,切换页面自动执行渲染
    • 增加 DOMContentLoaded 监听,解决刷新页面空白问题
  • register注册方法 外部调用注册路由,把路径和渲染回调存入对象,解耦路由与页面逻辑

  • load渲染核心方法

    • location.hash.slice(1) 去除#符号,拿到纯净路由路径
    • 匹配注册表内函数,无匹配返回404页面
    • call(this) 保证回调内this指向路由实例,方便扩展

    四、Hash路由开发高频踩坑清单

    坑1:页面刷新后内容空白

    原因:仅监听hashchange,页面首次加载不会触发事件 解决:构造函数增加 DOMContentLoaded 监听,页面载入自动执行一次渲染

    坑2:this指向丢失,读取不到routers注册表

    原因:hashchange事件回调默认this指向window 解决:使用 .bind(this) 绑定路由实例上下文

    坑3:原生锚点和路由#冲突

    原因:页面内存在 #top、#bottom 锚点,会触发路由渲染逻辑 解决:统一路由前缀 #/,判断路径是否以/开头区分页面锚点与路由

    坑4:空hash(直接访问域名)无默认首页

    优化扩展:load方法增加兜底逻辑,currentHash为空时默认渲染/page1首页

    load() {
    const currentHash = location.hash.slice(1) || '/page1';
    const renderFn = this.routers[currentHash];
    // …后续逻辑不变
    }

    坑5:URL带查询参数解析异常

    场景:#/page1?id=100 解决方案:使用 split('?')[0] 截取纯路由路径

    const currentHash = location.hash.slice(1).split('?')[0];

    五、Hash路由优缺点 & 适用场景

    ✅ 优势

  • 零服务端配置,静态服务器、GitHub Pages、本地打开html均可运行
  • 兼容性极强,最低兼容IE8,老项目、后台管理系统首选
  • hash变更不会发送请求,不会出现404刷新报错
  • 浏览器前进、后退、历史记录原生支持
  • ❌ 劣势

  • URL带有#符号,视觉上不够简洁美观
  • 不利于SEO优化,搜索引擎抓取会忽略#后路径
  • 无法使用锚点定位页面,需要自己做兼容处理
  • 适合使用Hash路由的场景

    • 内部后台管理系统、中台项目(无SEO需求)
    • 静态托管页面、线上预览Demo、本地离线项目
    • 老旧浏览器兼容、无权限修改后端服务配置

    不推荐场景

    • C端官网、需要搜索引擎收录的页面(优先History模式)

    六、全文核心总结

  • MPA多页应用每次跳转全页面刷新,白屏卡顿,SPA用前端路由解决该痛点
  • Hash路由依靠URL#哈希片段 + hashchange事件实现无刷新页面切换
  • #后的内容不会传递给服务端,无需后端配置,部署成本极低
  • 手写路由核心三步:监听hash变化、注册路由映射、动态替换容器DOM
  • 后台、静态Demo优先Hash;面向公网、需SEO选择History路由
  • 赞(0)
    未经允许不得转载:171主机测评 » SPA白屏卡顿痛点|1套手写Hash路由|彻底掌握前端路由底层原理
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址