路由

平时我们用的 App 总是多页面,如果用原生开发页面性能当然是很好的。但是当我们用 html 页面做移动端时,我们需要使用很多的 a 标签去链接页面,在页面简单的情况下性能也是很好的。
但是当我们的功能越来越多时,切换起来没那么流畅。又碍于网速,手机性能,页面加载进来有点慢。假如我们把需要变化的部分使用异步加载,公共的部分不变化这样就能优化性能问题,基于这个需求我们需要定制地址栏。路由就是在这样的场景下产生的。

地址栏

http://www.website.com/path/a/b/c.html?key1=1&key2=2&key3=3#/path/d/e/f.html

分析下这个 url 地址由那些部分组成?

http : 协议
www.website.com : 域名	
/path/a/b/c.html : 路径(请求地址,不要理解为文件地址)
?key1=1&key2=2&key3=3 : 参数,数据,请求时发送给服务器的数据
#/path/d/e/f.html : hash (常说的锚点) 

上面部分改变那些值会导致浏览器刷新?

协议, 域名, 路径, 参数 这些数据都是与服务器进行交互的。唯独 hash 是与浏览器进行交互的。

hash

改变 hash 的值浏览器不会刷新


点击测试, 观察浏览器地址的变化

#hash-1 #hash-2 #hash-3 #hash-4 #hash-5

点击上面测试, 观察浏览器地址的变化

Source code

<a class="btn btn-default" href="#hash-1">#hash-1</a>
<a class="btn btn-default" href="#hash-2">#hash-2</a>
<a class="btn btn-default" href="#hash-3">#hash-3</a>
<a class="btn btn-default" href="#hash-4">#hash-4</a>
<a class="btn btn-default" href="#hash-5">#hash-5</a>
<br/>
<div id="hash-box" style="color: red;">点击上面测试, 观察浏览器地址的变化</div>



<script>
	//监听地址栏 hash 参数值的变化
	window.addEventListener("hashchange", function(){
		//变化后输出当前地址栏中的值
		document.getElementById("hash-box").innerHTML = location.hash;
	});
</script>

定制路由

