淺析列表頁請求優化(history API)

最近搞了下列表頁請求的功能,並作了一下調研整理,記此文備忘。javascript

列表頁請求的功能處處可見,好比在博客園。css

點擊相應的頁碼,頁面返回相應的內容,看上去彷佛大同小異,可是一些小的細節仍是能夠區分優劣。html

full load

公司原來的代碼採用的是 full load 的方式,也就是每點擊一次,頁面徹底加載。並不僅有咱們網站這樣作,不少大廠也這樣搞,好比 新浪html5

列表頁中的不少部份內容,其實都是同樣的,這樣作就每次須要從新加載這部分的內容,沒有必要,並且 css、js 都須要從新加載(雖然可能有緩存)。之前我逛學校的論壇,是用 PHP 的 Discuz! 搭建的,每一個主題後的回覆頁之間的跳轉都是 full load 的方式,體驗不好。java

因此我的以爲,不論是性能仍是用戶體驗上,full load 的方式在如今的 web 開發中,都是不可取的。jquery

ajax

接着 ajax 出現了。ajax 就很少作介紹了,局部刷新,體驗很是好。可是單純的 ajax 雖然性能上比 full load 提升了很多,用戶體驗還不是很好,主要有如下兩點。git

  • 保存不了書籤
  • 不支持瀏覽器的後退前進操做

究其根本,是由於傳統的 ajax 操做不改變 url。github

ajax + #

爲了解決以上問題,聰明的開發者們用 # 來改善體驗。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 搜索制定了一套規則。

  1. 網站提交 sitemap 給 Google;
  2. Google 發現 URL 裏有 #! 符號,例如example.com/#!/detail/1,因而 Google 開始抓取 example.com/?_escaped_fragment_=/detail/1;

_escaped_fragment_ 這個參數是 Google 指定的命名,若是開發者但願把網站內容提交給 Google,就必須經過這個參數生成靜態頁面。

也就是說,每一個 ajax 請求的內容,都須要提供一個相同內容的靜態頁面,供爬蟲爬取。

隨着 web 的發展,這一切已經成爲了歷史,如今的爬蟲已經能夠執行 JavaScript,爬取 ajax 請求的內容了!這部分的內容,就不展開了,有興趣的能夠參考下如下連接。

ajax + pushState

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.pushStatehistory.replaceState 以及 popstate 事件。

history.pushState 方法接受三個參數,依次爲:

  • state:一個與指定網址相關的狀態對象,popstate 事件觸發時,該對象會傳入回調函數。若是不須要這個對象,此處能夠填 null。history.state 屬性能保存當前頁面的 state 對象。
  • title:新頁面的標題,可是全部瀏覽器目前都忽略這個值,所以這裏能夠填 null。
  • url:新的網址,必須與當前頁面處在同一個域。瀏覽器的地址欄將顯示這個網址。

假設如今的網頁是 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 判斷了)。

pushState vs #

ajax + pushState 以及 ajax + hash 的做用相似,可是推薦使用前者,有如下幾個優點:

  • pushState 能改變的 URL 的範圍大,在一個域名下的均可以,而 hash 的方法,由於 URL 只能改變 location.hash 的值,因此 URL 實際上是隻能在一個文檔(document)下改變。好比 GitHub 中的路由,用 hash 去作,就會很麻煩,並且也很醜
  • 插入一條新的 history 記錄,用 pushState,不必定要改變 URL,而 hash 必須改變當前的 URL(精確地說是當前文檔的 hash 值)
  • 毫無疑問,咱們須要把一些數據存儲起來,在頁面上提取,而後發起相應的 ajax。用 pushState 的方法,咱們能夠把數據存在 history.state 中,也能夠根據 URL 去判斷;而 hash 法只能改變 URL,根據 URL 判斷(準確說是根據 hash 值判斷)
  • 目前瀏覽器還不支持 pushState 的 title 參數,一旦支持,就能夠被利用;而 hash 法是沒法改變 title 的。

pjax

上面只是個簡單的例子,若是要是實際生產環境中使用,大可用封裝過的插件。

pjax = pushState + ajax,GitHub 使用的就是封裝過的 pjax 插件。

使用方式能夠參照相應的 README,很少作介紹了。

read more

相關文章
相關標籤/搜索