在浏览器中,都会有一个history对象,用来保存历史信息。
一些网页,为了优化用户体验,使用了无刷新页面切换、无刷新返回等设计,这个设计主要是通过history.replaceState、history.pushState等方法和onhashchange事件等来实现的。
history.pushState
假设你当前的页面为https://www.xxx.com/,则history.pushState(state, null, url) 的作用是将当前地址栏修改为https://www.xxx.com/url,并将这些信息压入到一个类似栈的容器中,当用户有返回事件时,会将信息取出,url变换成之前的url,即实现了返回页面。
实质上你可以理解为,你的浏览器就像点击了一个a标签一样,地址栏由https://www.xxx.com/变成了https://www.xxx.com/url,但是实际上浏览器没有真正向服务器请求https://www.xxx.com/url的数据。这时,在
第一个参数是一个与指定网址相关的状态对象,popstate事件触发时,该对象会传入回调函数。如果不需要这个对象,此处可以填null。
第二个参数保持null即可,浏览器大多会忽略它。
history.replaceState
history.replaceState与history.pushState,两者都可以修改地址栏的数据,两者最大的区别在于,pushState会将信息压入一个类似栈的容器内,用户返回时可以返回上一层url,而replaceState则是类似替换掉栈顶。两者参数相同。
script标签做模板
script标签一般用于执行JavaScript,而把type设置为text/html后,即<script type="text/html" id="tpl_id">,就不会被当做js来执行了,标签内可以写html模板,浏览器也不会渲染。可以在页面需要更新的地方设置个<div id="container">,当需要调用这个模板时,以jQuery为例,使用$('#container').html($('#tpl_id').html());即可将container的内容设置成这个script标签内的内容
script标签做模板后,模板使用时执行js
这里给出一种情况,模板绑定一个js,在模板生效时,执行这个js。由于模板用的就是script标签,内部不可能再嵌套一个script标签,采用的思路是在模板script标签上添加一个属性:load-script,表示使用模板的时候要执行的脚本,在模板管理类上用eval执行一下即可。
if($(ele).attr('load-script') != undefined){
//执行模板附带的脚本
eval($("#" + hash).attr('load-script'));
}
location.hash
在url中,#和#后面的东西就是hash。例如https://www.xxx.com/abc#123,这里location.hash返回的就是#123。当url的hash变化时,会触发onhashchange事件。
无刷新多级列表&无刷新返回
假设通过接口/getList?fid=xxx可以获取一个多级列表的数组,fid表示父级id,如果想做到无刷新多级列表&无刷新返回,其中一个思路是将多层级id数据存放在url的hash中,例如https://www.xxx.com/lists#顶级目录id/二级目录id/三级.....这可以利用history.pushState和window.onhashchange来实现。
对于模板,思路是将模板id放在urlhash中,可以通过a标签直接定向到#tpl_id,也可以用函数的方式来调用,不会产生刷新。
下面给出一种实现方式
window.pageManager = {
container: null, //装页面的容器
defaultPage: null, //默认页面
tplPage: [], //script模板集合
setDeafultPage: function(page){ //设置默认页面
this.defaultPage = page;
return this;
},
setContainer: function(div){ //设置页面容器
this.container = div;
return this;
},
init: function(){ //初始化
//注册hashchange事件
$(window).on('hashchange', function (){
hash = location.hash.replace("#", ""); //获取hash
idx = hash.lastIndexOf('/') //判断是否在子目录
if(idx == -1){
//当前在顶级目录,直接变化页面
window.pageManager.changePage(hash);
}else{
fid = hash.substr(idx + 1);
//当前在列表子目录下,fid是父级id,通过setList变化列表
window.pageManager.setList(fid);
}
});
//获取全部模板
tpls = $('script[type="text/html"]');
for(var i = 0; i < tpls.length; i++){
this.tplPage.push(tpls[i].id);
}
//初始化默认页
this.changePage(this.defaultPage);
return this;
},
setPage: function(hash){
//window.pageManager.setPage,前进到某个页面,调用的是pushState
history.pushState(hash, "", "#" + hash);
$("#" + this.container).html($("#" + hash).html());
if($("#" + hash).attr('load-script') != undefined){
//执行模板附带的脚本
eval($("#" + hash).attr('load-script'));
}
},
changePage: function(hash){
//window.pageManager.changePage,更改当前页面,调用的是replaceState
history.replaceState(hash, "", "#" + hash);
$("#" + this.container).html($("#" + hash).html());
if($("#" + hash).attr('load-script') != undefined){
eval($("#" + hash).attr('load-script'));
}
},
setList: function(fid){ //设置列表项方法
$.ajax({
url : '/getList.php?fid=' + fid,
type : 'get',
dataType : 'json',
success: function(data){
if(data.code == 200){
data = data.data;
html = '';
for(var i = 0; i < data.length; i++){
//构建列表
}
}
}
});
hash = location.hash.replace("#", "");
new_hash = hash + '/' + fid
history.pushState(new_hash, "", "#" + new_hash);
}
};
//设置容器和默认页
window.pageManager
.setContainer('tpl_container')
.setDeafultPage('tpl_default')
.init();
和pjax是同一个原理吗?