JavaScript hash 与 history 实现客户端路由的原理

客户端路由有两种实现方式:基于hash 和基于html5 history api。
vue-router 组件就是基于它们来实现的。

vue-router 组件中的路由涉及到了三个基本的概念: route, routes, router。

  1. route,它是一条路由,由这个英文单词也可以看出来,它是单数, Home按钮 => home内容, 这是一条route, about按钮 => about 内容, 这是另一条路由。
  2. routes 是一组路由,把上面的每一条路由组合起来,形成一个数组。[{home 按钮 =>home内容 }, { about按钮 => about 内容}]
  3. router 是一个机制,相当于一个管理者,它来管理路由。因为routes 只是定义了一组路由,它放在哪里是静止的,当真正来了请求,怎么办? 就是当用户点击home 按钮的时候,怎么办?这时router 就起作用了,它到routes 中去查找,去找到对应的 home 内容,所以页面中就显示了 home 内容。
  4. 客户端中的路由,实际上就是dom 元素的显示和隐藏。VUE会将页面中所有内容全部组件化的,根据当前路由分别做显示或隐藏。例如:当页面中显示home 内容的时候,about 中的内容全部隐藏,反之也是一样。

回到 H5 的 history 上来。

history 可以通过参数来保存用户的操作,并且无需刷新就可以在页面间进行切换。常见于单页面应用(SPA,Simple Page Application)框架里。

window.history 对象在编写时可不使用 window 这个前缀。

  • history.back()
    加载历史列表中的上一个 URL,与浏览器点击后退按钮作用相同。等价于history.go(-1)。
    返回上一页时,页面通常是从浏览器缓存之中加载,而不是重新要求服务器发送新的网页。
  • history.forward()
    加载历史列表中的下一个 URL,与浏览器点击向前按钮作用相同。等价于history.go(-1)。
  • history.go(int n)
    移动到该整数指定的页面,比如go(1)相当于forward(),go(-1)相当于back(),history.go(0)相当于刷新当前页面。

如果移动的位置超出了访问历史的边界,以上三个方法并不报错,而是默默的失败。

  • history.scrollRestoration
    设置滚动恢复行为。通俗的来讲就是,当我们从当前页面跳转到a页面,随后又从a页面回退过来的时候,我们的页面是否应该自动滚动到我们之前浏览的位置。
    scrollRestoration默认值是auto,默认滚动恢复。另外一个值是manual,表示不会默认恢复

  • window.location
    对象用于获得当前页面的地址 (URL),或将浏览器重定向到新的页面。

    • location.hash 设置或返回从井号 (#) 开始的 URL(锚)
    • location.host 设置或返回主机名和当前 URL 的端口号
    • location.hostname 设置或返回当前 URL 的主机名
    • location.href 属性返回当前页面的 URL
    • location.pathname 设置或返回当前 URL 的路径部分
    • location.port 设置或返回当前 URL 的端口号
    • location.protocol 设置或返回当前 URL 的协议(http:// 或 https://)
    • search 设置或返回从问号 (?) 开始的 URL(查询部分)
    • location.assign(url) 加载新的文档
    • location.reload() 重新加载当前文档
    • location.replace(url) 加载新的文档,在历史列表中,新文档会替换当前文档

HTML5为history对象添加了两个新方法,history.pushState()和history.replaceState(),用来在浏览历史中添加和修改记录。

  • history.pushState(state, title, url)
    调用该方法后,浏览器只会替换地址栏地址,并且推入历史列表,但并不会刷新页面。
    • state 存储数据的对象,浏览器会自动序列化。最大存储空间为640k,超过这个值会抛出错误。当浏览器地址改变,且处于同一域的时候,会触发popstate事件,该事件的参数event.state即为本对象。
    • title 设置标题(所有浏览器均不支持)
    • url 地址栏替换成新的URL(在firefox下测试发现,url只支持相对地址,且必须在该域名下面;如果原地址已html结尾,那么只能加#)

在本地测试的时候,大家可能会遇到如下问题:SecurityError: The operation is insecure.
 
https://stackoverflow.com/questions/13348766/securityerror-the-operation-is-insecure-window-history-pushstate
 
Make sure you are following the Same Origin Policy. This means same domain, same subdomain, same protocol (http vs https) and same port.
 
How does pushState protect against potential content forgeries?
 
As @robertc aptly pointed out in his comment, some browsers actually implement slightly different security policies when the origin is file:///. Not to mention you can encounter problems when testing locally with file:/// when the page expects it is running from a different origin (and so your pushState assumes production origin scenarios, not localhost scenarios)
 
翻译如下:
请确认你遵守了同源策略。它意味着相同的域名,相同的子域名,相同的协议(http 和 https)和端口。
那么 pushState 如何防止潜在的内容伪造呢?
正如某人在他的评论中指出的,一些浏览器对于源是 file:/// 的实现稍有不同。这并不意味着当你在本地测试 来自不同源的 file:/// 的时候会遭遇困难(因此你可以在生产脚本上测试pushState,而非本地脚本)

最佳回答说了一堆废话。简而言之就是 pushState只适用于http 和 https协议,而对于 file 协议仅能采用锚点,其他都违反了安全策略

  • history.replaceState(state, title, url)
    replaceState和pushState的参数一摸一样,功能也差不多。
    唯一的区别是:pushState()是在history栈中添加一个新的条目,replaceState()是替换当前的记录值。

  • window.addEventListener(“popstate”, (event)=>{})
    history 实体改变时触发的事件。通过在popstate()事件中添加pushState()方法,可以让用户无法后退,且页面不会刷新

测试代码如下

<!DOCTYPE html>
<html>
<head>
<meta charset="utf8">
<script>
function objToStr(obj) {
	let desc = ""; 
	for(let i in obj){ 
		let property = obj[i]; 
		desc += i + " = " + property + "<br>"; 
	}
	return desc;
}
function init() {
	document.getElementById("number").value = -1;
	
	const locationElem = document.getElementById("location");
	locationElem.innerHTML = "location: <br>" + objToStr(location);
	
	const historyElem = document.getElementById("history");
	historyElem.innerHTML = "history: <br>" + objToStr(history);
	
	window.addEventListener("popstate", (event)=>{
		console.log(event);
		//通过在popstate添加pushState方法,可以让用户无法后退,且页面不会刷新
		//pushState();
	}, false);
}
function pushState() {
	var state = {
		title: "title",
		url: "#history"
	};
	window.history.pushState(state, state.title, state.url);
}
</script>
</head>
<body "init()">
<button onclick="javascript:history.forward()">forward</button>
<button onclick="javascript:history.back()">back</button>
<div>
<input type="number" id="number"/><button onclick="javascript:history.go(document.getElementById('number').value)">go</button>
</div>
<!--
history:
go = function go() { [native code] }
back = function back() { [native code] }
forward = function forward() { [native code] }
pushState = function pushState() { [native code] }
replaceState = function replaceState() { [native code] }
length = 3
scrollRestoration = auto
state = null
-->
<p id="history"></p>

<button onclick="javascript:location='http://www.baidu.com'">relocate</button>
<!--
location:
href = file:///D:/node/test20181018/public/history.html
origin = null
protocol = file:
host =
hostname =
port =
pathname = /D:/node/test20181018/public/history.html
search =
hash =
assign = function assign() { [native code] }
replace = function replace() { [native code] }
reload = function reload() { [native code] }
toString = function toString() { [native code] }
-->
<p id="location"></p>
<button onclick="pushState()">pushState</button>
</body>
</html>
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值