hash路由和history路由的区别,对前段路由的理解

9,381 阅读9分钟

基础知识

前端路由主要实现的功能

  1. 记录当前页面的状态(保存或者分享当前页面的URL,再次打开该url的时候,页面还是保存或者分享的状态)
  2. 可以使用浏览器的前进,后退,跳转功能

要实现以上功能,开发者需要做的是

  1. 改变url且不让浏览器向服务器发起请求
  2. 检测url的变化,
  3. 截获url的地址,并分析出需要的信息匹配路由规则

hash模式

location的属性

location.href
//当前url的协议,包括http:/https:
location.protocol
//主机号和端口号,如果端口号是80(http)或443(https),那就回省略端口号,"localhost:8081"或者"www.cnblogs.com"
location.host
//主机号,比如www.baidu.com
location.hostname
//端口号,比如8080
location.port
//url的路径部分,从/开始,比如https://www.baidu.com/s?ie=utf-8,那么 pathname = '/s'了,从端口后,问号之前的字段
location.pathname
// 查询参数,从?开始;比如 https://www.baidu.com/s?ie=utf-8 那么 search = '?ie=utf-8'
location.search
// hash是页面中的一个片段,从 # 开始的,比如 https://www.baidu.com/#/a/b 那么返回值就是:"#/a/b"
location.hash

这里的hash是指尾巴后的 # 号以及后面的字符。这里的 # 和 css 里的 # 是一个意思。hash也称作锚点,本身是用来做页面定位的,她可以使对应 id 的元素显示在可视区域内。

由于 hash 值变化不会导致浏览器向服务器发出请求,而且 hash 改变会触发 hashchange 事件,浏览器的进后退也能对其进行控制,所以人们在 html5 的 history 出现前,基本都是使用 hash 来实现前端路由的。

window.location.hash = 'qq' // 设置 url 的 hash,会在当前url后加上 '#qq'

var hash = window.location.hash // '#qq'

window.addEventListener('hashchange', function(){
    // 监听hash变化,点击浏览器的前进后退会触发
})

history模式

history是HTML5提出来的api

首先,hash 本来是拿来做页面定位的,如果拿来做路由的话,原来的锚点功能就不能用了。其次,hash 的传参是基于 url 的,如果要传递复杂的数据,会有体积的限制,而 history 模式不仅可以在url里放参数,还可以将数据存放在一个特定的对象中。

window.history.pushState(state, title, url)
// state:需要保存的数据,这个数据在触发popstate事件时,可以在event.state里获取
// title:标题,基本没用,一般传 null
// url:设定新的历史记录的 url。新的 url 与当前 url 的 origin 必须是一樣的,否则会抛出错误。url可以是绝对路径,也可以是相对路径。
//如 当前url是 https://www.baidu.com/a/,执行history.pushState(null, null, './qq/'),则变成 https://www.baidu.com/a/qq/,
//执行history.pushState(null, null, '/qq/'),则变成 https://www.baidu.com/qq/

window.history.replaceState(state, title, url)
// 与 pushState 基本相同,但她是修改当前历史记录,而 pushState 是创建新的历史记录

window.addEventListener("popstate", function() {
    // 监听浏览器前进后退事件,pushState 与 replaceState 方法不会触发
});

window.history.back() // 后退
window.history.forward() // 前进
window.history.go(1) // 前进一步,-2为后退两步,window.history.lengthk可以查看当前历史堆栈中页面的数量

history 模式改变 url 的方式会导致浏览器向服务器发送请求,这不是我们想看到的,我们需要在服务器端做处理:如果匹配不到任何静态资源,则应该始终返回同一个 html 页面。

hash和history的区别

最明显之差别:

(1)在url显示: hash有#很Low ; history 无#好看

(2)回车刷新: hash 可以加载到hash值对应页面 ; history一般就是404掉了

(3)支持版本: hash支持低版本浏览器和IE浏览器 ; historyHTML5新推出的API

hash路由

hash模式背后的原理是onhashchange事件,可以在window对象上监听这个事件:

		window.onhashchange = function(event){
		  console.log(event.oldURL, event.newURL);
		  let hash = location.hash.slice(1);
		  document.body.style.color = hash;
		}

