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

📅 2026/8/7 6:46:47
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. 致命缺点网速一般时跳转必白屏交互割裂页面公共头部、底部重复加载资源浪费无法局部刷新所有内容全部重绘移动端流量消耗更高流畅度差配套演示代码多页跳转demoindex.html!DOCTYPEhtmlhtmllangenheadmetacharsetUTF-8metanameviewportcontentwidthdevice-width, initial-scale1.0titleMPA多页首页/title/headbodyheadernavulliahrefindex.html首页/a/liliahrefabout.html关于我们/a/li/ul/nav/headermainh1首页内容/h1/mainfooter/footer/body/htmlabout.html!DOCTYPEhtmlhtmllangenheadmetacharsetUTF-8metanameviewportcontentwidthdevice-width, initial-scale1.0title关于我们/title/headbodyheadernavulliahrefindex.html首页/a/liliahrefabout.html关于我们/a/li/ul/nav/headermainh1我们/h1/main/body/html测试现象两个文件放在同一文件夹点击导航切换页面会完全刷新滚动条重置页面闪烁白屏。二、SPA单页应用解决方案Hash路由核心原理1. URL结构拆解完整URL示例http://www.baidu.com/u/123?a1b2#/page1protocolhttp/https 传输协议host域名/IP地址path/u/123资源路径queryString?a1b2查询参数hash#/page1哈希片段#开头2. Hash独一无二的特性修改hash部分浏览器不会向服务器发送任何请求hash变更会自动新增浏览器历史记录前进后退按钮可用触发原生hashchange事件JS可监听URL变化hash仅作用于浏览器本地服务端接收请求时会自动忽略#及后面内容3. 原生锚点# 和 前端Hash路由的区别基础锚点demo仅页面滚动无法切换页面内容!DOCTYPEhtmlhtmllangenheadmetacharsetUTF-8metanameviewportcontentwidthdevice-width, initial-scale1.0title锚点基础Demo/title/headbody!-- 页面锚点 --anametop/aahref#bottom去到底部/adivstyleheight:200vh;background-color:yellow/divahref#top回到顶部/adivstyleheight:300vh;background-color:red/divanamebottom/ascriptwindow.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打开即可运行!DOCTYPEhtmlhtmllangenheadmetacharsetUTF-8metanameviewportcontentwidthdevice-width, initial-scale1.0title手写HashRouter SPA/titlestylenav ul{display:flex;gap:20px;list-style:none;padding:0;}#container{margin-top:30px;font-size:20px;}/style/headbodyheadernavulliahref#/page1页面一/a/liliahref#/page2页面二/a/liliahref#/page3页面三/a/li/ul/nav/header!-- 路由内容挂载容器 --dividcontainer/divscriptclassHashRouter{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格式 #/page1slice(1)切掉#得到 /page1constcurrentHashlocation.hash.slice(1);// 获取对应渲染函数constrenderFnthis.routers[currentHash];// 存在路由则执行渲染无匹配显示404if(renderFn){renderFn.call(this);}else{document.getElementById(container).innerHTMLh2404 页面不存在/h2;}}}// 初始化路由实例constrouternewHashRouter();constcontainerdocument.getElementById(container);// 注册三条路由规则router.register(/page1,()container.innerHTMLh2页面一内容/h2p这里是首页展示信息/p);router.register(/page2,()container.innerHTMLh2页面二内容/h2p关于我们介绍页面/p);router.register(/page3,()container.innerHTMLh2页面三内容/h2p联系我们表单页面/p);/script/body/html核心代码拆解constructor构造函数创建路由注册表this.routers存储路径与渲染函数绑定hashchange监听切换页面自动执行渲染增加DOMContentLoaded监听解决刷新页面空白问题register注册方法外部调用注册路由把路径和渲染回调存入对象解耦路由与页面逻辑load渲染核心方法location.hash.slice(1)去除#符号拿到纯净路由路径匹配注册表内函数无匹配返回404页面call(this)保证回调内this指向路由实例方便扩展四、Hash路由开发高频踩坑清单坑1页面刷新后内容空白原因仅监听hashchange页面首次加载不会触发事件解决构造函数增加DOMContentLoaded监听页面载入自动执行一次渲染坑2this指向丢失读取不到routers注册表原因hashchange事件回调默认this指向window解决使用.bind(this)绑定路由实例上下文坑3原生锚点和路由#冲突原因页面内存在#top、#bottom锚点会触发路由渲染逻辑解决统一路由前缀#/判断路径是否以/开头区分页面锚点与路由坑4空hash直接访问域名无默认首页优化扩展load方法增加兜底逻辑currentHash为空时默认渲染/page1首页load(){constcurrentHashlocation.hash.slice(1)||/page1;constrenderFnthis.routers[currentHash];// ...后续逻辑不变}坑5URL带查询参数解析异常场景#/page1?id100解决方案使用split(?)[0]截取纯路由路径constcurrentHashlocation.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路由