浅谈前端路由的实现 hash&history
引言
在现代Web开发中,单页应用程序(SPA)因其快速加载和流畅的用户体验而广受欢迎。且在面试中,前端路由的底层原理也是高频题。本文将详细讲解vue前端的两种路由【hash&history】的不同,并通过手动实现一个Hash Router来理解其工作原理。
传统HTTP请求
传统的HTTP协议是无状态的,服务器无法主动向浏览器推送内容,浏览器只能通过URL改变向服务器发送请求以获取新的资源。每次请求新的页面时,服务器都会返回完整的HTML,这会导致页面刷新。而SPA带来了全新的用户体验:
- 加载快:无需每次都请求完整的页面,只需加载变化的部分。
- 无白屏:URL改变但不会刷新整个页面,页面更新更快。
- 动态更新:通过JavaScript和DOM操作实现页面的动态更新。
Hash 与 History的不同
在前端路由中,有两种常见的方式:History模式和Hash模式。它们的主要区别在于URL的形式和对浏览器历史记录的影响。
-
History模式:使用真实的URL路径(如
/page2
),可以通过HTML5 History API来管理路由。这种模式需要后端服务器的支持来处理这些真实路径,以防止404错误。当点击跳转history路由的链接则会致使整个页面刷新。 -
Hash模式:使用带有
#
符号的URL路径(如#/page2
)。Hash部分的变化不会触发整个页面的重新加载,因此适合单页应用(SPA)。Hash模式不需要服务器端特殊配置,是前端完全控制的一种路由方式。
如下代码所示:
<nav id="nav">
<ul>
<!-- 有#号的是哈希路由 -->
<li><a href="#/page1">page1</a></li>
<li><a href="/page2">page2</a></li>
<li><a href="#/page3">page3</a></li>
</ul>
</nav>
页面效果如下
当我们点击page1和page3链接后,页面没刷新,且URL里加了#
而当我们点击page2的链接后
则URL无#,跳转的是真实路径,且重新加载了页面,导致了错误。 这也是history路由的不足。 所以推荐使用SPA和hash路由。 单页应用程序(SPA)带来了全新的用户体验:
- 快速加载:页面不会完全刷新,只会动态更新必要部分。
- URL管理:URL改变但不会刷新整个页面,例如
#/page2
。 - 锚链接:使用hash可以实现页面内导航。
- 事件监听:通过
hashchange
事件监听URL变化,结合ajax
和DOM
操作动态更新页面内容。
在SPA中,使用Hash Router可以实现URL管理而不刷新页面。这种技术也常用于移动应用程序中。
手搓一个哈希路由
我们新建一个index.html
文件,包含三个a
标签。通常情况下,点击a
标签会引起页面的刷新,但浏览器的机制是哈希值的变更不会引起页面刷新。因此,我们可以利用哈希值来实现页面的切换而不刷新整个页面。
<nav id="nav">
<ul>
<!-- 有#号的是哈希路由 -->
<li><a href="#/page1">page1</a></li>
<li><a href="#/page2">page2</a></li>
<li><a href="#/page3">page3</a></li>
</ul>
</nav>
<!-- router-view -->
<div id="container"></div>
接着封装一个HashRouter类
// #/page1 page1 -> container
// #/page2 page2 -> container
class HashRouter {
constructor(){
this.routes = {}; //page => Component
window.addEventListener('hashchange',this.load.bind(this),false) // 动态指定函数内部this的指向,bind会返回一个全新的函数
}
register (hash,callback = function(){}){
this.routes[hash]=callback
}
registerIndex(callback=function(){}){ // 首页的注册函数
this.routes['index']=callback;
}
load(){
console.log(location.hash); //BOM
let hash = location.hash.slice(1) //去掉第一个字符# 方是路由
console.log(hash,'////');
let handler;
if (!hash){
// 首页
handler=this.routes['index']
}
else {
// 相应页面
handler = this.routes[hash]
}
handler && handler.call(this)
}
}
在构造函数constructor()中初始化路由表,并绑定hashchange
事件监听器。
此处需要注意为了确保事件处理函数中的this
指向正确的对象(即HashRouter
实例),我们使用bind
方法来显式地绑定函数的this
值。在这种情况下,this.load.bind(this)
创建了一个新的函数,其this
值永久地绑定到HashRouter
实例,而不会受到事件触发时的影响。bind的用法将在下文详细讲述。
在register方法中注册特定路径的路由及其对应的回调函数,registerIndex方法为注册首页路由及其对应的回调函数。
load方法根据当前的哈希值,执行对应的回调函数来更新页面内容。
hashchange事件
当URL的哈希部分变化时,触发hashchange
事件,调用load
方法更新页面内容。
回调函数
在回调函数中,动态更新页面的内容。
注册路由
let router = new HashRouter();
let container = document.getElementById('container')
router.registerIndex(()=> container.innerHTML = '我是首页')
router.register('/page1',()=> container.innerHTML = '我是Page1')
router.register('/page2',function() {
console.log(this,this.routes);
container.innerHTML = '我是Page2'})
router.register('/page3',()=> container.innerHTML = '我是Page3')
console.log(router.routes);
router.load();
先将三个链接的路由都注册好,当触发hashchange事件后渲染对应的hash的页面内容。
成果如下:
首页
点击page1
这就实现了一个简单hash router的功能!
回顾call,apply,bind
在JavaScript中,call
, apply
和bind
都是用来改变函数运行时的上下文(即this
指向)的方法。
call
方法:立即调用函数,传入一个新的this
值和一系列参数。格式为func.call(newThis, arg1, arg2, ...)
.apply
方法:立即调用函数,传入一个新的this
值和一个参数数组。格式为func.apply(newThis, [arg1, arg2, ...])
.bind
方法:返回一个新的函数,this
值被绑定为传入的对象,但不会立即执行。格式为let newFunc = func.bind(newThis)
.
结合以下示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
let obj = {
name:'xyao',
getName: function(...args){
console.log(args);
console.log(this.name);
}
}
let obj2 = {
name:'04'
}
obj.getName();
obj.getName.call(obj2,1,2) // 参数一个一个传的
obj.getName.apply(obj2,[1,2]) // 参数以数组形式传
let func = obj.getName
// 未来执行用bind,call和apply是立即执行
func.bind(obj2)()
</script>
</body>
</html>
打印结果如下:
而在手搓的hashrouter类中,我们使用这些方法来确保在HashRouter
类中,load
方法中的this
指向正确。
总结
本文介绍了前端的两种路由【hash&history】,并通过手动实现一个简单的Hash Router,详细介绍了如何在单页应用程序(SPA)中使用哈希路由管理页面导航。同时,解释了JavaScript中的bind
, call
, 和apply
方法的使用。
转载自:https://juejin.cn/post/7389650655427837962