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';

相關文章
相關標籤/搜索