PJAX,站點加速之翼

file

pjax 是一款可愛的 jQuery 小插件,將 ajax 和瀏覽器的 pushState API 封裝到一塊兒,解決了單純使用 ajax 進行無刷新加載時對搜索引擎的不友好,而且節省了 HTTP 開支、提升了瀏覽速度,明顯地優化了用戶體驗。javascript

知識要點

  • ajax 自很少說,在這裏負責攜帶 pjax 標識請求後端,將生成好的 html 碎片(注意不是前端取回JSON來進行渲染)取回,而後 jQuery 將它替換到 DOM 當中。php

  • pushState 是 html5 提供的API,是對瀏覽器歷史對象 history 的加強。瞭解 Javascript 的都知道 BOM (瀏覽器對象模型),而 window 則是 BOM 的具體實現,history 則是window 的子對象,這個 pushState 就屬於 window.history 的一個方法。簡單明瞭。html

  • 接下來,咱們進一步瞭解一下 pushState 。前端

    先看下面的一段代碼:html5

    var stateObj = { foo: "bar" }
    
    history.pushState(stateObj, "title", "bar.html")

    首先聲明一個狀態對象,可以儲存任何可序列化數據,好比將 html 碎片存儲於此,但大小有限制(640k),可使用 localStorage 等機制。固然也能夠不使用,它的取捨咱們後面具體實施時會提到。java

    pushState 方法往瀏覽器歷史棧裏插入一條歷史項,執行完成以後,瀏覽器會當即將歷史項中的 url(bar.html) 顯示在地址欄中,(url 接受的是相對地址,會自動補上域名),但不會將其加載。而 "title" 在這裏暫時沒有用處,瀏覽器不會用它來修改頁面標題,能夠填 null。jquery

    那什麼時機調用此方法?監聽你須要 pjax 效果的超連接的 click 事件,禁用默認的跳轉,而後 do that。但講到這裏你可能會想到,若是用戶進行瀏覽器的前進和後退操做,仍是會執行跳轉加載,那該如何處理呢?laravel

  • 這就要用到 pjax 不可忽視的關鍵角色 -- popstate 事件。這個事件只在瀏覽器的前進和後退操做時觸發,因此經過監聽它,如法炮製上述操做便可 。至此,咱們每一次的瀏覽訪問都向搜索引擎伸出了友好的橄欖枝。 git

    講到這裏,咱們大致瞭解了 pjax 的流程,就是監聽全部須要 pjax 效果的超連接,使用 ajax 和後端達成協議取回 html 碎片並填充到 DOM 當中,pushState 負責將瀏覽器地址欄修改爲咱們想要的 URL,而且往歷史棧中增長一個歷史項,經過監聽 popstate,讓瀏覽器的前進和後退也 pjax 化。github

  • 最後,replaceState 這個 history API 也有必要介紹一下。當你須要將當前激活的歷史項從歷史棧中完全抹去並替換成另外一個,那用它就對了,使用方法和 pushState 徹底同樣,最多見的使用場景是使用 pjax 刷新頁面。

具體實施