上面的代码可以通过改变hash来改变页面字体颜色,虽然没什么用,但是一定程度上说明了原理。

更关键的一点是,因为hash发生变化的url都会被浏览器记录下来,从而你会发现浏览器的前进后退都可以用了,同时点击后退时,页面字体颜色也会发生变化。这样一来,尽管浏览器没有请求服务器,但是页面状态和url一一关联起来,后来人们给它起了一个霸气的名字叫前端路由,成为了单页应用标配。

history路由

history路由可分为两大部分,切换和修改

切换

包括back,forward,go三个方法,对应浏览器的前进,后退,跳转操作,有同学说了,(谷歌)浏览器只有前进和后退,没有跳转,嗯,在前进后退上长按鼠标,会出来所有当前窗口的历史记录,从而可以跳转(也许叫跳更合适):

history.go(-2);//后退两次
history.go(2);//前进两次
history.back(); //后退
hsitory.forward(); //前进

修改历史状态

包括了pushState,replaceState两个方法,这两个方法接收三个参数:stateObj,title,url

history.pushState({color:'red'}, 'red', 'red'})
window.onpopstate = function(event){
console.log(event.state)
if(event.state && event.state.color === 'red'){
	document.body.style.color = 'red';
	}
}
history.back();
history.forward();

通过pushstate把页面的状态保存在state对象中,当页面的url再变回这个url时,可以通过event.state取到这个state对象,从而可以对页面状态进行还原,这里的页面状态就是页面字体颜色,其实滚动条的位置,阅读进度,组件的开关的这些页面状态都可以存储到state的里面。

具体这两个方法的用法: www.jb51.net/html5/60648…

对前端路由的理解

为什么出现前端路由

路由就是根据不同的url地址来展示不同的页面或内容的功能,这个概念首先是后端提出来的,后端之前是这么做的,当我们访问 xxx.abc.com/xx 的时候,大致流程可以想象成这样的:

  1. 浏览器向服务器发出请求。
  2. 服务器监听到80端口,如果有请求过来,那么就解析url地址。
  3. 服务器根据客户端的路由配置,然后就返回相应的信息(比如html字符串、json数据或图片等)。
  4. 浏览器根据数据包的 Content-Type来决定如何解析数据。 因为后端路由有一个很大的缺点,每次路由切换的时候都需要去刷新页面,然后发出ajax请求,然后将请求数据返回回来,那么这样每次路由切换的时候都要刷新页面,然后发出ajax请求,然后将请求数据返回回来,那么这样每次路由切换都要刷新页面对于用户体验来说就不好了,因此为了提升用户体验,我们前段路由就产生了,它就可以解决浏览器不会重新刷新了

前端路由实现的原理是什么

hash路由的话,只是hash部分改变并不会刷新页面

history路由的话,是根据history.pushSate和history.replaceState这两个方法,并不会刷新路由。

单页面应用

特点便是“通过JS渲染页面”

举个例子:以前我们直出DOM,而现在运用这些单页面框架后,HTML页面基本只有一个DOM入口,大致如下所示:

所有的页面组件,都是通过运行上图底部的app.js文件,挂载到

这个节点下面,用一个极其简单的JS展示挂在这一步骤

<body>
  <div id="root"></div>
  <script>
    const root = document.getElementById('root') // 获取根节点
    const divNode = document.createElement('div') // 创建 div 节点
    divNode.innerText = '哈哈哈哈' // 插入内容
    root.appendChild(divNode) // 插入根节点
  </script>
</body>

好的,接下来,弄出了单页面,如果十几个页面之间相互跳转切换,咋办?

这个时候,前端路由应运而生,他的穿线就是为了解决单页面网站,通过切换浏览器地址路径,来匹配相对应的页面组件

前端路由会根据浏览器地址栏pathname的变化,去匹配相应的页面组件,然后将其通过创建DOM节点的形式,塞入根节点

,这就达到了无刷新页面切换的效果,从侧面也能说明正是因为无刷新,所以 React 、 Vue 、 Angular 等现代框架在创建页面组件的时候,每个组件都有自己的 生命周期 。原理

用hash实现前端路由

hash路由,浏览器地址#后面的变化,是可以被监听到的,浏览器为我们提供了原生监听事件hashchange,它可以监听到如下的变化:

  • 点击a标签,改变了浏览器地址
  • 浏览器的前进后退行为
  • 通过window.location方法,改变浏览器地址