var Hash = (function(){
 	var hashroute = {};
 	function hashchange(){
		var url = window.location.hash;
		url = url.replace(/^#/i,""); //清除开头的 # 字符
		//判断 url 是否在路由中
		if(url && url in hashroute){
			//循环执行添加的路由
			hashroute[url].forEach(function(hook) {
				//将当前触发的路由地址传入回去
				hook(url);
			});
		}
	}
	//监听地址栏 hash 参数值的变化
	window.addEventListener("hashchange", function(){
		//变化后调用地址栏 hash 值的解析
		hashchange(); 
	});
 	function main(){
 		//添加路由
		this.addroute = function(url,hook){
			//判断路由是否存在
			if(url in hashroute){
				//存在的情况下将回调函数添加到路由对应的值中
				hashroute[url].push(hook);
			}else{
				//不存在直接将回调函数保存起来
				hashroute[url] = [hook];
			}
			//以数组的形势保存回调函数是因为避免出现一个路由触发两个函数
		}

		//更新当前路由
		this.refresh = function(){
			hashchange(); 
		};
 	}
 	return main;
 })();
var hash = new Hash();
//监听 hash-1 地址变化
hash.addroute("hash-1",function(url){
	//输出当前触发的路由
	console.log(url);
});

//监听 hash-2 地址变化
hash.addroute("hash-2",function(url){
	//输出当前触发的路由
	console.log(url);
});

动态路由

从上面的看,所有的路有都是固定的,不能动态监听,比如下面的

/html/test.html
/html/a/b/c/d.html

上面两个路由地址都由一个函数触发
可以从这两个地址上看以出来,这两个路由都必须是 /html 开头且以 .html 结尾

#hash-1 #hash-2 #hash-3 #hash-4 #hash-5

点击上面测试, 观察浏览器地址的变化
<a class="btn btn-default" href="#/html/a.html">#hash-1</a>
<a class="btn btn-default" href="#/html/a/b/c.html">#hash-2</a>
<a class="btn btn-default" href="#/path/3333/detail.html">#hash-3</a>
<a class="btn btn-default" href="#/path/4444/detail.html">#hash-4</a>
<a class="btn btn-default" href="#/page/5555.html">#hash-5</a>
<br/>
<div id="hash-box2" style="color: red;">点击上面测试, 观察浏览器地址的变化</div>
var Hash = (function(){
 	var hashroute = {};
 	//添加一个动态路由集合
 	var regextroute = [];
 	//地址解析的
 	function Url(href){
 		href = href.replace(/^#+/,""); //清除开头的 # 字符
 		var url = href.split("?");
		var search = (url[1]) || "";
		var match = search.match(/(^|&)([\w]+)/g) || [];
		var query = {};
		match.forEach(function(key){
			key = key.replace(/&/,"");
			var reg = RegExp(`(&|^)${key}=([^&].*?)(&|$)`);
			var value = search.match(reg) || [];
			query[key] = value[2] ? decodeURI(value[2]) : void 0;
		});

		return {
			"pathname" : url[0],
			"href" : href,
			"search" : search,
			"query" : query
		};
 	}
 	//路由事件响应
 	function hashchange(){
 		var location = new Url(window.location.hash);
		var url = location.pathname;
		var state = true; //假设静态固定的路由里面匹配不到此次触发的地址
		//判断 url 是否在路由中
		if(url && url in hashroute){
			state = false; //假设条件不成立,已经匹配到
			//循环执行添加的路由
			hashroute[url].forEach(function(hook) {
				//将当前触发的路由地址传入回去
				hook(location);
			});
		}
		if(state == true){
			//循环以前的所有路由,依次匹配保存的路由
			for(var i=0 , len = regextroute.length; i < len;i++){
				var item = regextroute[i];
				//测试字符串是否匹配正则条件
				if(item.reg.test(url)){
					//循环勾子中的回调函数
					item.hooks.forEach(function(hook) {
						hook(location);
					});
					state = false;
					break;
				}
			}
		}
		//证明上面两种都不存在,现在是默认路由,执行默认路
		if(state == true && hashroute["/default"]){
			hashroute["/default"].forEach(function(hook) {
				hook(location);
			});
		}
	}
	//监听地址栏 hash 参数值的变化
	window.addEventListener("hashchange", function(){
		//变化后调用地址栏 hash 值的解析
		hashchange(); 
	});
	//添加路由事件
 	return function Hash(){
 		//添加路由
		this.addroute = function(url,hook){
			switch (typeof url) {
				//固定的字符串格式路由
				case "string":
					//判断路由是否存在
					if(url in hashroute){
						//存在的情况下将回调函数添加到路由对应的值中
						hashroute[url].push(hook);
					}else{
						//不存在直接将回调函数保存起来
						hashroute[url] = [hook];
					}
					break;
				case "object":
					//假设以前没有添加该路由
					var state = true; 
					//循环以前的所有路由,依次判断新添加的路有以前是否添加过
					for(var i=0 , len = regextroute.length; i < len;i++){
						var item = regextroute[i];
						var reg = item.reg;
						//判断两个正则的匹配条件是否相同
						//在匹配两个正则的修饰符是否相同
						if(reg.source == url.source && reg.flags == url.flags){
							//把回调函数添加到勾子中
							item.hooks.push(hook);
							//上面的假设条件不成立,因为比对到相同的正则
							state = false;
							break;
						}
					}
					//假设条件成立
					if(state == true){
						//将新的路有添加到集合中
						regextroute.push({
							"reg" : url,
							"hooks" : [ hook ]
						});
					}
					break;
				default:
					// statements_def
					break;
			}
		}
		//更新当前路由
		this.refresh = function(){
			hashchange(); 
		};
		this.url = function(){
			return new Url(window.location.hash);
		}
 	};
 })();
var hash = new Hash();
//监听 hash-1/2 地址变化
hash.addroute(/^\/html\/.+\.html/,function(url){
	//输出当前触发的路由
	document.getElementById("hash-box2").innerHTML = url;
});

//监听 hash-3/4 地址变化
hash.addroute(/^\/path\/[0-9]+\/detail\.html/,function(url){
	//输出当前触发的路由
	document.getElementById("hash-box2").innerHTML = url;
});

//监听 hash-5 地址变化
hash.addroute(/^\/page\/[0-9]+\.html/,function(url){
	//输出当前触发的路由
	document.getElementById("hash-box2").innerHTML = url;
});