两种前端路由(hash模式和history模式)实现原理解析及区别

    |     2020年3月27日   |   html/css/js, web前端技术   |     0 条评论   |    1635

单页应用(SPA)越来越成为前端主流。一大特点是使用前端路由:跳转逻辑由前端直接控制,不再完全交给后端,前端也因此更自由。

实现上主要有两种:hash 模式和history 模式。前者靠 URL 里 # 后面的片段,后者靠 HTML5 History API,看起来更像普通路径。

对比 hash history
URL 形态 #plan 这种片段 /plan/index 这种路径
改 URL 会不会请求后端 不会,只触发 hashchange pushState / replaceState 本身不请求
刷新 一般没问题 后端必须把所有路径指到 index.html,否则 404
兼容 IE8+ IE10+

一、hash 模式

做锚点跳转时就能见到 URL 后面跟 #id。hash 就是从 # 开始到结束的那一段。纷享销客就是 hash 模式:

//纷享销客系统采取的就是hash模式

 

https://www.fxiaoke.com/XV/Home/Index#plan

 

https://www.fxiaoke.com/XV/Home/Index#approve

 

https://www.fxiaoke.com/XV/Home/Index#crm/index

hash 变化浏览器不会重新发请求,但会触发 window.hashchange(原文示例里写作 hashChange)。在这个事件里读当前 hash 并改页面内容,就达到了前端路由的目的。

实现原理

//打开浏览器控制台执行如下代码

window.onhashchange = () => {

    console.log('hash :',location.hash);

    switch (location.hash) {

        case '#index':

            console.log('index>>>>');

            break

        case '#news':

             console.log('news>>>>');

            break

        default:

            console.log('404>>>>');

    }

}

location.hash = '#index'



location.hash = '#news'

 

location.hash = '#other'



 

用 hash 切换页面

HTML:菜单 href 写成 hash,id 为 app 的容器里放页面内容。

<ul id="menu">

    <li>

        <a href="#index">首页</a>

    </li>

    <li>

        <a href="#news">资讯</a>

    </li>

    <li>

        <a href="#user">个人中心</a>

    </li>

 

</ul>

 

<div id="app"></div>

JS:在 window.onhashchange 里取 hash,按不同值改 app 内容。

function hashChange(e){

    // console.log(location.hash)

    // console.log(location.href)

    //

    // console.log(e.newURL)

    // console.log(e.oldURL)

 

    let app = document.getElementById('app')

 

    switch (location.hash) {

        case '#index':

            app.innerHTML = '<h1>这是首页内容</h1>'

            break

        case '#news':

            app.innerHTML = '<h1>这是新闻内容</h1>'

            break

        case '#user':

            app.innerHTML = '<h1>这是个人中心内容</h1>'

            break

        default:

            app.innerHTML = '<h1>404</h1>'

    }

}

 

window.onhashchange = hashChange

 

hashChange()

这样就实现了一个简单的 hash 路由。

二、history 模式

hash 看起来比较丑,都带着 #。history 模式是平时看到的正常连接形式:

https://47.109.74.84#plan/index  //hash模式路由 

 

https://47.109.74.84/plan/index  //history模式路由

它基于 window.history。HTML4 里已经能控制历史记录跳转,常用方法包括:

history.forward(); //在历史记录中前进一步

   history.back(); //在历史记录中后退一步

   history.go(n): //在历史记录中跳转n步骤,n=0为刷新本页,n=-1为后退一页。

  

  在HTML5中,window.history对象得到了扩展,新增的API包括:

  

   history.pushState(data[,title][,url]);//向历史记录中追加一条记录

   history.replaceState(data[,title][,url]);//替换当前页在历史记录中的信息。

   history.state;//是一个属性,可以得到当前页的state信息。

   window.onpopstate;//是一个事件,在点击浏览器后退按钮或js调用forward()、back()、go()时触发。监听函数中可传入一个event对象,event.state即为通过pushState()或replaceState()方法传入的data参数

