服务端路由
后端路由: 又称服务器路由,
浏览器在地址栏中切换不同的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 对象
最重要的,就是H5新增的方法 history.pushState
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错误