前言
在工作和面试中,Vue-router都是比较热的知识点,Vue中的路由解决方案是基于前端路由原理进行封装实现的,因此掌握必备的前端路由原理是很有必要的。
一、路由原理
前端路由的实现起来比后端简单的多,本质是监听URL的变化,然后匹配路由规则,显示相应的页面,而无须刷新。目前前端路由的实现方法主要有两种:
- Hash模式
- History模式
二、前端路由
2.1、Hash路由
2.1.1、介绍
url的hash是以#开头,原本是用来作为锚点定位到页面特定的区域。当 # 后面的哈希值发生变化时,不会向服务器请求数据,可以通过 hashchange 事件来监听到 URL 的变化,从而进行跳转页面。
2.1.2、原理
其实现原理很简单,就是基于location.hash来实现的,localtion.hash的值就是#
后面的值。hash
具有以下几个特征:
- URL 中 hash 值只是客户端的一种状态,也就是说当向服务器端发出请求时,hash 部分不会被发送。
- hash 值的改变,都会在浏览器的访问历史中增加一个记录。因此我们能通过浏览器的回退、前进按钮控制hash 的切换。
- 我们可以使用 hashchange 事件来监听 hash 的变化。
我们可以通过两种方式出发hash的变化,一种是通过a
标签,并设置href属性,当用户点击这个标签后,URL
就会发生改变,也就会触发hashchange
事件。另一种是直接使用Javascript对loaction.hash进行赋值,从而改变URL
,触发hashchange
事件。
2.1.3、实现
基于a
标签的实现
<div>
<a href="#/">home</a>
<a href="#/admin">admin</a>
<a href="#/manager">manager</a>
<div id="content"></div>
</div>
<script>
class Router {
constructor(options) {
this.routes = {};
this.init();
// 遍历,绑定视图更新
options.forEach(item => {
this.route(item.path, () => {
document.getElementById('content').innerHTML = item.component;
});
});
}
// 绑定监听事件
init() {
window.addEventListener('load', this.updateView.bind(this), false);
window.addEventListener('hashchange', this.updateView.bind(this), false);
}
// 更新试图
updateView() {
const currentUrl = window.location.hash.slice(1) || '/';
this.routes[currentUrl] && this.routes[currentUrl]();
}
// 将路由与回调函数关联
route(path, cb) {
this.routes[path] = cb;
}
}
const router = new Router([
{
path: '/',
component: 'home'
},
{
path: '/admin',
component: 'admin'
},
{
path: '/manager',
component: 'manager'
}
]);
</script>
实现效果:
2.2、History路由
2.2.1、介绍
上面的hash方式虽然不错,但是要加上#
,并不是很美观,因此在HTML5中,又提供了History API来实现URL的变化,主要涉及的API有以下两个history.pushState()
和history.repalceState()
。
2.2.2、原理
主要通过上面两个 API可以在不进行刷新的情况下,操作浏览器的历史纪录。唯一不同的是,前者是新增一个历史记录,后者是直接替换当前的历史记录。
window.history.pushState(null, null, path);
window.history.replaceState(null, null, path);
同时,history
存在下面几个特性:
- pushState 和 repalceState 的标题(title):一般浏览器会忽略,最好传入 null ;
- 可以使用 popstate 事件来监听 url 的变化;
- history.pushState() 或 history.replaceState() 不会触发 popstate 事件,这时我们需要手动触发页面渲染;
2.2.3、实现
<div>
<a href="javascript:void(0);" data-href="/">home</a>
<a href="javascript:void(0);" data-href="/admin">admin</a>
<a href="javascript:void(0);" data-href="/manager">manager</a>
<div id="content"></div>
</div>
<script>
class Router {
constructor(options) {
this.routes = {};
this.init();
this.bindEvent();
// 遍历,绑定视图更新
options.forEach(item => {
this.route(item.path, () => {
document.getElementById('content').innerHTML = item.component;
});
});
}
// 绑定点击事件
bindEvent() {
const _this = this;
const links = document.getElementsByTagName('a');
[].forEach.call(links, link => {
link.addEventListener('click', function () {
const url = this.getAttribute('data-href');
_this.push(url);
});
});
}
// 绑定监听事件
init() {
window.addEventListener('load', this.updateView.bind(this), false);
window.addEventListener('popstate', this.updateView.bind(this), false);
}
push(url) {
window.history.pushState({}, null, url);
this.updateView();
}
// 更新试图
updateView() {
const currentUrl = window.location.pathname || '/';
this.routes[currentUrl] && this.routes[currentUrl]();
}
// 将路由与回调函数关联
route(path, cb) {
this.routes[path] = cb;
}
}
const router = new Router([
{
path: '/',
component: 'home'
},
{
path: '/admin',
component: 'admin'
},
{
path: '/manager',
component: 'manager'
}
]);
</script>
实现效果:
2.3、两种路由的对比
对比点 | Hash 模式 | History 模式 |
---|---|---|
美观性 | 带着 # 字符,较丑 | 简洁美观 |
兼容性 | >= ie 8,其它主流浏览器 | >= ie 10,其它主流浏览器 |
实用性 | 不需要对服务端做改动 | 需要服务端对路由进行相应配合设置 |
2.4、abstract路由
在vue-router代码中还有一种路由方式: 他是支持所有javascript运行模式。如果发现没有浏览器的API,路由会自动强制进入这个模式。
总结
有时间有能力可以看看vue-router的源码实现,对提高自己技能很有帮助。