SPA 是 single page web application 的简称,译为单页Web应用。
SPA 就是一个 WEB 项目只有一个 HTML 页面,一旦页面加载完成,SPA 不会因为用户的操作而进行页面的重新加载或跳转。 取而代之的是利用 JS 动态的变换 HTML 的内容,从而来模拟多个视图间跳转
简单的说,就是在保证只有一个 HTML 页面,且与用户交互时不刷新和跳转页面的同时,为 SPA 中的每个视图展示形式匹配一个特殊的 url。在刷新、前进、后退和SEO时均通过这个特殊的 url 来实现。
路由需要实现三个功能:
接下来要介绍的 hash 模式和 history 模式,就是实现了上面的功能
http://localhost/index.html#abc,这里的#abc就是hash;window.location.hash值的变化,从而触发 onhashchange 事件;http://www.baidu.com/#home,这时按下输入,浏览器发送http://www.baidu.com/请求至服务器,请求完毕之后设置散列值为#home,进而触发onhashchange事件;<a>标签的属性 href 可以设置为页面的元素ID如 #top,当点击该链接时页面跳转至该id元素所在区域,同时浏览器自动设置 window.location.hash 属性,地址栏中的哈希值也会发生改变,并触发 onhashchange 事件;History.length:当前窗口访问过的网址数量(包括当前网页,由于安全原因,浏览器不允许脚本读取这些网址,但是允许在地址之间导航。)History.state:History 堆栈最上层的状态值History.back(): 移动到上一个网址,等同于点击浏览器的后退键。对于第一个访问的网址,该方法无效果。History.forward():移动到下一个网址,等同于点击浏览器的前进键。对于最后一个访问的网址,该方法无效果。History.go(): 接受一个整数作为参数,以当前网址为基准,移动到参数指定的网址。如果参数超过实际存在的网址范围,该方法无效果;如果不指定参数,默认参数为0,相当于刷新当前页面。
history.go(0) // 刷新当前页面
history.go(1) // 相当于history.forward()
history.go(-1) // 相当于history.back()
注意:移动到以前访问过的页面时,页面通常是从浏览器缓存之中加载,而不是重新要求服务器发送新的网页。
History.pushState():用于在历史中添加一条记录。pushState()方法不会触发页面刷新,只是导致 History 对象发生变化,地址栏会有变化;该方法接受三个参数,依次为:
object:是一个对象,通过 pushState 方法可以将该对象内容传递到新页面中。如果不需要这个对象,此处可以填 null。title:指标题,几乎没有浏览器支持该参数,传一个空字符串比较安全。url:新的网址,必须与当前页面处在同一个域。不指定的话则为当前的路径,如果设置了一个跨域网址,则会报错。History.replaceState(): 用来修改 History 对象的当前记录,用法与 pushState() 方法一样。pushState()方法或replaceState()方法 ,并不会触发该事件;History.back()、History.forward()、History.go()方法时才会触发。//http://127.0.0.1:8001/01-hash.html?a=100&b=20#/aaa/bbb
location.protocal // 'http:'
localtion.hostname // '127.0.0.1'
location.host // '127.0.0.1:8001'
location.port //8001
location.pathname //'01-hash.html'
location.serach // '?a=100&b=20'
location.hash // '#/aaa/bbb'
以下代码拷贝至面试官: 你了解前端路由吗?
在线例子 ```js class Routers { constructor() { // 储存hash与callback键值对 this.routes = {}; // 当前hash this.currentUrl = ‘’; // 记录出现过的hash this.history = []; // 作为指针,默认指向this.history的末尾,根据后退前进指向history中不同的hash this.currentIndex = this.history.length - 1; this.refresh = this.refresh.bind(this); this.backOff = this.backOff.bind(this); // 默认不是后退操作 this.isBack = false; window.addEventListener(‘load’, this.refresh, false); window.addEventListener(‘hashchange’, this.refresh, false); }
route(path, callback) { this.routes[path] = callback || function() {}; }
refresh() {
this.currentUrl = location.hash.slice(1) || ‘/’;
if (!this.isBack) {
// 如果不是后退操作,且当前指针小于数组总长度,直接截取指针之前的部分储存下来
// 此操作来避免当点击后退按钮之后,再进行正常跳转,指针会停留在原地,而数组添加新hash路由
// 避免再次造成指针的不匹配,我们直接截取指针之前的数组
// 此操作同时与浏览器自带后退功能的行为保持一致
if (this.currentIndex < this.history.length - 1)
this.history = this.history.slice(0, this.currentIndex + 1);
this.history.push(this.currentUrl);
this.currentIndex++;
}
this.routesthis.currentUrl;
console.log(‘指针:’, this.currentIndex, ‘history:’, this.history);
this.isBack = false;
}
// 后退功能
backOff() {
// 后退操作设置为true
this.isBack = true;
this.currentIndex <= 0
? (this.currentIndex = 0)
: (this.currentIndex = this.currentIndex - 1);
location.hash = #${this.history[this.currentIndex]};
this.routesthis.history[this.currentIndex];
}
}
### History路由
> [在线例子](https://codepen.io/xiaomuzhu/pen/QmJorQ/)
```js
class Routers {
constructor() {
this.routes = {};
// 在初始化时监听popstate事件
this._bindPopState();
}
// 初始化路由
init(path) {
history.replaceState({path: path}, null, path);
this.routes[path] && this.routes[path]();
}
// 将路径和对应回调函数加入hashMap储存
route(path, callback) {
this.routes[path] = callback || function() {};
}
// 触发路由对应回调
go(path) {
history.pushState({path: path}, null, path);
this.routes[path] && this.routes[path]();
}
// 监听popstate事件
_bindPopState() {
window.addEventListener('popstate', e => {
const path = e.state && e.state.path;
this.routes[path] && this.routes[path]();
});
}
}