原理可以这样理解:给超链接加 onclick,阻止默认跳转,改用 history.pushState 或 replaceState 改浏览器 URL,同时改页面内容。History API 本身不会向后端发请求,于是也实现了前端路由。

用户点浏览器前进后退会触发 window.onpopstate,监听后再按路由改内容。菜单也不一定是超链接,任意元素在点击里调 history 即可。

实现原理

//浏览器控制台执行下列测试代码

window.onpopstate = (e) => {   

    const path = location.pathname;

    console.log('path :',path);

    switch (path) {

        case '/index':

            console.log('这是首页内容')

            break

        case '/news':

            console.log('这是新闻内容')

            break

        default:

            console.log('404找不到资源')

    }

}

/**

使用方法

pushState(state, title, url)



参数说明

state: 可以通过history.state读取

title: 可选参数,暂时没有用,建议传个短标题

url: 改变过后的url地址

*/

history.pushState({page: 1}, "title 1", "/index");

window.history.pushState({page:1},'','https://it.zhousir.top/index')

popstate 事件

用户点后退、前进,或 JS 调用 history.back()、history.go()、history.forward() 会触发 popstate;pushState、replaceState 本身不会。若要监听后两者的变化,可参考 如何监听URL的变化?。

window.addEventListener('pushState', function(e) {

  console.log('THEY DID IT AGAIN! pushState 2222222');

});

history 示例

HTML:

<ul id="menu">

    <li>

        <a href="/index">首页</a>

    </li>

    <li>

        <a href="/news">资讯</a>

    </li>

    <li>

        <a href="/user">个人中心</a>

    </li>

 

</ul>

 

<div id="app"></div>

JS:改造超链接,阻止默认跳转(默认跳转会刷新页面)。

//改造超链接,阻止默认跳转,默认的跳转是会刷新页面的

document.querySelector('#menu').addEventListener('click',function (e) {

 

    if(e.target.nodeName ==='A'){

        e.preventDefault()

 

        let path = e.target.getAttribute('href')  //获取超链接的href,改为pushState跳转,不刷新页面

 

        window.history.pushState({},'',path)  //修改浏览器中显示的url地址

        render(path)  //根据path,更改页面内容

 

    }

})

 

function render(path) {

    let app = document.getElementById('app')

    switch (path) {

        case '/index':

            app.innerHTML = '<h1>这是首页内容</h1>'

            break

        case '/news':

            app.innerHTML = '<h1>这是新闻内容</h1>'

            break

        case '/user':

            app.innerHTML = '<h1>这是个人中心内容</h1>'

            break

        default:

            app.innerHTML = '<h1>404</h1>'

    }

}

 

//监听浏览器前进后退事件,并根据当前路径渲染页面

 

window.onpopstate = function (e) {

    render(location.pathname)

}

 

//第一次进入页面显示首页

render('/index')

上面写法比较初级,可以用类封装:add 添加路由,pushState 跳转,初始化时改掉所有超链接的跳转方式。这个 demo 不能直接用于生产,只为加深理解。

三、两种模式的区别

  • hash 较丑,history 较优雅
  • pushState 的新 URL 可以是同源任意 URL;hash 只能改 # 后面,只能是当前文档内的 URL
  • pushState 即使新 URL 和当前一模一样,也会入栈;hash 必须和新值不同才会入栈
  • pushState 可通过 stateObject 往记录里放任意类型数据;hash 只能加短字符串
  • pushState 可额外设置 title 供后续使用
  • hash 兼容 IE8 以上,history 兼容 IE10 以上
  • history 需要后端把所有访问指到 index.html,否则用户刷新会 404

一句话总结:hash 改的是 # 后面、不依赖后端;history 更美观,但刷新必须由服务端兜底到同一份 SPA。

转载请注明来源:两种前端路由(hash模式和history模式)实现原理解析及区别
本文链接地址:https://ai.zhousir.top/?p=2514
回复 取消