js pjax 和window.history.pushState,replaceState

2019年11月09日 阅读数:55
这篇文章主要向大家介绍js pjax 和window.history.pushState,replaceState,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

原文:http://blog.linjunhalida.com/blog/pjax/

github:https://github.com/defunkt/jquery-pjaxcss

 

什么是pjax?

如今不少网站(facebooktwitter)都支持这样的一种浏览方式, 当你点击一个站内的连接的时候, 不是作页面跳转, 而是只是站内页面刷新。 这样的用户体验, 比起整个页面都闪一下来讲, 好不少。html

其中有一个很重要的组成部分, 这些网站的ajax刷新是支持浏览器历史的, 刷新页面的同时, 浏览器地址栏位上面的地址也是会更改, 用浏览器的回退功能也可以回退到上一个页面。jquery

那么若是咱们想要实现这样的功能, 咱们如何作呢?git

我发现pjax提供了一个脚本支持这样的功能。github

pjax项目地址在 https://github.com/defunkt/jquery-pjax 。 实际的效果见: http://pjax.heroku.com/ 没有勾选pjax的时候, 点击连接是跳转的。 勾选了以后, 连接都是变成了ajax刷新。web

为何要用pjax?

pjax有好几个好处:ajax

  • 用户体验提高。django

    页面跳转的时候人眼须要对整个页面做从新识别, 刷新部分页面的时候, 只须要从新识别其中一块区域。自从我在本身的网站 GuruDigger 上面采用了pjax技术后, 不禁以为访问其余只有页面跳转的网站难受了许多。 同时, 因为刷新部分页面的时候提供了一个loading的提示, 以及在刷新的时候旧页面仍是显示在浏览器中, 用户可以容忍更长的页面加载时间。api

  • 极大地减小带宽消耗和服务器消耗。跨域

    因为只是刷新部分页面, 大部分的请求(css/js)都不会从新获取, 网站带有用户登陆信息的外框部分都不须要从新生成了。 虽然我没有具体统计这部分的消耗, 我估计至少有40%以上的请求, 30%以上的服务器消耗被节省了。

坏处我以为也有:

  • IE6等历史浏览器的支持

    虽然我没有实际测试, 可是因为pjax利用到了新的标准, 旧的浏览器兼容会有问题。 不过pjax自己支持fallback, 当发现浏览器不支持该功能的时候, 会回到原始的页面跳转上面去。

  • 复杂的服务器端支持

    服务器端须要根据过来的请求, 判断是做全页面渲染仍是部分页面渲染, 相对来讲系统复杂度增大了。 不过对于设计良好的服务器代码, 支持这样的功能不会有太大的问题。

综合起来, 因为用户体验和资源利用率的提高, 坏处是能够彻底获得弥补的。 我强烈推荐你们使用。

如何使用pjax?

直接看 官方文档 就能够了。

我以为作技术的人要养成看一手的技术资料的习惯。

有一个rails针对pjax的 gem插件 能够直接使用。 也有 django的支持 。

pjax的原理

为了可以处理问题, 咱们须要可以理解pjax的运做方式。 pjax的代码只有一个文件: https://github.com/defunkt/jquery-pjax/blob/master/jquery.pjax.js

若是有能力, 能够本身去看一遍。 我这里解释一下原理。

首先, 咱们在html里面指定, 须要作pjax的连接内容是哪些, 以及点击以后须要更新的部分(放在data-pjax属性里面):

1
$('a[data-pjax]').pjax() 

当加载了pjax脚本以后, 它会拦截这些连接的事件, 而后包装成一个ajax请求, 发送给服务器。

1
2
3
4
5
6 7 8 9 10 11 12 13 14 15 
$.fn.pjax = function( container, options ) {  return this.live('click.pjax', function(event){  handleClick(event, container, options)  }) }  function handleClick(event, container, options) {  $.pjax($.extend({}, defaults, options))  ...  event.preventDefault() } var pjax = $.pjax = function( options ) {  ...  pjax.xhr = $.ajax(options) } 

这个请求带有X-PJAX的HEADER标识, 服务器在收到这样的请求的时候, 就知道只须要渲染部分页面返回就能够了。

1
2
xhr.setRequestHeader('X-PJAX', 'true') xhr.setRequestHeader('X-PJAX-Container', context.selector) 

pjax接受到返回的请求以后, 更新data-pjax指定的区域, 同时也会更新浏览器的地址。

1
2
3
4
5
6 
options.success = function(data, status, xhr) {  var container = extractContainer(data, xhr, options)  ...  if (container.title) document.title = container.title  context.html(container.contents) } 

为了可以支持浏览器的后退, 利用到了history的api, 记录下来对应的信息,

1
2
3
4
5
6 7 8 9 10 11 
pjax.state = {  id: options.id || uniqueId(),  url: container.url,  container: context.selector,  fragment: options.fragment,  timeout: options.timeout }  if (options.push || options.replace) {  window.history.replaceState(pjax.state, container.title, container.url) } 

