最近搞了下列表頁請求的功能,並作了一下調研整理,記此文備忘。javascript
列表頁請求的功能處處可見,好比在博客園。css
點擊相應的頁碼,頁面返回相應的內容,看上去彷佛大同小異,可是一些小的細節仍是能夠區分優劣。html
公司原來的代碼採用的是 full load 的方式,也就是每點擊一次,頁面徹底加載。並不僅有咱們網站這樣作,不少大廠也這樣搞,好比 新浪。html5
列表頁中的不少部份內容,其實都是同樣的,這樣作就每次須要從新加載這部分的內容,沒有必要,並且 css、js 都須要從新加載(雖然可能有緩存)。之前我逛學校的論壇,是用 PHP 的 Discuz! 搭建的,每一個主題後的回覆頁之間的跳轉都是 full load 的方式,體驗不好。java
因此我的以爲,不論是性能仍是用戶體驗上,full load 的方式在如今的 web 開發中,都是不可取的。jquery
接着 ajax 出現了。ajax 就很少作介紹了,局部刷新,體驗很是好。可是單純的 ajax 雖然性能上比 full load 提升了很多,用戶體驗還不是很好,主要有如下兩點。git
究其根本,是由於傳統的 ajax 操做不改變 url。github
爲了解決以上問題,聰明的開發者們用 # 來改善體驗。web
以博客園爲例,咱們請求第二頁的時候,實際的 url 是 http://www.cnblogs.com/#p2,當點擊頁碼發送請求時,同時改變頁面的 url,由於改變的是 lcation.hash,因此頁面並不會重載。咱們將其保存爲書籤,當咱們打開 http://www.cnblogs.com/#p2 時,咱們能夠提取 hash 值,據此發起相應的 ajax 請求。ajax
接着咱們來看第二個需求,如何支持瀏覽器的後退前進操做。有些童鞋可能會問,已經有了上一頁、下一頁的功能,支持瀏覽器的後退前進操做,有必要嗎?灰常有必要,好比咱們先點了第二頁,而後點了第四頁,我須要回到第二頁,又忘了剛纔點的是第幾頁,會條件反射地去點瀏覽器的回退。其實博客園 http://www.cnblogs.com/ 沒有作這個功能,如何支持?咱們能夠監聽 hashchange
事件,當 url 的 hash 值發生變化時,從新發送請求。可是 hashchange
事件並不支持某些 IE (http://caniuse.com/#search=hashchange),對於不支持的瀏覽器,咱們只能設置一個定時器,不斷得去查看頁面的 hash 是否改變,會形成不小的性能問題(或者直接放棄這部分瀏覽器,或者降級處理)。
簡單地寫了個 demo,猛戳 https://github.com/hanzichi/practice/tree/master/2016/pjax/hash 查看(沒有兼容不支持 hashchange 事件的瀏覽器)。
這裏再插點題外話,講點小歷史。之前的搜索引擎爬蟲,是不會抓取 ajax 請求的內容的(畢竟木有這麼智能),只會去抓網頁的源代碼,這就蛋疼了,咱們既但願用 ajax 改善體驗,又但願內容能夠被搜索引擎爬蟲抓取,兩者不可得兼?Google 搜索制定了一套規則。
_escaped_fragment_ 這個參數是 Google 指定的命名,若是開發者但願把網站內容提交給 Google,就必須經過這個參數生成靜態頁面。
也就是說,每一個 ajax 請求的內容,都須要提供一個相同內容的靜態頁面,供爬蟲爬取。
隨着 web 的發展,這一切已經成爲了歷史,如今的爬蟲已經能夠執行 JavaScript,爬取 ajax 請求的內容了!這部分的內容,就不展開了,有興趣的能夠參考下如下連接。
ajax + #,彷佛能夠知足通常的需求了,可是若是不止限於列表請求呢?改變 hash 值搞的 URL 看起來不像一個正常的 URL,並且 hash 原本的用處並不是如此,這樣搞有點黑科技的感受。HTML5 的出現,能讓 ajax 變的更加優雅。
爲了解決傳統的 ajax 帶來的問題,HTML5 裏增強了 history API,加入了 pushState、replaceState 接口和 popstate 事件。
舉個簡單的例子,咱們看 GitHub,首先定位到頁面 https://github.com/hanzichi/underscore-analysis,而後點擊該 repo 下第一個行第一個文件夾 『underscore-1.8.3.js』,URL 變爲 https://github.com/hanzichi/underscore-analysis/tree/master/underscore-1.8.3.js,頁面局部刷新,看了下 Network 面板,是一個 ajax 請求,且該操做支持保存書籤、回退前進等功能。這一切的實現都基於 history 新增的 API。
history 原有的 API 大都灰常簡單,好比 history.length
(該 tab 訪問過的網頁數量,新建 tab 時的空標籤該屬性值爲 1),history.back()
,history.forward()
,history.go(-1)
等等,很少加介紹,簡單介紹下新增的 history.pushState
,history.replaceState
以及 popstate
事件。
history.pushState 方法接受三個參數,依次爲:
假設如今的網頁是 http://localhost/1.htm,咱們使用 pushState 方法在瀏覽記錄(history 對象)中添加一個新紀錄。
var stateObj = {page: 2 }; history.pushState(stateObj, "page 2", "2.htm");
瀏覽器地址欄馬上顯示 <localhost/2.htm>,可是並不會跳到 2.htm 的頁面(pushState 不會觸發頁面刷新),甚至這個頁面不存在也不會報錯,它只是成爲了瀏覽器中的最新記錄,能夠查看 history.length,會發現該屬性值增長了 1。若是這時點擊倒退,url 將顯示 1.htm,內容不變。
若是 pushState 的 url 參數,設置了一個當前網頁的 # 值(hash),並不會觸發 hashchange 事件。若是設置了一個非同域的網址,則會報錯。
history.replaceState 方法的參數和 pushState 同樣,區別是它修改瀏覽器歷史中當前頁面的值,即便用 replaceState,history.length 並不會增長,只是替換了當前頁面在 history 中的記錄。
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
前面 ajax + # 的例子中,咱們用 hashchange 去判斷瀏覽器的前進後退操做,那麼,是否有原生的監聽瀏覽器前進回退操做的事件呢?有的,popstate 事件。每當同一個文檔的瀏覽歷史(即 history 對象)出現變化時,就會觸發 popstate 事件,只有當用戶手動點擊瀏覽器後退前進按鈕,或者調用 back、forward、go 方法時纔會觸發。
window.onpopstate = function(e) { console.log(e.state); // 等價於 // console.log(history.state); }
因而咱們要實現一個列表頁請求的功能,就呼之欲出了。點擊頁碼,用 pushState 塞入一條新的記錄,同時改變 url,而後發送 ajax 請求,局部更新。點擊瀏覽器後退前進按鈕,觸發 popstate 事件,發送請求,局部更新,請求的字段,能夠根據 url 去判斷,也能夠儲存在 state 中。寫了個簡單的 demo,猛戳 https://github.com/hanzichi/practice/tree/master/2016/pjax/pushState 查看(根據 url 判斷了)。
ajax + pushState 以及 ajax + hash 的做用相似,可是推薦使用前者,有如下幾個優點:
上面只是個簡單的例子,若是要是實際生產環境中使用,大可用封裝過的插件。
pjax = pushState + ajax,GitHub 使用的就是封裝過的 pjax 插件。
使用方式能夠參照相應的 README,很少作介紹了。