接下来我们利用这些特点,实现一个hash模式的建议路由:在线运行

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Hash 模式</title>
</head>
  <body>
    <div>
      <ul>
        <li><a href="#/page1">page1</a></li>
        <li><a href="#/page2">page2</a></li>
      </ul>
      <!--渲染对应组件的地方-->
      <div id="route-view"></div>
    </div>
  <script type="text/javascript">
    // 第一次加载的时候,不会执行 hashchange 监听事件,默认执行一次
    // DOMContentLoaded 为浏览器 DOM 加载完成时触发
    window.addEventListener('DOMContentLoaded', Load)
    window.addEventListener('hashchange', HashChange)
    // 展示页面组件的节点
    var routeView = null
    function Load() {
      routeView = document.getElementById('route-view')
      HashChange()
    }
    function HashChange() {
      // 每次触发 hashchange 事件,通过 location.hash 拿到当前浏览器地址的 hash 值
      // 根据不同的路径展示不同的内容
      switch(location.hash) {
      case '#/page1':
        routeView.innerHTML = 'page1'
        return
      case '#/page2':
        routeView.innerHTML = 'page2'
        return
      default:
        routeView.innerHTML = 'page1'
        return
      }
    }
  </script>
  </body>
</html>

浏览器展示效果如下 blog.csdn.net/weixin_3985…

用history实现前端路由

history 模式会比 hash 模式稍麻烦一些,因为 history 模式依赖的是原生事件 popstate ,下面是来自 MDN 的解释:

小知识:pushState 和 replaceState 都是 HTML5 的新 API,他们的作用很强大,可以做到改变浏览器地址却不刷新页面。这是实现改变地址栏却不刷新页面的重要方法。

包括a 标签的点击事件也是不会被popstate 监听。我们需要想个办法解决这个问题,才能实现history 模式。

解决思路
我们可以通过遍历页面上的所有 a 标签,阻止 a 标签的默认事件的同时,加上点击事件的回调函数,在回调函数内获取 a 标签的 href 属性值,再通过 pushState 去改变浏览器的 location.pathname 属性值。然后手动执行 popstate 事件的回调函数,去匹配相应的路由。逻辑上可能有些饶,我们用代码来解释一下: 在线地址 ———————————————— 版权声明:本文为CSDN博主「weixin_39857876」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:blog.csdn.net/weixin_3985…

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>History 模式</title>
</head>
<body>
  <div>
    <ul>
      <li><a href="/page1">page1</a></li>
      <li><a href="/page2">page2</a></li>
    </ul>
    <div id="route-view"></div>
  </div>
  <script type="text/javascript">
    window.addEventListener('DOMContentLoaded', Load)
    window.addEventListener('popstate', PopChange)
    var routeView = null
    function Load() {
      routeView = document.getElementById('route-view')
      // 默认执行一次 popstate 的回调函数,匹配一次页面组件
      PopChange()
      // 获取所有带 href 属性的 a 标签节点
      var aList = document.querySelectorAll('a[href]')
      // 遍历 a 标签节点数组,阻止默认事件,添加点击事件回调函数
      aList.forEach(aNode => aNode.addEventListener('click', function(e) {
        e.preventDefault() //阻止a标签的默认事件
        var href = aNode.getAttribute('href')
        //  手动修改浏览器的地址栏
        history.pushState(null, '', href)
        // 通过 history.pushState 手动修改地址栏,
        // popstate 是监听不到地址栏的变化,所以此处需要手动执行回调函数 PopChange
        PopChange()
      }))
    }
    function PopChange() {
      console.log('location', location)
      switch(location.pathname) {
      case '/page1':
        routeView.innerHTML = 'page1'
        return
      case '/page2':
        routeView.innerHTML = 'page2'
        return
      default:
        routeView.innerHTML = 'page1'
        return
      }
    }
  </script>
</body>
</html>

这里注意,不能在浏览器直接打开静态文件,需要通过 web 服务,启动端口去浏览网址。

参考博客:

前端浏览器刷新事件_你好,谈谈你对前端路由的理解

前端路由hash、history原理及简单的实践

简单聊聊H5的pushState与replaceState的用法