spa流行的今天很多同窗会把前端路由跟后端路由弄混, 莫名其妙的怎么页面404啦之类'奇怪'的问题, 其实这就是没弄清楚前端路由和后端路由的缘由(固然你用hash当我没说).css
本文全部前端路由都是spa的状况下, 不存在后端渲染好变量的状况html
首先咱们看看先后端路由在浏览器中是怎么工做的, 上图:前端
后端控制的路由:jquery
咱们能够知道后端其实返回的是html字符串, 也就是dom节点不出意外的话是确认的. 无论你请求多少次, 结果都是肯定的(get 幂等). 因此也就不存在404的状况ajax
前端控制的路由:bootstrap
若是是spa的话, 咱们能够知道无论你请求那个页面, 在后端处理好的状况下后端都会返回一个html文件(所谓单页的由来), 静态资源固然也是相似的. 那么咱们可能有点疑问, 好比一个我的主页, 若是只返回一个html文件的话, 怎么获得不一样的用户资料呢, 答案就是前端路由(大部分状况, 不排除本地存储😂), js根据不一样的路由再向服务器请求相关资料, 也就是说其实第一次服务端渲染咱们的页面是空的, 后期ajax请求. 因此咱们看到不少单页页面打开了首先要loading一会. 就是在向服务器请求渲染页面.后端
后端路由咱们暂且不去管它, 咱们看看是怎么实现的:浏览器
在非hash的状况下, 前端路由的实现基础是window.history, 固然咱们不用去管它的兼容性了, 反正如今大部分浏览器能用就是了: 服务器
history
有个重要的方法就是pushState
, 其它的方法暂时用不到不提, 它的做用呢就是改变浏览器地址栏里的地址, 以及在历史纪录里加上一条, 除此之外没啥别的反作用了, 好比:app
var stateObj = { foo: "bar" };
history.pushState(stateObj, "page 2", "bar.html");
复制代码
上面的代码只会跳到一个 bar.html
的地址, 可是页面自己并未跳转, 咱们不是来说history对象自己的, 有兴趣能够自行翻看mdn.
其实参考后端对路由的控制, 咱们大略能够想像一个前端路由所具备的功能:
固然咱们如今一切从简, 上面那些说清楚了起实现无非就是苦力了, 先给你们看看效果吧:
仍是有点意思的吧.
下面是html代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-default nav-static-top">
<div class="container-fluid">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<a class="navbar-brand" href="#">LOGO</a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li><a href="/1" data-role="custom-history">地址1 <span class="sr-only">(current)</span></a></li>
<li><a href="/2" data-role="custom-history">地址2</a></li>
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
<div id="app" class="container">
<div class="panel panel-default">
<div class="panel-heading">Panel heading without title</div>
<div class="panel-body">
Panel content
</div>
</div>
</div>
<script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script>
<script src="./route.js"></script>
<script src="./index.js"></script>
</body>
</html>
复制代码
index.js
const $routeController = $('a[data-role=custom-history]')
const app = new Route()
app.set('/1', function () {
$('#app').html('1')
})
app.set('/2', function () {
$('#app').html(2)
})
复制代码
route.js
class Route {
constructor () {
this.urls = []
this.handles = {}
window.addEventListener('popstate', (e) => {
const state = e.state || {}
const url = state.url || null
if (url) {
this.refresh(url)
}
})
const $routeController = $('a[data-role=custom-history]')
$routeController.on('click', e => {
e.preventDefault()
const link = $(e.target).attr('href')
history.pushState({ url: link }, '', link)
this.refresh(link)
})
}
set (route, handle) {
if (this.urls.indexOf(route) === -1) {
this.urls.push(route)
this.handles[route] = handle
}
}
refresh (route) {
if (this.urls.indexOf(route) === -1) throw new Error('请不要这样调用, 路由表中不存在!')
this.handles[route]()
}
}
复制代码
按个人本意是不想在一篇文章里贴这么多代码的, 可是由于也不能够直接嵌入jsbin之类的, 方便你们试试看效果, 就放进来把, 由于代码比较简单, 并且深度绑定到了dom上, 就不要嘲笑啦!