一.如何切换页面
我们能在同一个网址上访问不同的页面正是用到了路由功能,这个路由地址能通过window.location.hash
得到,以Vue官网为例,打开一个页面查询此属性可以得到
window.location.hash
"#router-start-replaced"
这个值是自带一个#
号的
那么我们如何监听到route的变化呢,有一个api,hashchange
window.addEventListener("hashchange", () => {
console.log("hash 变了");
});
那么我们只需要监听hashchange事件,如何将相应的页面展示出来就好了
二. 路由表
我们得到了window.location.hash
的值以后,如何将它跳转到相应的页面呢,这就需要一个hash与页面的映射表,这就是路由表
const app = document.querySelector("#app");
const div1 = document.createElement("div");
div1.innerHTML = "1";
const div2 = document.createElement("div");
div2.innerHTML = "2";
const div3 = document.createElement("div");
div3.innerHTML = "3";
const div4 = document.createElement("div");
div4.innerHTML = "4";
const routeTable = {
"1": div1,
"2": div2,
"3": div3,
"4": div4
};
这里我们创建几个DOM节点,然后在路由表中建立映射
三. router
接下来我们就可以来写一个router了
function route(container) {
let number = window.location.hash.substr(1);
number = number || 1;
//默认路由,当hash值为空时将它设为1
//根据hash得到路由表中相应页面
let div = routeTable[number.toString()];
//如果路由表中不存在则跳转到404页面
if (!div) {
div = document.querySelector("#div404");
}
div.style.display = "block";
//先将容器中清空
container.innerHTML = "";
//展示内容
container.appendChild(div);
}
router(app)
最后配合上面的事件监听,当hash值变化就路由一次
window.addEventListener("hashchange", () => {
route(app);
});
自此一个简单的单页面路由就完成了
四. 路由嵌套
如果一级路由下还有子代元素那么可再创建一个字路由表
const routeTable = {
"1/1": div11,
"1/2": div12,
"1/3": div13,
"1/4": div14,
};
然后在router函数中加入一定的条件判断,比如用正则匹配然后分发到相应的页面
五.history
上述的hash模式,我们的请求路径自带一个#
,history模式则是自带一个/
我们可以通过window.location.pathname
来得到这个值,路由表也相应更改
const routeTable = {
"/1": div1,
"/2": div2,
"/3": div3,
"/4": div4
};
hash对SEO不友好,因为hash部分会被浏览器直接忽略,因此服务器得不到这部分数据,而history模式IE8以下浏览器不支持
使用history也有个问题那就是每次请求都会重新刷新页面因此会变得很慢,所以我们要对他进行改进
window.history.pushState(stateObj, "page 2", "bar.html");
//1. 状态对象 2. 标题 3. url
这个api可以可以改变referrer,它在用户发送 XMLHttpRequest 请求时在HTTP头部使用,改变state后创建的 XMLHttpRequest 对象的referrer都会被改变。因为referrer是标识创建 XMLHttpRequest 对象时 this 所代表的window对象中document的URL。
注意 pushState() 绝对不会触发 hashchange 事件
然后我们如何监听这个变化呢,答案是不同监听,直接得到路径然后重新router
function route(container) {
let number = window.location.pathname;
console.log("number: " + number);
if (number === "/") {
number = "/1";
}
// 获取界面
let div = routeTable[number.toString()];
if (!div) {
div = document.querySelector("#div404");
}
div.style.display = "block";
// 展示界面
container.innerHTML = "";
container.appendChild(div);
}
const allA = document.querySelectorAll("a.link");
for (let a of allA) {
a.addEventListener("click", e => {
//组织a标签的默认跳转动作
e.preventDefault();
const href = a.getAttribute("href");
window.history.pushState(null, `page ${href}`, href);
// 直接调用一个函数重新router
onStateChange(href);
});
}
route(app);
function onStateChange() {
console.log("state 变了");
route(app);
}
六.memory
我们将上述的history模式路劲存放地址放到localStorage中会怎样呢
function route(container) {
//从localStorage中取路径,默认为1
let number = window.localStorage.getItem("xxx");
if (!number) {
number = "/1";
}
// 获取界面
let div = routeTable[number.toString()];
if (!div) {
div = document.querySelector("#div404");
}
div.style.display = "block";
// 展示界面
container.innerHTML = "";
container.appendChild(div);
}
const allA = document.querySelectorAll("a.link");
for (let a of allA) {
a.addEventListener("click", e => {
e.preventDefault();
const href = a.getAttribute("href");
//点击之后改变本地中的路径
window.localStorage.setItem("xxx", href);
onStateChange(href);
});
}
这样的好处是,每次刷新页面后,总是会渲染出上一次退出的页面,因为这部分是存在本地上的,就像有了记忆
history模式和hash模式的区别
hash
1. #
# 代表网页中的一个位置。其右边的字符,就是该位置的标识符。比如,
http://www.example.com/index.html/#/print
就代表网页 index.html 的 print 位置。浏览器读取这个 url 后,
2. 设置#
1. 锚点
<a name="print"></a>
2. id
<div id="print">
3. 特点
hash出现在URL中,但是并不会被带入HTTP请求,因此后端接收到的还是原始路径http://www.example.com/index.htm
4. 获取
window.location.hash
可以得到当前的hash值
读取时,可以用来判断网页状态是否改变
写入时,则会在不重载网页的前提下,创造一条访问历史记录。
5. 监听
window.addEventListener('hashchange', function(){}
可以通过hashchange事件来监听hash值变化
history
1. 兼容性
hash 能兼容到IE8, history 只能兼容到 IE10
2. 参数传递
hash模式原本的作用是来跳转元素所在的锚点的,如果将其作为路由,则锚点的作用无法兼容,切hash模式的参数完全依靠#
所带的参数
history则不仅可以在url中填写参数,还有一个window.history对象可以访问,上面可以承载更多的参数
3. 跳转
//参数为-1,作用等价
window.history.back();
window.history.go(-1);
window.history.forward();
window.history.go(1);
//等等
window.history.go(2)
4. 修改历史
HTML5引入了 history.pushState()
和 history.replaceState()
方法,它们分别可以添加和修改历史记录条目。这些方法通常与window.onpopstate
配合使用。
window.history.pushState(state, title, url)
// state:需要保存的数据,这个数据在触发popstate事件时,可以在event.state里获取
// title:标题,基本没用,一般传 null
// url:设定新的历史记录的 url。新的 url 与当前 url 的 origin 必须是一樣的,否则会抛出错误。url可以是绝对路径,也可以是相对路径。
//如 当前url是 https://www.baidu.com/a/,执行history.pushState(null, null, './qq/'),则变成 https://www.baidu.com/a/qq/,
//执行history.pushState(null, null, '/qq/'),则变成 https://www.baidu.com/qq/
window.history.replaceState(state, title, url)
// 与 pushState 基本相同,但她是修改当前历史记录,而 pushState 是创建新的历史记录
window.addEventListener("popstate", function() {
// 监听浏览器前进后退事件,pushState 与 replaceState 方法不会触发
通过pushstate把页面的状态保存在state对象中,当页面的url再变回这个url时,可以通过event.state取到这个state对象,从而可以对页面状态进行还原,这里的页面状态就是页面字体颜色,其实滚动条的位置,阅读进度,组件的开关的这些页面状态都可以存储到state的里面。
5. 后端支持
当我们通过history模式更改url时,由于url改变,所以浏览器会重新向服务器发送页面请求,因此,后端应该将其他的url请求都返回同一个html页面,这样浏览器就会发现,服务器返回内容与当前页面相同,则不会重新渲染页面
同样的,当我们直接访问http://www.example.com/index.htm
时,如果后端未做相应处理,路由匹配不到任何资源,则跳转到404页面,因此history需要后端支持才能实现