路由原理 hash、history

2,858 阅读7分钟

服务端路由

后端路由: 又称服务器路由,

浏览器在地址栏中切换不同的url时,每次都向后台服务器发出请求,服务器响应请求,在后台拼接html文件传给前端显示,java web中的jsp就是如此实现的。常用的后台MVC模式的基本路由处理流程:浏览器输入一个url请求,从中找到Controller和Action的值,将请求传递给Controller处理,Controller获取Model数据对象,并且将Model传递给View,最后View呈现界面。

例如输入一个url:localhost/home/index

其中localhost是域名,对应结构{controller}/{action}/{id}

优点: 分担了前端的压力,html和数据的拼接都是由服务器完成。

缺点: 当项目十分庞大时,加大了服务器端的压力,同时在浏览器端不能输入制定的url路径进行指定模块的访问。另外一个就是如果当前网速过慢,那将会延迟页面的加载,对用户体验不是很友好。

浏览器端路由

1. history

首先了解一下 history 对象

blog.csdn.net/u012060033/…

最重要的,就是H5新增的方法 history.pushState

blog.csdn.net/weixin_3966…

pushState的特点

允许用户可以手动的添加一条历史记录,主要特别注意的是,该条记录不会刷新页面!

历史状态分为两种:

方式1:由传统的网页加载生成的历史状态,即向服务器请求新的页面.

方式2:通过pushState()方法生成的历史状态,并不会向服务器请求新页面。 有了历史状态,浏览器的前进后退按钮就处于可用状态,可以在历史状态之间来回跳转。

注意:刷新不会产生历史状态。

  • 对于方式1:可以随便找个网页尝试一下,比如你在a.html中跳转到b.html,此时切换后退和前进按钮,也就是切换了页面

  • 对于方式2:

而假设目前这样的记录 a.html → b.html

那么这时通过pushState添加一条记录 c.html

此时,url就会立即变为 c.html , 历史记录应变为 a.html → b.html → c.html

此时点击后退按钮,并不像方式1里那样,后退了页面,而是仅仅url变为了b.html,页面内容还是没变的(页面内容还是b.html)。

然后再点后退时,页面内容才变为 a.html

总结就是,pushState 插入的记录,不会刷新或者请求一个新的页面!!!

pushState有什么用?或者说,为什么需要无刷新地插入一条历史状态?

就是为了以后的 ajax 和spa单页面应用 才推出来的

想象一下我们的项目,首先确定的是整个项目只有一个html页面吧,单页面应用。跳转路由时,是不是url变了?然后也没跳转到一个新的页面?(单页面),这就是用到了pushState

popstate事件

window的一个事件,当点击 前进 后退 按钮时,触发!!!

或者history对象调用back、forward、go方法时才会触发

注意:pushState 和 replaceState 时,并不会触发这个事件!!!

2. 何为前端路由?

路由(Router)这个概念最先是后端出现的,是用来跟后端服务器进行交互的一种方式,通过不同的路径,来请求不同的资源,请求不同的页面是路由的其中一种功能。

前端随着 ajax 的流行,数据请求可以在不刷新浏览器的情况下进行。异步交互体验中最盛行的就是 SPA —— 单页应用。单页应用不仅仅是在页面交互时无刷新的,连页面跳转都是无刷新的,为了实现单页应用,所以就有了前端路由。

3.前端Router基本功能

一个基本的前端路由至少应该提供以下功能:

  • 前端Router可以控制浏览器的 history,使的浏览器不会在 URL 发生改变时刷新整个页面。

  • 前端Router需要维护一个 URL 历史栈,通过这个栈可以返回之前页面,进入下一个页面。

前端路由实现原理就是匹配不同的 url 路径,进行解析,然后动态的渲染出区域 html 内容。但是这样存在一个问题,就是 url 每次变化的时候,都会造成页面的刷新。那解决问题的思路便是在改变 url 的情况下,保证页面的不刷新。

目前 Router有两种实现方式 History 和 hash。

4. hash 路由

URL Hash 的形式类似如下:

可以看到 url 里面是有 # 号的,#号后面就是不同的hash路由

