介绍 Hash Router
Hash Router,即哈希路由,是一种在单页面应用(SPA)中实现客户端路由的策略,尤其在使用如Vue.js、React等前端框架时较为常见。它利用URL的哈希部分来改变页面状态,而无需重新加载整个页面。
手写 Hash Router
HTML部分
我们知道的,路由中有一个router-link to和router-view,前者是用来导航到不同的路由,后者则是用来显示路由中的内容。我们先实现这俩个功能:
<body>
<nav id="nav">
<ul>
<li>
<a href="#/page1">页面一</a>
</li>
<li>
<a href="#/page2">页面二</a>
</li>
<li>
<a href="#/page3">页面三</a>
</li>
</ul>
</nav>
<div id="router-view">
</div>
</body>
我们使用<a>标签来实现导航的功能,并定义了一个id为router-view的<div>标签来实现显示的功能
JS部分
一、定义一个HashRouter的类,用于实现基于URL哈希值的页面切换功能
HashRouter{
}
- 在这个类里面,我们先构造一个函数,在函数里面,我们创建一个空对象用来存储路由(hash值)和对应的函数映射,其次,在函数里面定义一个hashchange事件,当路由变化的时候,自动调用方法来处理路由变化
constructor{
this.router={}
window.addEventListener('hashchange',this.load.bind(this),false);
//这里面的load是我们用来处理路由变化所定义的方法
}
- 接下来我们来写当路由变化时所调用的方法,我们要知道我们应该需要对路由进行什么样的处理:
- 第一:我们应该将上面传下来的hash值中的
#去除,去除#是为了提取出纯粹的路由标识符,便于在客户端进行路由匹配和页面内容的切换
let hash = location.hash.slice(1)
location.hash是JavaScript的内置对象window.location的一个属性,用于获取或设置当前URL的哈希部分。
.slice(1)是一个字符串方法,用于提取字符串的一部分。
- 第二:我们要根据hash值找到用户想去的那个页面
let handler;
if(!hash){
handler =this.routes['index']
//去首页
}else{
handler = this.routes[hash]
//相应页面
}
handler && handler.call(this)
我们先声明一个handle的变量,用来存储要执行的路由处理,
然后我们要判断这个哈希值是否存在,如果存在则从路由映射表中根据hash值查找对应的处理函数,并将其赋值给handler,如果不存在则将handler设置为路由映射表(this.routes)中键为'index'的处理函数让页面去到首页。
最后我们要判断handle是否成功拿到值,只有拿到才执行后续的操作,
- 现在我们要定义注册路由的方法
register(hash,callback=function(){}){
this.routes[hash]=callback;
}
registerIndex(callback=function(){}){
this.routes['index']=callback;
}
hash:一个字符串,表示路由的哈希部分。
callback:一个可选的函数,当该路由被激活时执行。
此方法用于注册一个普通的哈希路由。它接收两个参数,第一个是路由的哈希值(hash),第二个是一个回调函数(callback),当用户导航到该哈希值其对应的函数时会被调用。第二个函数则是直接将hash值设为'index'
二、我们创建完HashRouter类,接下来我们将使用这个类来管理单页面中的页面路由,通过监听hash值的变化来无刷新地更新页面内容:
- 首先,创建了一个
HashRouter的实例router。 然后,获取页面中的一个元素container,它将用于显示不同路由对应的页面内容。
let router = new HashRouter();
let container = document.getElementById("container");
- 使用我们在HashRouter类中定义的注册路由的方法实现页面内容的更新
router.registerIndex(()=>container.innerHTML ='我是首页')
router.register('/page1',()=>container.innerHTML ='我是Page1')
router.register('/page2',function(){container.innerHTML ='我是Page2'})
router.register('/page3',()=>container.innerHTML ='我是Page3')
注册了一个路径为 /page 的路由。当哈希为 #/page 时,通过箭头函数将 container 的 innerHTML 设置为 '我是Page',显示页面的内容。
- 加载页面
router.load()
最后,调用load方法来根据当前URL的哈希值加载对应的页面内容。这个方法会检查当前的哈希值,并执行与之匹配的路由处理函数。如果哈希值不存在,则默认显示首页。
到这里,hash router就全部写完了,下面我们附上全部代码和效果图:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>手写Hash Router</title>
</head>
<body>
<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>
<script>
// hash url 改变 页面无刷新 显示单页面(SPA)的一种可能
// #/page page1 ->comtainer
// #/page2 page2 -> comtainer
// #/page3 page3 -> comtainer
class HashRouter{
constructor(){
this.routes = {}; //对象 page=> Component
window.addEventListener('hashchange',this.load.bind(this),false);
}
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) // 执行相应页面的函数 重点 如果用户输入错误的URL则不执行
}
}
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()
</script>
</body>
</html>
可以看到页面没有刷新便完成了页面内容的切换,同时网页的URL也改变了
重点:This的指向
在前面的文章中,我们学习了this,在手写hash router的过程中,我们也多次用到了this
- 构造函数中的
this: 在class HashRouter{...}定义中,当使用new关键字创建HashRouter的实例时,构造函数内部的this指向新创建的实例对象。例如,this.routes = {};中的this就指向新创建的HashRouter实例。 - 方法中的
this: 类的方法(如load,register,registerIndex)在非箭头函数形式下,默认情况下this也指向实例对象。因此,在这些方法内部可以访问和修改实例的属性(如this.routes)。 - 事件监听器中的
this: 当使用addEventListener添加事件监听器时,如果不做特殊处理,事件处理函数内的this通常指向触发事件的元素。但在本例中,通过.bind(this),手动将load函数内的this绑定到了HashRouter实例上,确保在事件处理时this能正确指向路由器实例。 - 箭头函数中的
this: 箭头函数不会创建自己的this上下文,它会从外层(词法作用域)继承this值。在注册路由处理函数时,如router.register('/page1',()=>container.innerHTML ='我是Page1'),箭头函数内的this不会指向调用它的对象(即事件处理程序的上下文),而是继承了外层作用域的this,在这里外层作用域是全局作用域或者说是立即执行的函数作用域,但因为这里没有直接使用this,所以箭头函数的这一特性在这个上下文中影响不大。重要的是,箭头函数保证了在回调函数中this的指向符合预期,即不希望它被事件处理器或定时器等改变上下文的情况所影响。 - 直接函数调用中的
this: 在非严格模式下,如果一个函数不作为对象的方法调用,而是在全局作用域中直接调用,那么其this会被绑定到全局对象(在浏览器中是window)。但在严格模式下,或者如本例中的箭头函数,this会是undefined或者按照词法作用域来确定,虽然这次没有用到,但我们还是要注意一下的
ok,今天的手写Hash Router的分享就到这了,小编还在学习中,欢迎大佬们提出建议!!!非常感谢!!!