技术控

    今日:104| 主题:49431
收藏本版 (1)
最新软件应用技术尽在掌握

[其他] PJAX,站点加速之翼

[复制链接]
花般破碎流年 发表于 2016-11-30 11:19:43
33 2

立即注册CoLaBug.com会员,免费获得投稿人的专业资料,享用更多功能,玩转个人品牌!

您需要 登录 才可以下载或查看,没有帐号?立即注册

x

PJAX,站点加速之翼-1 (搜索引擎,history,浏览器,window,china)

  pjax 是一款 Adorable jQuery 小插件,将 ajax 和浏览器的 pushState API 封装到一起,解决了单纯使用 ajax 进行无刷新加载时对搜索引擎的不友好,并且节省了 HTTP 开支、提高了浏览速度,明显地优化了用户体验。
   前一段时间在 laravel china 接触到了 pjax ,而且在站长龙哥的帮助下,解决了安装配置过程中的关键问题。在此,感谢龙哥和所有我参考过的有关 pjax 文章的作者们,感谢开源和分享。
  也是好久没有更新博客了,今天终于完成了这篇文章,也算是对十一月有了交代(惭愧)。有些事情,决定要做好的,坚持做下去是对自己最好的交代。目的当然还是作为自己的笔记来整理思路和方便以后查阅,文中有不正确或者不准确的地方如果能够得到反馈,那真真是极好的。
  知识要点

  
       
  •   ajax自不多说,在这里负责带着 pjax 标识请求后端,将生成好的 html 碎片(注意不是前端取回JSON来进行渲染)取回,然后 jQuery 将它替换到 DOM 当中。
       
  •   pushState 是 html5 提供的API,是对浏览器历史对象 history 的增强。 了解 Javascript 的都知道 BOM (浏览器对象模型),而 window 则是 BOM 的具体实现, history 则是 window 的子对象,这个 pushState 就属于 window.history 的一个方法。简单明了。
       
  • 接下来,我们进一步了解一下 pushState 。

    先看下面的一段代码:
    [code]var stateObj = { foo: "bar" }
    history.pushState(stateObj, "title", "bar.html")[/code] 首先声明一个状态对象,储存的任何可序列化数据,比如将 html 碎片存储于此,但大小有限制(640k),可以使用 localStorage 等机制。当然也可以不使用,它的取舍我们后面具体实施时会提到。
    pushState 方法往浏览器历史栈里插入一条历史项,执行完成之后,浏览器会立即将历史项中的 url(bar.html) 显示在地址栏中,(url 接受的是相对地址,会自动补上域名),但不会将其加载。而 "title" 在这里暂时没有用处,浏览器不会用它来修改页面标题,可以填 null。
    那什么时机调用此方法?监听你需要 pjax 效果的超链接的 click 事件,禁用默认的跳转,然后 do that。但讲到这里你可能会想到,如果用户进行浏览器的前进和后退操作,还是会执行跳转加载,那该如何处理呢?
       
  •   这就要用到 pjax 不可忽视的关键角色 -- popstate 事件。这个事件只在浏览器的前进和后退操作时触发,所以通过监听它,如法炮制上述操作即可 。至此,我们每一次的浏览访问都向搜索引擎伸出了友好的橄榄枝。
    讲到这里,我们大体了解了 pjax 的流程,就是监听所有需要 pjax 效果的超链接,使用 ajax 和后端达成协议取回 html 碎片并填充到 DOM 当中,pushState 负责将浏览器地址栏修改成我们想要的 URL,并且往历史栈中增加一个历史项,通过监听 popstate,让浏览器的前进和后退也 pjax 化。
       
  • 最后,replaceState 这个 history API 也有必要介绍一下。 当你需要将当前激活的历史项从历史栈中彻底抹去并替换成另一个,那用它就对了,使用方法和 pushState 完全一样,最常见的使用场景是使用 pjax 刷新页面。  
  具体实施

  pjax 的安装配置需要前后端配合进行。前后端的轮子都有不少现成的,但前端的轮子做不到开箱即用,这是因为 pjax 的实现需要结合项目的具体代码进行实施,下面我会分别讲解。
  
       
  •   前端采用最流行的 defunkt/jquery-pjax 。话不多说,文档写的都很详细。这里主要根据源码提几点需要注意的地方:
           
    •   绑定选择器时,推荐使用 data-pjax 属性,这个属性会自动寻找标签及其子标签中的超链接,绑定 click.pjax (顺便注意这里的事件命名空间,目的是为了主动 trigger 时能区别对待 click) 事件。
      [code]$(document).pjax('[data-pjax] a, a[data-pjax]', '#pjax-container')[/code]     
    •   jquery-pjax 做到了自动向后兼容,不需要单独做兼容性判断,放心调用 pjax 方法即可。
           
    • 如果你的后端程序响应慢,pjax 会不耐烦的直接跳转,要么将后端程序或者网络环境优化,要么让 pjax 稍微耐心一点:
      [code]$.pjax.defaults.timeout = 1600 /\*默认 650 毫秒\*/[/code]     
    •   之所以说 pjax 不是开箱即用,主要是因为所有 js 脚本的调用会在 第二次执行 pjax 方法时失效 。我刚开始遇到这个问题时,一头雾水,折腾了许久而不得解,然后在 laravel china 发帖求助,很快站长龙哥就站出来,耐心细致的解答了我的 疑惑 。仔细研究了源码,我发现了其中两个有趣的函数:
      [code]var container = extractContainer("", xhr, options)

      executeScriptTags(container.scripts)[/code] 字面意思是将取回的 html 碎片 进行加工处理成一个容器对象,并处理其中的脚本标签,那为什么第一次之后的 pjax 就没执行我的脚本呢?我们继续阅读两个函数体内的关键代码:
      extractContainer :

      [code]// Gather all script[src] elements
      obj.scripts = findAll(obj.contents, 'script[src]').remove()
      obj.contents = obj.contents.not(obj.scripts)[/code] 将 html 碎片中的所有带 src 的脚本删除并储存在容器对象的 scripts 属性中,将去除了 scripts 的 html 碎片内容赋给 contents 属性。看到这里,你可能会大概明白了 pjax 的用意。继续看另一个函数体的内容:
      executeScriptTags :

      [code]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)
      })[/code] 获取目前 DOM 中的所有带 src 的脚本,然后和 html 碎片中的脚本逐个做比对,如果碎片中有新的脚本就将其插入到 head 标签的最后。啊哈~ pjax 这么做是确保不会重复请求任何已经下载过的脚本文件,节省 HTTP 开支。但这么做的弊端就是本段开头说的那个问题,那如何解决呢?
           
    •   灵活运用 pjax 提供的事件。要解决上述问题,我们可以监听 pjax:end 事件,当然 pjax:success 和 pjax:complete 也行,区别不大 :
      [code]$(document).on('pjax:end', function() {
          NProgress.done()
          self.blogBootUp()
      })[/code]  当 pjax 生命周期结束,主动调用一下脚本启动程序即可。这里,我将我所有的脚本启动程序都封装到 blogBootUp 中了,具体的代码请移步我的 Blog 项目。
           
    • 使用了 pjax ,就相当于我们阻断了浏览器的常规浏览机制,使用相关接口去重写浏览逻辑。对于浏览器的前进和后退功能,我们监听了 popstate 事件去使用 pjax,但在常规的情况下,浏览器是有缓存的,所以我们能秒进或秒退,但如果 pjax 不优化这一块,那前进和后退也要去请求服务器的话会付出不少的代价。查看 pjax 源码发现,作者是做了缓存处理的,通过两个关键函数 cachePush 和 cachePop 来模拟浏览器缓存,只不过是存在数组中(也就是内存中),如果你想达到 真正的 webApp 的水准,我觉得还需要配合 localStorage 和 WebSocket 等类似的机制来稳健的存储数据和灵活的控制页面的缓存时间,当然这就比较复杂了,待以后再去实践好了。   
       
  • 后端我选用了 JacobBennett/pjax 。 兼容 laravel 5.* ,采用了中间件的形式,所以使用起来很简单,直接将中间件引入到 app/Http/Kernel.php 中即可。读了下源码,发现无非是使用了一个 DOM 爬虫根据客户端 header 传递的 pjax 标识和 pjax 容器标识 (就是 selector),抓取 laravel 响应对象内容中的 title 和 容器内容,然后连缀在一起复写回去,返回给客户端。  
   最后,再推荐使用一款加载效果动画的 javascript 插件,配合 pjax 使用毫无违和感。 rstacruz/nprogress ,文档写的很简洁明了,有专门针对 pjax 的使用说明,用上之后,立马和 google+ 和 github 看齐,你值得拥有。。
  参考资料

  
       
  • 在 Laravel 应用中使用 pjax 进行页面加速   
  • PJAX的实现与应用   
  • HTML5 Page Cache with pjax + Web Storage + Firebase   
  • defunkt/jquery-pjax   
  • JacobBennett/pjax  
   本文链接: https://macken.me/article/speed-up-your-website-with-pjax
   声明 在转载或修改本文后发布的文章中注明原文来源信息的前提下,允许进行转载该篇文章或经修改后发布且不用告知本文作者。
友荐云推荐




上一篇:多重共线性的判别与解决
下一篇:在 C 的世界之外
酷辣虫提示酷辣虫禁止发表任何与中华人民共和国法律有抵触的内容!所有内容由用户发布,并不代表酷辣虫的观点,酷辣虫无法对用户发布内容真实性提供任何的保证,请自行验证并承担风险与后果。如您有版权、违规等问题,请通过"联系我们"或"违规举报"告知我们处理。

西安网 发表于 2016-11-30 13:23:49
顶起花般破碎流年
回复 支持 反对

使用道具 举报

wqrtx 发表于 5 天前
帖子要回!回复有分加
回复 支持 反对

使用道具 举报

*滑动验证:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

我要投稿

推荐阅读

扫码访问 @iTTTTT瑞翔 的微博
回页顶回复上一篇下一篇回列表手机版
手机版/CoLaBug.com ( 粤ICP备05003221号 | 文网文[2010]257号 )|网站地图 酷辣虫

© 2001-2016 Comsenz Inc. Design: Dean. DiscuzFans.

返回顶部 返回列表