hash的特性:

  • hash 只作用在浏览器,不会在请求中发送给服务器。

  • hash 发生变化时,浏览器并不会重新给后端发送请求加载页面。

  • 修改 hash 时会在浏览器留下历史记录,可以通过浏览器返回按钮回到上一个页面。

  • hash 发生变化时会触发 hashchange 事件,在该事件中可以通过 window.location.hash 获取到当前 hash值。

可以看出,hash的特性,非常适合用来构造单页面路由

简单JS实现:

<body>
  <ul>
    <!-- 定义路由 -->
    <li><a href="#/home">home</a></li>
    <li><a href="#/about">about</a></li>
 
    <!-- 渲染路由对应的 UI -->
    <div id="routeView"></div>
  </ul>
</body>
 
// JavaScript 部分
 
// 页面加载完不会触发 hashchange,这里主动触发一次 hashchange 事件
window.addEventListener('DOMContentLoaded', onLoad)
 
// 监听路由变化
window.addEventListener('hashchange', onHashChange)
 
// 路由视图
var routerView = null
 
function onLoad () {
  routerView = document.querySelector('#routeView')
  onHashChange()
}
 
// 路由变化时,根据路由渲染对应 UI
function onHashChange () {
  switch (location.hash) {
    case '#/home':
      routerView.innerHTML = 'Home'
      return
    case '#/about':
      routerView.innerHTML = 'About'
      return
    default:
      return
  }
}

如果是 vue 框架,那么就不是切换 innerHTML 了,而是切换 虚拟DOM

5. history 路由

因为hash路由中url里面是带#号的不美观,所以很多采用history路由

回想一下,vue中切换路由的两种情况:

(1)点击链接 $router.push() ,跳转路由

(2)点击 前进 后退 按钮,跳转路由

可以判断出,切换页面的内容是在 window.popstate 事件中做的,

对于情况(2),之前说了,点击按钮能直接触发事件

对于情况(1),之前说了,history.pushState并不会触发事件

因此可以想象出,$router.push 方法中,共做了两个操作:


$router.push = function (path) {
    history.pushState(null, null, path)
    render(path)  //  手动触发页面内容的重渲染。。。如果是情况(1),那么就把render()写到window.popstate事件中
}

简单JS实现:

// 页面加载完不会触发 hashchange,这里主动触发一次 hashchange 事件
window.addEventListener('DOMContentLoaded', onLoad)
 
// 监听路由变化
window.addEventListener('popstate', onPopState)
 
// 路由视图
var routerView = null
 
function onLoad () {
  routerView = document.querySelector('#routeView')
  onPopState()
 
  // 拦截 <a> 标签点击事件默认行为, 点击时使用 pushState 修改 URL并更新手动 UI,从而实现点击链接更新 URL 和 UI 的效果。
  var linkList = document.querySelectorAll('a[href]')
  linkList.forEach(el => el.addEventListener('click', function (e) {
    e.preventDefault()
    history.pushState(null, '', el.getAttribute('href'))
    onPopState()
  }))
}
 
// 路由变化时,根据路由渲染对应 UI
function onPopState () {
  switch (location.pathname) {
    case '/home':
      routerView.innerHTML = 'Home'
      return
    case '/about':
      routerView.innerHTML = 'About'
      return
    default:
      return
  }
}

注意,因为url改变了,此时如果手动刷新页面,浏览器认为是请求一个新的页面,而新的页面是不存在的(因为url上的是pushState加的记录,实际上没有对应的页面),所以肯定会报错!!!

所以需要在服务端做url的重定向,即如果找不到页面,那么就定向到index.html(本身整个系统也就只有这一个html)

  • 开发时,webpack-dev-server 中已经配置好相关配置了,不需要自行添加

  • 线上,nginx 中配置了,可以看看

6. history VS hash

  • hash模式较丑,history模式较优雅

  • pushState设置的新URL可以是与当前URL同源的任意URL;而hash只可修改#后面的部分,故只可设置与当前同文档的URL

  • pushState设置的新URL可以与当前URL一模一样,这样也会把记录添加到栈中;而hash设置的新值必须与原来不一样才会触发记录添加到栈中

  • pushState通过stateObject可以添加任意类型的数据到记录中;而hash只可添加短字符串

  • pushState可额外设置title属性供后续使用

  • hash兼容IE8以上,history兼容IE10以上

  • history模式需要后端配合将所有访问都指向index.html,否则用户刷新页面,会导致404错误