引言
程序员面对屏幕抓狂
一、那个让我血压升高的面试现场 ⚡
"小明同学,能讲讲前端路由的实现原理吗?" 面试官推了推金丝眼镜,嘴角微微上扬。
我表面稳如老狗,内心慌得一批:"路由嘛,就是...那个...通过监听URL变化加载不同内容?"
"那手写一个Hash路由试试?" 面试官的笑容逐渐变态。
此刻我的CPU开始疯狂运转:Hash路由的组成要素是啥?事件监听怎么绑定?路由表用对象还是数组?脑子里突然蹦出曾经背过的八股文知识点,却像被猫玩乱的毛线球一样纠缠不清... 😿
三十分钟后,我交出了一份漏洞百出的代码,面试官的表情仿佛在说"就这?"。这次惨痛经历让我痛定思痛------是时候彻底征服这个前端必考题了!
背景知识
零、先来补个课:什么是哈希路由?🔍
在开始前可能有部分同学不知道哈希路由是什么? 我们可以来到掘金
打开某位大佬的文章: 看到右边的目录:
我们可以试着点一点这各个模块
我们可以看到这上面的路由从
变成了:
这个#/*******
就是我们说的哈希值 (Hash),也叫锚点。它最初的设计是用来跳转到页面内的特定位置(就像书签一样),但是!前端大佬们发现了它的隐藏用法:
神奇特性 :
修改#
后面的内容不会触发页面刷新 !(但会新增历史记录)
这意味着我们可以通过监听这个值的变化,实现无刷新页面切换------这就是哈希路由的核心魔法!✨
二、解剖Hash路由:原来你是这样的变色龙 🦎
2.1 为什么选择Hash路由?
先来点硬核知识开胃菜:
- 兼容性之王:支持IE8等上古浏览器(虽然现在用的人不多)
- SPA救星:改变hash不会触发页面刷新
- 监控简单 :一个
hashchange
事件全搞定 - 服务端无痛:不需要服务端特殊配置
2.2 核心实现原理脑图
graph TD
A[初始化路由表] --> B[监听hashchange事件]
B --> C{当前hash是否存在}
C -->|存在| D[执行对应回调]
C -->|不存在| E[执行默认回调或404]
三、手把手实现:从青铜到王者的进化之路 👑
3.1 基础版代码实现(含逐行解析)
javascript
class HashRouter {
constructor() {
this.routes = {}; // 路由登记簿
window.addEventListener('load', this.load.bind(this)); // 首次加载监听
window.addEventListener('hashchange', this.load.bind(this)); // 哈希变化监听
}
// 注册普通路由
register(path, callback = () => {}) {
this.routes[path] = callback; // 把路由存入"通讯录"
}
// 注册首页
registerIndex(callback = () => {}) {
this.routes['/index'] = callback; // 特殊待遇的首页
}
// 核心调度方法
load() {
const hash = location.hash.slice(1); // 去掉#号
let handler;
if (!hash) { // 空路径走首页
handler = this.routes['/index'];
} else if (this.routes[hash]) { // 正常路由
handler = this.routes[hash];
} else { // 404处理
handler = () => { container.innerHTML = '404 页面走丢了!' };
}
handler.call(this); // 执行对应回调
}
}
关键点解析:
location.hash.slice(1)
:获取#后的路径,比如#/page1
得到/page1
- 双保险的事件监听:页面加载时和hash变化时都要触发
- 路由表使用对象存储:实现O(1)时间复杂度查找
实现效果:
3.2 常见翻车现场 🚑
坑1:this指向问题
javascript
// 错误示范
window.addEventListener('hashchange', this.load); // this会指向window!
// 正确姿势
window.addEventListener('hashchange', this.load.bind(this));
坑2:重复注册问题
javascript
// 如果多次注册同一个路由...
router.register('/page1', () => console.log('第一次'));
router.register('/page1', () => console.log('第二次'));
// 解决方案:注册前先检查
register(path, callback) {
if(this.routes[path]) {
console.warn(`重复注册路由:${path}`);
}
this.routes[path] = callback;
}
四、给路由加点黑科技 🔥
4.1 动态路由支持
javascript
// 支持形如 /user/:id 的路由
register('/user/:id', (params) => {
console.log(`用户ID:${params.id}`);
});
// 在load方法中增加路径解析
const match = Object.keys(this.routes).find(key => {
const routePath = key.replace(/:\w+/g, '\\w+');
return new RegExp(`^${routePath}$`).test(hash);
});
4.2 路由守卫
javascript
class HashRouter {
constructor() {
this.beforeHooks = [];
}
beforeEach(callback) {
this.beforeHooks.push(callback);
}
load() {
// 执行守卫钩子
const next = () => {
// 原有逻辑...
};
this.runQueue(this.beforeHooks, next);
}
runQueue(queue, fn) {
const step = index => {
if (index >= queue.length) return fn();
queue[index](() => step(index + 1));
}
step(0);
}
}
五、站在巨人肩膀上看世界 🌍
5.1 与History路由的对比
特性 | Hash路由 | History路由 |
---|---|---|
URL美观度 | 有#号 | 干净清爽 |
兼容性 | IE8+ | IE10+ |
服务端支持 | 无需特殊配置 | 需要服务端配合 |
SEO友好度 | 较差 | 较好 |
实现难度 | 简单 | 较复杂 |
5.2 真实框架源码赏析(Vue Router节选)
javascript
// Vue Router的hash模式实现
setupListeners() {
const eventType = supportsPushState ? 'popstate' : 'hashchange'
window.addEventListener(
eventType,
handleRoutingEvent
)
}
六、写在最后:从面试题到源码大佬的蜕变 🦋
通过这次手写Hash路由的旅程,我们不仅搞懂了:
- 路由的基本工作原理 🕹️
- 事件监听的正确姿势 🎧
- 动态路由的魔法实现 ✨
- 路由守卫的拦截艺术 🛡️
更重要的是掌握了通过源码学习框架的终极奥义。下次面试官再让你手写路由时,你可以微微一笑:"要不咱们聊聊Vue Router的导航守卫实现?" 😎