前端路由hash模式与history模式

96 阅读2分钟

背景

目前单页应用(SPA)越来越成为前端主流,单页应用一大特点就是使用前端路由,由前端来直接控制路由跳转逻辑,而不再由后端人员控制,这给了前端更多的自由。

目前前端路由主要有两种实现方式:hash 模式和 history模式,下面分别详细说明。

hash模式

hash模式是基于location.hash来实现的,通过onhashchange事件监听hash改变,从而对页面进行跳转(渲染)
比如下面这个网站,它的 location.hash 的值为 '#home':

https://www.test.com#home

特点

  • URL中的hash值只是客户端的一种状态,服务器发出请求时,hash部分不会被发送
  • hash值的改变,都会在浏览器历史中增加一个记录,因此我们可以通过浏览器的前进、后退控制hash的切换
  • 可以通过hashchange事件来监听hash的变化

history模式

history模式通过H5中History API来实现url的变化。主要通过 pushState()和replaceState()这两个api结合popstate事件实现页面跳转(渲染)
history 模式与原url无异,不带#字符,比如下面这个网站

https://www.test.com/home

特点

  • pushState、repalceState 两个API来操作实现URL的变化
  • 通过popstate事件来监听url变化
  • pushState、replaceState不会触发popstate事件,需要手动触发页面跳转

区别:

  1. hash模式,#后的信息不会包含在http请求中,history需要后端进行适配,否则刷新会访问真实url
  2. 兼容性:hash兼容IE8以上、history兼容IE10以上
  3. 描述性:hash只能添加字符串,history可以添加title、state等描述对象
  4. 局限性:hash只能改变#后的字符串,history模式可以修改同源的任意URL

进阶

实现一个Router类,通过add方法添加路由配置,第一个参数为路由路径,第二个参数为render函数,返回要插入页面的html;通过listen方法,监听hash变化,并将每个路由返回的html,插入到app中。
以下分别通过hash模式与history模式进行实现

手写hash模式

class Router {
    constructor(){
        this.routers = []  //存放我们的路由配置
    }
    add(route,callback){
        this.routers.push({
            path:route,
            render:callback
        })
    }
    listen(callback){
        window.onhashchange = this.hashChange(callback)
        this.hashChange(callback)()  //首次进入页面的时候没有触发hashchange,必须要就单独调用一下
    }
    hashChange(callback){
        let self = this
        return function () {
            let hash = location.hash
            for(let i=0;i<self.routers.length;i++){
                let route = self.routers[i]
                if(hash===route.path){
                    callback(route.render())
                    return
                }
            }
        }
    }
}
 
let router = new Router()
router.add('#index',()=>{
    return '<h1>这是首页内容</h1>'
}) 
router.add('#news',()=>{
    return  '<h1>这是新闻内容</h1>'
})
router.add('#user',()=>{
    return  '<h1>这是个人中心内容</h1>'
})
router.listen((renderHtml)=>{
    let app = document.getElementById('app')
    app.innerHTML = renderHtml
})

手写history模式

class Router {
    constructor(){
        this.routers = []
        this.renderCallback = null
    }
    add(route,callback){
        this.routers.push({
            path:route,
            render:callback
        })
    }
    pushState(path,data={}){
        window.history.pushState(data,'',path)
        this.renderHtml(path)
    }
    listen(callback){
        this.renderCallback = callback
        this.changeA()
        window.onpopstate = ()=>this.renderHtml(this.getCurrentPath())
        this.renderHtml(this.getCurrentPath())
    }
    changeA(){
        document.addEventListener('click', (e)=> {
            if(e.target.nodeName==='A'){
                e.preventDefault()
                let path = e.target.getAttribute('href')
                this.pushState(path)
            }
        })
    }
    getCurrentPath(){
        return location.pathname
    }
    renderHtml(path){
        for(let i=0;i<this.routers.length;i++){
            let route = this.routers[i]
            if(path===route.path){
                this.renderCallback(route.render())
                return
            }
        }
    }
}
  
let router = new Router()
router.add('/index',()=>{
    return '<h1>这是首页内容</h1>'
})
router.add('/news',()=>{
    return  '<h1>这是新闻内容</h1>'
})
router.add('/user',()=>{
    return  '<h1>这是个人中心内容</h1>'
})
router.listen((renderHtml)=>{
    let app = document.getElementById('app')
    app.innerHTML = renderHtml
})