当浏览器后退的时候, 拦截事件, 根据记录的历史信息, 产生一个新的ajax请求。

1
2
3
4
5
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 
$(window).bind('popstate', function(event){  var state = event.state  if (state && state.container) {  var container = $(state.container)  if (container.length) {  ...  var options = {  id: state.id,  url: state.url,  container: container,  push: false,  fragment: state.fragment,  timeout: state.timeout,  scrollTo: false  }   if (contents) {  // pjax event is deprecated  $(document).trigger('pjax', [null, options])  container.trigger('pjax:start', [null, options])  // end.pjax event is deprecated  container.trigger('start.pjax', [null, options])   container.html(contents)  pjax.state = state   container.trigger('pjax:end', [null, options])  // end.pjax event is deprecated  container.trigger('end.pjax', [null, options])  } else {  $.pjax(options)  }  ...  }  } } 

为了支持fallback, 一个是在加载的时候判断浏览器是否支持history push state API:

1
2
3
4
5
// Is pjax supported by this browser?
$.support.pjax =  window.history && window.history.pushState && window.history.replaceState  // pushState isn't reliable on iOS until 5.  && !navigator.userAgent.match(/((iPod|iPhone|iPad).+\bOS\s+[1-4]|WebApps\/.+CFNetwork)/) 

另外一个是当发现请求一段时间没有回复的时候(能够设置参数timeout), 直接作页面跳转。

1
2
3
4
5
6 7 8 9 
options.beforeSend = function(xhr, settings) {  if (settings.timeout > 0) {  timeoutTimer = setTimeout(function() {  if (fire('pjax:timeout', [xhr, options]))  xhr.abort('timeout')  }, settings.timeout)   // Clear timeout setting so jquerys internal timeout isn't invoked  settings.timeout = 0 

结论

既然都看到这里了, 你为何不去实际使用一下pjax呢? 有那么多好处, 我以为几乎全部网站都应该采用pjax。 赶忙用起来吧!

 

 

 

原文:https://www.studyscript.com/Post/index/id/3018.html?page=3

正文~

 

概述

浏览器窗口有一个history对象,用来保存浏览历史。

 

若是当前窗口前后访问了三个网址,那么history对象就包括三项,history.length属性等于3。

 

history.length // 3

history对象提供了一系列方法,容许在浏览历史之间移动。

 

back():移动到上一个访问页面,等同于浏览器的后退键。

forward():移动到下一个访问页面,等同于浏览器的前进键。

go():接受一个整数做为参数,移动到该整数指定的页面,好比go(1)至关于forward(),go(-1)至关于back()。

history.back();

history.forward();

history.go(-2);

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

 

history.go(0)至关于刷新当前页面。

 

history.go(0);

常见的“返回上一页”连接,代码以下。

 

document.getElementById('backLink').onclick = function () {

  window.history.back();

}

注意,返回上一页时,页面一般是从浏览器缓存之中加载,而不是从新要求服务器发送新的网页。

 

history.pushState()

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

 

if (!!(window.history && history.pushState)){

  // 支持History API

} else {

  // 不支持

}

上面代码能够用来检查,当前浏览器是否支持History API。若是不支持的话,能够考虑使用Polyfill库History.js。

 

history.pushState方法接受三个参数,依次为:

 

state:一个与指定网址相关的状态对象,popstate事件触发时,该对象会传入回调函数。若是不须要这个对象,此处能够填null。

title:新页面的标题,可是全部浏览器目前都忽略这个值,所以这里能够填null。

url:新的网址,必须与当前页面处在同一个域。浏览器的地址栏将显示这个网址。

假定当前网址是example.com/1.html,咱们使用pushState方法在浏览记录(history对象)中添加一个新记录。

 

var stateObj = { foo: 'bar' };

history.pushState(stateObj, 'page 2', '2.html');

添加上面这个新记录后,浏览器地址栏马上显示example.com/2.html,但并不会跳转到2.html,甚至也不会检查2.html是否存在,它只是成为浏览历史中的最新记录。假定这时你访问了google.com,而后点击了倒退按钮,页面的url将显示2.html,可是内容仍是原来的1.html。你再点击一次倒退按钮,url将显示1.html,内容不变。

 

总之,pushState方法不会触发页面刷新,只是致使history对象发生变化,地址栏会有反应。

 

若是pushState的url参数,设置了一个新的锚点值(即hash),并不会触发hashchange事件。若是设置了一个跨域网址,则会报错。

 

// 报错

history.pushState(null, null, 'https://twitter.com/hello');

上面代码中,pushState想要插入一个跨域的网址,致使报错。这样设计的目的是,防止恶意代码让用户觉得他们是在另外一个网站上。

 

history.replaceState()

history.replaceState方法的参数与pushState方法如出一辙,区别是它修改浏览历史中当前纪录。

 

假定当前网页是example.com/example.html。

 

history.pushState({page: 1}, 'title 1', '?page=1');

history.pushState({page: 2}, 'title 2', '?page=2');

history.replaceState({page: 3}, 'title 3', '?page=3');

 

history.back()

// url显示为http://example.com/example.html?page=1

 

history.back()

// url显示为http://example.com/example.html

 

history.go(2)

// url显示为http://example.com/example.html?page=3

history.state属性

history.state属性返回当前页面的state对象。

 

history.pushState({page: 1}, 'title 1', '?page=1');

 

history.state

// { page: 1 }

popstate事件

每当同一个文档的浏览历史(即history对象)出现变化时,就会触发popstate事件。

 

须要注意的是,仅仅调用pushState方法或replaceState方法 ,并不会触发该事件,只有用户点击浏览器倒退按钮和前进按钮,或者使用JavaScript调用back、forward、go方法时才会触发。另外,该事件只针对同一个文档,若是浏览历史的切换,致使加载不一样的文档,该事件也不会触发。

 

使用的时候,能够为popstate事件指定回调函数。这个回调函数的参数是一个event事件对象,它的state属性指向pushState和replaceState方法为当前URL所提供的状态对象(即这两个方法的第一个参数)。

 

window.onpopstate = function (event) {

  console.log('location: ' + document.location);

  console.log('state: ' + JSON.stringify(event.state));

};

 

// 或者

 

window.addEventListener('popstate', function(event) {

  console.log('location: ' + document.location);

  console.log('state: ' + JSON.stringify(event.state));

});

上面代码中的event.state,就是经过pushState和replaceState方法,为当前URL绑定的state对象。

 

这个state对象也能够直接经过history对象读取。

 

var currentState = history.state;

注意,页面第一次加载的时候,在load事件发生后,Chrome和Safari浏览器(Webkit核心)会触发popstate事件,而Firefox和IE浏览器不会。

 

URLSearchParams API

URLSearchParams API用于处理URL之中的查询字符串,即问号以后的部分。没有部署这个API的浏览器,能够用url-search-params这个垫片库。

 

var paramsString = 'q=URLUtils.searchParams&topic=api';

var searchParams = new URLSearchParams(paramsString);

URLSearchParams有如下方法,用来操做某个参数。

 

has():返回一个布尔值,表示是否具备某个参数

get():返回指定参数的第一个值

getAll():返回一个数组,成员是指定参数的全部值

set():设置指定参数

delete():删除指定参数

append():在查询字符串之中,追加一个键值对

toString():返回整个查询字符串

var paramsString = 'q=URLUtils.searchParams&topic=api';

var searchParams = new URLSearchParams(paramsString);

 

searchParams.has('topic') // true

searchParams.get('topic') // "api"

searchParams.getAll('topic') // ["api"]

 

searchParams.get('foo') // null,注意Firefox返回空字符串

searchParams.set('foo', 2);

searchParams.get('foo') // 2

 

searchParams.append('topic', 'webdev');

searchParams.toString() // "q=URLUtils.searchParams&topic=api&foo=2&topic=webdev"

 

searchParams.append('foo', 3);

searchParams.getAll('foo') // [2, 3]

 

searchParams.delete('topic');

searchParams.toString() // "q=URLUtils.searchParams&foo=2&foo=3"

URLSearchParams还有三个方法,用来遍历全部参数。

 

keys():遍历全部参数名

values():遍历全部参数值

entries():遍历全部参数的键值对

上面三个方法返回的都是Iterator对象。

 

var searchParams = new URLSearchParams('key1=value1&key2=value2');

 

for (var key of searchParams.keys()) {

  console.log(key);

}

// key1

// key2

 

for (var value of searchParams.values()) {

  console.log(value);

}

// value1

// value2

 

for (var pair of searchParams.entries()) {

  console.log(pair[0]+ ', '+ pair[1]);

}

// key1, value1

// key2, value2

在Chrome浏览器之中,URLSearchParams实例自己就是Iterator对象,与entries方法返回值相同。因此,能够写成下面的样子。

 

for (var p of searchParams) {

  console.log(p);

}

下面是一个替换当前URL的例子。

 

// URL: https://example.com?version=1.0

var params = new URLSearchParams(location.search.slice(1));

params.set('version', 2.0);

 

window.history.replaceState({}, '', `${location.pathname}?${params}`);

// URL: https://example.com?version=2.0

URLSearchParams实例能够看成POST数据发送,全部数据都会URL编码。

 

let params = new URLSearchParams();

params.append('api_key', '1234567890');

 

fetch('https://example.com/api', {

  method: 'POST',

  body: params

}).then(...)

DOM的a元素节点的searchParams属性,就是一个URLSearchParams实例。

 

var a = document.createElement('a');

a.href = 'https://example.com?filter=api';

a.searchParams.get('filter') // "api"

URLSearchParams还能够与URL接口结合使用。

 

var url = new URL(location);

var foo = url.searchParams.get('foo') || 'somedefault';