在传统的 Web 开发中,浏览器根据地址栏的 URL 向服务器发送一个 HTTP 请求,服务器根据 URL 返回一个 HTML 页面。这种状况下,一个 URL 对应一个 HTML 页面, 一个 Web 应用包含不少 HTML 页面,这样的应用就是多页面应用;在多页面应用中,页面路由的控制由服务器负责,这种路由方式称为后端路由。javascript
在多页面应用中,每次页面切换都须要向服务器发送一次请求,页面使用的静态资源也须要从新加载,存在必定的浪费。并且,页面的总体刷新对用户体验也有影响,由于不一样页面 间每每存在共同的部分,例如导航栏、侧边栏等,页面总体刷新也会致使公共部分的刷新。html
有没有一种方式让 Web 应用只是看起来像多页面应用,也就是说 URL 的变化能够引发页面内容的变化,但不会向服务器发送新的请求哪?知足这种条件的 Web 应用就是单页面 应用(Single Page Application,简称 SPA)。单页面应用虽然名为”单页“,但视觉上的感觉仍然是多页面,由于 URL 发生变化,页面上的内容也会变化,但这只是逻辑上的多页面,实际上不管 URL 如何变化,对应的 HTML 文件都是同一个,这也是单页面应用名字的由来。在单页面应用中,URL 发生变化并不会向服务器发送新的请求,因此”逻辑页面“的变化只能由前端负责,这种方式称为前端路由。前端
路由就是 URL 到函数的映射,这个是前端路由的原理。若是作到在 URL 发生变化的时候不向服务器发送请求,而是去执行一个控制 UI 组件的函数哪?那就不得不说说 hash 和 history 这两种实现方案了。java
在一个 URL 的组成中,#
号包括#
号后边的部分称为 hash。在浏览器中,能够经过location.hash
获取到。#
表明网页中的一个位置,其右边的字符,就是该位置的标识符。好比:git
// #title 是 hash
http://www.example.com/index.html#title
复制代码
#
号是用来指导浏览器动做的,对服务器彻底不起做用,HTTP 请求不会带上#
号以及它后边的内容。单单改变#
号后边的内容,浏览器只会滚动到指定的位置,不会从新加载网页。并且改变 hash 还会改变浏览器的历史记录。咱们能够经过onhashchange
监听到 hash 的改变来不刷新浏览器触发视图的更新。代码以下:github
<ul>
<li><a href="#">white</a></li>
<li><a href="#yellow">yellow</a></li>
<li><a href="#green">green</a></li>
</ul>
这是页面的内容
复制代码
function Router() {
this.routes = {};
this.currentUrl = "";
}
Router.prototype.route = function(path, callback) {
this.routes[path] =
callback ||
function() {
console.log("请为路由绑定处理方法");
};
};
Router.prototype.refresh = function() {
console.log("触发一次 hashchange,hash值为", location.hash);
this.currentUrl = "/" + location.hash.slice(1);
// 执行当前路由绑定的方法
this.routes[this.currentUrl]();
};
Router.prototype.init = function() {
window.addEventListener("DOMContentLoaded", this.refresh.bind(this), false);
window.addEventListener("hashchange", this.refresh.bind(this), false);
};
window.Router = new Router();
window.Router.init();
var content = document.querySelector("body");
function changeBgColor(color) {
content.style.backgroundColor = color;
}
Router.route("/", function() {
changeBgColor("white");
});
Router.route("/yellow", function() {
changeBgColor("yellow");
});
Router.route("/green", function() {
changeBgColor("green");
});
复制代码
在 HTML5 规范中,history新增了一下几个 API:后端
history.pushState(); // 添加新的状态到历史状态栈
history.replaceState(); // 用新的状态代替当前状态
history.state // 返回当前状态对象
复制代码
经过上面两个操做状态的 API,也可以作到:改变 url 的同时,不刷新页面。因此 history 也具有实现路由控制的潜力。仅仅是改变 url 不刷新页面还不够,还要可以监听到 url 的变化。对于 hash 来讲,hash 的改变能够出发 onhashchange 事件,history 并无这样的事件能够监听。然而,对于一个应用来讲,改变一个 url 只有下面三种途径:浏览器
第 2 种和第 3 种途径能够当作是一种,由于 a 标签的默认事件能够被禁止,进而调用 js 方法。关键是第 1 种,HTML5 规范种新增了一个 onpopstate 事件,经过它即可以监听到前进或者后退的按钮点击。要特别注意的是:调用history.pushState
和history.replaceState
并不会触发 onpopstate 事件。bash
// 页面加载完不会触发 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
}
}
复制代码
hash 模式下,每一个 url 都会带有#
号,看起来可能不太友好。可是,hash 模式兼容 IE8 及其以上的浏览器。history 模式使用了 HTML5 里边新的 API,看起来会比较友好。可是,仅仅有前端的参与仍是不够的,须要后端进行配置,前端的路由要和后端的路由要匹配起来,在刷新浏览器的时候会给后端发送请求,这个时候后台须要对请求的 url,作一个捕捉,后端不存在的 url,统一返回请求根路径时的前端环境,交由前端路由模块处理。服务器