pjax 的安裝配置須要先後端配合進行。先後端的輪子都有很多現成的,但前端的輪子作不到開箱即用,這是由於 pjax 的實現須要結合項目的具體代碼進行實施,下面我會分別講解。

  • 前端採用最流行的 defunkt/jquery-pjax。話很少說,文檔寫的都很詳細。這裏主要根據源碼提幾點須要注意的地方:

    • 綁定選擇器時,推薦使用 data-pjax 屬性,這個屬性會自動尋找標籤及其子標籤中的超連接,綁定 click.pjax (順便注意這裏的事件命名空間,目的是爲了主動 trigger 時能區別對待 click) 事件。

      $(document).pjax('[data-pjax] a, a[data-pjax]', '#pjax-container')
    • jquery-pjax 作到了自動向後兼容,不須要單獨作兼容性判斷,放心調用 pjax 方法便可。

    • 若是你的後端程序響應慢,pjax 會不耐煩的直接跳轉,要麼將後端程序或者網絡環境優化,要麼讓 pjax 稍微耐心一點:

      $.pjax.defaults.timeout = 1600 /\*默認 650 毫秒\*/
    • 之因此說 pjax 不是開箱即用,主要是由於全部 js 腳本的調用會在第二次執行 pjax 方法時失效。我剛開始遇到這個問題時,一頭霧水,折騰了許久而不得解,而後在 laravel china 發帖求助,很快站長龍哥就站出來,耐心細緻的解答了個人疑惑。仔細研究了源碼,我發現了其中兩個有趣的函數:

      var container = extractContainer("", xhr, options)
      
      executeScriptTags(container.scripts)

      字面意思是將取回的 html 碎片 進行加工處理成一個容器對象,並處理其中的腳本標籤,那爲何第一次以後的 pjax 就沒執行個人腳本呢?咱們繼續閱讀兩個函數體內的關鍵代碼:

      extractContainer :

      // Gather all script[src] elements
      obj.scripts = findAll(obj.contents, 'script[src]').remove()
      obj.contents = obj.contents.not(obj.scripts)

      將 html 碎片中的全部帶 src 的腳本刪除並儲存在容器對象的 scripts 屬性中,將去除了 scripts 的 html 碎片內容賦給 contents 屬性。看到這裏,你可能會大概明白了 pjax 的用意。繼續看另外一個函數體的內容:

      executeScriptTags :

      if (!scripts) return
      
        var existingScripts = $('script[src]')
      
        scripts.each(function() {
          var src = this.src
          var matchedScripts = existingScripts.filter(function() {
            return this.src === src
          })
          if (matchedScripts.length) return
      
          var script = document.createElement('script')
          var type = $(this).attr('type')
          if (type) script.type = type
          script.src = $(this).attr('src')
          document.head.appendChild(script)
        })

      獲取目前 DOM 中的全部帶 src 的腳本,而後和 html 碎片中的腳本逐個作比對,若是碎片中有新的腳本就將其插入到 head 標籤的最後。啊哈~ pjax 這麼作是確保不會重複請求任何已經下載過的腳本文件,節省 HTTP 開支。但這麼作的弊端就是本段開頭說的那個問題,那如何解決呢?

      • 靈活運用 pjax 提供的事件。 要解決上述問題,咱們能夠監聽 pjax:end 事件,固然 pjax:success 和 pjax:complete 也行,區別不大 :

      $(document).on('pjax:end', function() {
              self.blogBootUp()
        })

    當 pjax 生命週期結束,主動調用一下腳本啓動程序便可。這裏,我將我全部的腳本啓動程序都封裝到 blogBootUp 中了,具體的代碼請移步個人 Blog 項目。

    • 使用了 pjax ,就至關於咱們阻斷了瀏覽器的常規瀏覽機制,使用相關接口去重寫瀏覽邏輯。對於瀏覽器的前進和後退功能,咱們監聽了 popstate 事件去使用 pjax,但在常規的狀況下,瀏覽器是有緩存的,因此咱們能秒進或秒退,但若是 pjax 不優化這一塊,那前進和後退也要去請求服務器的話會付出很多的代價。查看 pjax 源碼發現,做者是作了緩存處理的,經過兩個關鍵函數 cachePushcachePop 來模擬瀏覽器緩存,只不過是存在數組中(也就是內存中),若是你想達到 真正的 webApp 的水準,我以爲還須要配合 localStorage 和 WebSocket 等相似的機制來穩健的存儲數據和靈活的控制頁面的緩存時間,固然這就比較複雜了,待之後再去實踐好了。

  • 後端我選用了 JacobBennett/pjax兼容 laravel 5.* ,採用了中間件的形式,因此使用起來很簡單,直接將中間件引入到 app/Http/Kernel.php 中便可。讀了下源碼,發現無非是使用了一個 DOM 爬蟲根據客戶端 header 傳遞的 pjax 標識和 pjax 容器標識 (就是 selector),抓取 laravel 響應對象內容中的 title 和 容器內容,而後連綴在一塊兒複寫回去,返回給客戶端。

最後,再推薦使用一個加載效果動畫的 javascript 插件,配合 pjax 使用毫無違和感。rstacruz/nprogress,文檔寫的很簡潔明瞭,有專門針對 pjax 的使用說明,用上以後,仍是至關酷炫的。

參考資料

原文連接:https://macken.me/article/speed-up-your-website-with-pjax

相關文章
相關標籤/搜索