在讲前端路由之前,先说下后端路由,以及为什么出现了前端路由。
后端路由: 浏览器在地址栏中切换不同的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路径进行指定模块的访问。另外一个就是如果当前网速过慢,那将会延迟页面的加载,对用户体验不是很友好。
前端路由: 随着(SPA)单页应用的不断普及,前后端开发分离,目前项目基本都使用前端路由,在项目使用期间页面不会重新加载。
优点:1、用户体验好,和后台网速没有关系,不需要每次都从服务器全部获取,界面展现快。2、可以再浏览器中输入指定想要访问的url路径地址。3.实现了前后端的分离,方便开发。有很多框架都带有路由功能模块。
缺点:1、对SEO不是很友好2、在浏览器前进和后退时候重新发送请求,没有合理缓存数据。3,初始加载时候由于加载所有模块渲染,会慢一点。
前端路由目前主要有两种方法:
- 1、利用url的hash,就是常用的锚点(#)操作,类似页面中点击某小图标,返回页面顶部,JS通过hashChange事件来监听url的改变,IE7及以下需要轮询进行实现。一般常用框架的路由机制都是用的这种方法,例如Angualrjs自带的ngRoute和二次开发模块ui-router,react的react-route,vue-route…
- 2、利用HTML5的History模式,使url看起来类似普通网站,以”/”分割,没有”#”,但页面并没有跳转,不过使用这种模式需要服务器端的支持,服务器在接收到所有的请求后,都指向同一个html文件,通过historyAPI,监听popState事件,用pushState和replaceState来实现。具体API参考MDN文档https://developer.mozilla.org…
由于使用hash方法能够兼容低版本的IE浏览器,简单的的自己搭建前端路由。
方法一:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>前端路由实现</title>
</head>
<body>
<ul>
<li><a href="#/"></a></li>
<li><a href="#/home">主页</a></li>
<li><a href="#/about">详情页</a></li>
</ul>
<script type="text/javascript" src="router.js"></script>
<script type="text/javascript">
var content = document.querySelector('body');
Router.route('/', function() {
console.log("这是主页");
});
Router.route('/home', function() {
console.log("这是主页");
});
Router.route('/about', function() {
console.log("这是详情页");
});
</script>
</body>
</html>
router.js
//构造函数
function Router() {
this.routes = {};
this.currentUrl = '';
}
//route 存储路由更新时的回调到回调数组routes中,回调函数将负责对页面的更新
Router.prototype.route = function(path, callback) {
this.routes[path] = callback || function(){};//给不同的hash设置不同的回调函数
};
//refresh 执行当前url对应的回调函数,更新页面
Router.prototype.refresh = function() {
console.log(location.hash.slice(1));//获取到相应的hash值
this.currentUrl = location.hash.slice(1) || '/';//如果存在hash值则获取到,否则设置hash值为/
// console.log(this.currentUrl);
this.routes[this.currentUrl]();//根据当前的hash值来调用相对应的回调函数
};
//init 监听浏览器url hash更新事件
Router.prototype.init = function() {
window.addEventListener('load', this.refresh.bind(this), false);
window.addEventListener('hashchange', this.refresh.bind(this), false);
}
//给window对象挂载属性
window.Router = new Router();
window.Router.init();
方法二:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf8" />
<script type="text/javascript" src="./router.js"></script>
<style type="text/css">
</style>
</head>
<body>
<li><a href="#/"></a></li>
<li><a href="#/home">主页</a></li>
<li><a href="#/about">详情页</a></li>
</body>
<script type="text/javascript">
window.onload = function() {
var router = new Router();
router.add('home', function() {
console.log('这是主页');
});
router.add('about', function() {
console.log('这是详情页');
});
router.setIndex('home');
router.start();
};
</script>
</html>
router.js
(function() {
window.Router = function() {
var self = this;
self.hashList = {}; /* 路由表 */
self.index = null;
self.key = '/';
window.onhashchange = function() {
self.reload();
};
};
/**
* 添加路由,如果路由已经存在则会覆盖
* @param addr: 地址
* @param callback: 回调函数,调用回调函数的时候同时也会传入相应参数
*/
Router.prototype.add = function(addr, callback) {
var self = this;
self.hashList[addr] = callback;
};
/**
* 删除路由
* @param addr: 地址
*/
Router.prototype.remove = function(addr) {
var self = this;
delete self.hashList[addr];
};
/**
* 设置主页地址
* @param index: 主页地址
*/
Router.prototype.setIndex = function(index) {
var self = this;
self.index = index;
};
/**
* 跳转到指定地址
* @param addr: 地址值
*/
Router.prototype.go = function(addr) {
var self = this;
window.location.hash = '#' + self.key + addr;
};
/**
* 重载页面
*/
Router.prototype.reload = function() {
var self = this;
var hash = window.location.hash.replace('#' + self.key, '');
var addr = hash.split('/')[0];
var cb = getCb(addr, self.hashList);
if(cb != false) {
var arr = hash.split('/');
arr.shift();
cb.apply(self, arr);
} else {
self.index && self.go(self.index);
}
};
/**
* 开始路由,实际上只是为了当直接访问路由路由地址的时候能够及时调用回调
*/
Router.prototype.start = function() {
var self = this;
self.reload();
}
/**
* 获取callback
* @return false or callback
*/
function getCb(addr, hashList) {
for(var key in hashList) {
if(key == addr) {
return hashList[key]
}
}
return false;
}
})();
模拟hashchange 在低版本 IE 需要通过轮询监听 url 变化来实现:
(function(window) {
// 如果浏览器不支持原生实现的事件,则开始模拟,否则退出。
if ( "onhashchange" in window.document.body ) { return; }
var location = window.location,
oldURL = location.href,
oldHash = location.hash;
// 每隔100ms检查hash是否发生变化
setInterval(function() {
var newURL = location.href,
newHash = location.hash;
// hash发生变化且全局注册有onhashchange方法(这个名字是为了和模拟的事件名保持统一);
if ( newHash != oldHash && typeof window.onhashchange === "function" ) {
// 执行方法
window.onhashchange({
type: "hashchange",
oldURL: oldURL,
newURL: newURL
});
oldURL = newURL;
oldHash = newHash;
}
}, 100);
})(window);
主要就是监听hash的变更,然后在里面根据配置,用ajax去读对应的界面片段,然后append到主容器中就可以了。市面上的选择比较多的是:director.js,path.js,Backbone.Router,leeluolee/stateman · GitHub