前言:
事情的起因是最近临近春节,许久未曾联系的高中同学和想和我见一面聊聊诗和远方。于是我顺着他的话问道,聚一起有什么节目安排。他却发了一句话,我定睛一看,上面写着898 SPA。我一看怒了,想着SPA(Single - Page Application) 不是不要米的么?想到还有很多友友因为不了解SPA还在为自己的物质买单,于是我义正言辞地拒绝了他的安排,关掉了手机,开始手写SPA。
SPA为什么能由hash router完成
SPA
它是一种现代的 Web 应用架构模式,在这种模式下,整个 Web 应用只有一个 HTML 页面。当用户与应用进行交互,如点击链接、提交表单等操作时,不会像传统的多页面应用那样导致浏览器重新加载整个页面,而是通过 JavaScript 动态地更新页面的部分内容来模拟页面跳转的效果,提供类似于桌面应用的流畅用户体验。
锚点
HTML 中的一种元素,用于在一个页面内创建链接,使得用户点击链接后能够跳转到同一页面中的特定位置。它通过<a>标签来实现,并且通常会带有一个href属性,其值以#开头,后面跟着目标锚点的名称。我们便可以使用锚点的这个特性实现哈希路由,使得点击链接但是页面不会刷新。
<body>
<a href="#1">111</a>
<a href="#2">222</a>
<a href="#3">333</a>
</body>
当点击111时,页面不会刷新而通过url我们可以发现确实页面已经实现跳转了这就是锚点,给href链接前添加#
hashchange事件
当我们的url地址发生更变时,我们能够通过将hashchange事件挂载到window上,监听到这次的变化
<script>
window.addEventListener('hashchange', () => {
console.log('url改变了');
})
</script>
开始搭建哈希路由
初始化
通过仿照vue中的哈希路由的方法,我们可以首先构造一个class类,实现复用性。在这个类当中写入routes对象当做哈希表使用,并添加hashchange事件,使得每次页面变化时我们都能够监听到,并实现页面的变化。
class myHashRouter{
constructor(){
const routes={}
window.addEventListener('hashchange',()=>{
})
}
}
注册与加载功能
每个链接我们都需要为它注册属于它自己的路由,其实就是将链接与链接相应完成的功能存入准备好的哈希表(routes)中,并写一个专门用来完成链接相应功能的加载函数
class myHashRouter {
constructor() {
this.routes = {}
window.addEventListener('hashchange', () => {
this.load()
})
}
register(route, callback = function () { }) {
this.routes[route] = callback
}
load() {
let route = location.hash.slice(1)
let method
if (!route) {
method = this.routes['index']
}
else {
method = this.routes[route]
}
method && method.call(this)
}
}
如以上代码所示,首先在constructor()方法体中的hashchange事件回调了load()方法。紧接着开始写了一个注册的函数register(),将传入的路径作为了哈希表的键,而把传入的函数作为了键对应的值完成在哈希表上注册路由。而对于load()方法,首先通过loaction拿到当前页面的路由,slice方法可以将字符串切割传入的长度(切的是原字符串),由于锚点总是会携带一个#不利于可读性,所以用这种方式将#去除。接着定义了method作为回调函数。当我们检测不到route时,这时可以存入一个默认的值'index'默认到首页。最后是一个短路操作,如何method存在,则会执行method并使用call方法指向调用该函数的实例化对象(this)。
默认跳转首页
由于第一次进入界面时,我们没有默认点击某个锚点,因此,需要特地写入一个注册默认地址的方法并添加到class类当中。同时由于我们在前文当中提到的hashchange事件中对load调用其实不完全,这是因为load函数体内this会被指向事件源,而不是实例化对象,又因为这里需要添加的是一个函数,因此在此处使用bind显示绑定在合适不过了。
constructor() {
const routes = {}
window.addEventListener('hashchange', this.load.bind(this))
}
registerHome(callback = function () { }) {
this.routes['index'] = callback
}
实现
万事具备只欠东风,该到了SPA的时候了。
<body>
<a href="#/1">111</a>
<a href="#/2">222</a>
<a href="#/3">333</a>
<div class="box"></div>
</body>
<script>
class myHashRouter {
constructor() {
this.routes = {}
window.addEventListener('hashchange', this.load.bind(this))
}
registerHome(callback = function () { }) {
this.routes['index'] = callback
}
register(route, callback = function () { }) {
this.routes[route] = callback
}
load() {
let route = location.hash.slice(1)
let method
if (!route) {
method = this.routes['index']
}
else {
method = this.routes[route]
}
method && method.call(this)
}
}
const box = document.querySelector('.box')
const router = new myHashRouter()
router.registerHome(() => { box.innerHTML = '首页' })
router.register('/1', () => { box.innerHTML = '我是1' })
router.register('/2', () => { box.innerHTML = '我是2' })
router.register('/3', () => { box.innerHTML = '我是3' })
router.load()
</script>
首先我准备了一个容器用于展示哈希表中的回调函数的结果。实例化对象router后接着对首页的路由进行注册registerHome,然后将地址传入对三个锚点进行注册并将callback函数传入。最后对首页渲染调用load()使得一进来就加载首页。结果如下图所示。