最近搞了下列表页请求的功能,并做了一下调研整理,记此文备忘。

列表页请求的功能到处可见,比如在博客园。

点击相应的页码,页面返回相应的内容,看上去似乎大同小异,但是一些小的细节还是可以区分优劣。

full load

公司原来的代码采用的是 full load 的方式,也就是每点击一次,页面完全加载。并不只有我们网站这样做,很多大厂也这样搞,比如 新浪

列表页中的很多部分内容,其实都是一样的,这样做就每次需要重新加载这部分的内容,没有必要,而且 css、js 都需要重新加载(虽然可能有缓存)。以前我逛学校的论坛,是用 PHP 的 Discuz! 搭建的,每个主题后的回复页之间的跳转都是 full load 的方式,体验很差。

所以个人觉得,不管是性能还是用户体验上,full load 的方式在现在的 web 开发中,都是不可取的。

ajax

接着 ajax 出现了。ajax 就不多做介绍了,局部刷新,体验非常好。但是单纯的 ajax 虽然性能上比 full load 提高了不少,用户体验还不是很好,主要有以下两点。

  • 保存不了书签
  • 不支持浏览器的后退前进操作

究其根本,是因为传统的 ajax 操作不改变 url。

ajax + #

为了解决以上问题,聪明的开发者们用 # 来改善体验。

以博客园为例,我们请求第二页的时候,实际的 url 是 http://www.cnblogs.com/#p2,当点击页码发送请求时,同时改变页面的 url,因为改变的是 lcation.hash,所以页面并不会重载。我们将其保存为书签,当我们打开 http://www.cnblogs.com/#p2 时,我们可以提取 hash 值,据此发起相应的 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 对象)中添加一个新纪录。

  1. var stateObj = {page: 2 };
  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 中的记录。

  1. history.pushState({page: 1}, "title 1", "?page=1");
  2. history.pushState({page: 2}, "title 2", "?page=2");
  3. history.replaceState({page: 3}, "title 3", "?page=3");
  4. history.back(); // url 显示为http://example.com/example.html?page=1
  5. history.back(); // url 显示为http://example.com/example.html
  6. history.go(2); // url 显示为http://example.com/example.html?page=3

前面 ajax + # 的例子中,我们用 hashchange 去判断浏览器的前进后退操作,那么,是否有原生的监听浏览器前进回退操作的事件呢?有的,popstate 事件。每当同一个文档的浏览历史(即 history 对象)出现变化时,就会触发 popstate 事件,只有当用户手动点击浏览器后退前进按钮,或者调用 back、forward、go 方法时才会触发。

  1. window.onpopstate = function(e) {
  2. console.log(e.state);
  3. // 等价于
  4. // console.log(history.state);
  5. }

于是我们要实现一个列表页请求的功能,就呼之欲出了。点击页码,用 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

浅析列表页请求优化(history API)的更多相关文章

  1. 关于帝国cms 列表页SEO优化的问题

    一般列表页面中,我们都需要带分页信息区分当前页号,为区分第一页,和第一页后的其他所有分页页面.我们推荐的做法为:第一页显示正常的标题,从第二页开始便显示xxxxx-第2页-xxxx网.做法是.修改帝国 ...

  2. 转: html5 history api详解~很好的文章

    从Ajax翻页的问题说起 请想象你正在看一个视频下面的评论,在翻到十几页的时候,你发现一个写得稍长,但非常有趣的评论.正当你想要停下滚轮细看的时候,手残按到了F5.然后,页面刷新了,评论又回到了第一页 ...

  3. 有关html5的history api

    从Ajax翻页的问题说起 请想象你正在看一个视频下面的评论,在翻到十几页的时候,你发现一个写得稍长,但非常有趣的评论.正当你想要停下滚轮细看的时候,手残按到了F5.然后,页面刷新了,评论又回到了第一页 ...

  4. HTML5 history API,创造更好的浏览体验

    HTML5 history API有什么用呢? 从Ajax翻页的问题说起 请想象你正在看一个视频下面的评论,在翻到十几页的时候,你发现一个写得稍长,但非常有趣的评论.正当你想要停下滚轮细看的时候,手残 ...

  5. 转:HTML5 History API 详解

    从Ajax翻页的问题说起 请想象你正在看一个视频下面的评论,在翻到十几页的时候,你发现一个写得稍长,但非常有趣的评论.正当你想要停下滚轮细看的时候,手残按到了F5.然后,页面刷新了,评论又回到了第一页 ...

  6. 大熊君学习html5系列之------History API(SPA单页应用的必备)

    一,开篇分析 Hi,大家好!大熊君又和大家见面了,(*^__^*) 嘻嘻……,这系列文章主要是学习Html5相关的知识点,以学习API知识点为入口,由浅入深的引入实例, 让大家一步一步的体会" ...

  7. HTML5 History API让ajax能回退到上一页

    HTML5 History API提供了一种功能,能让开发人员在不刷新整个页面的情况下修改站点的URL.这个功能很有用,例如通过一段JavaScript代码局部加载页面的内容,你希望通过改变当前页面的 ...

  8. 大熊君学习html5系列之------History API(SPA单页应用的必备------重构完结版)

    一,开篇分析 Hi,大家好!大熊君又和大家见面了,(*^__^*) 嘻嘻……,这系列文章主要是学习Html5相关的知识点,以学习API知识点为入口,由浅入深的引入实例, 让大家一步一步的体会" ...

  9. python测试开发django-23.admin列表页优化和排序

    前言 列表页优化和排序 ModelAdmin django的options.py里面 ModelAdmin类定义的参数可以设置admin后台列表页面,相关的参数如下 class ModelAdmin( ...

随机推荐

  1. python中logging模块

    1. 日志的等级 DEBUG.INFO.NOTICE.WARNING.ERROR.CRITICAL.ALERT.EMERGENCY 级别 何时使用 DEBUG 详细信息,典型地调试问题时会感兴趣. 详 ...

  2. wsl ubuntu 配置c++环境

    1.sudo apt-get install  build-essential 更新 配置源 2.sudo apt install gcc-8 3.sudo apt install g++-8 cd ...

  3. 【CPU微架构设计】利用Verilog设计基于饱和计数器和BTB的分支预测器

    在基于流水线(pipeline)的微处理器中,分支预测单元(Branch Predictor Unit)是一个重要的功能部件,它负责收集和分析分支/跳转指令的执行结果,当处理后续分支/跳转指令时,BP ...

  4. 解决Oracle数据库空间不足问题

    //查询表空间的大小以及文件路径地址select tablespace_name, file_id, file_name,round(bytes/(1024*1024),0) total_space ...

  5. Ubuntu 通过apt安装VSCode

    1. sudo vim /etc/apt/sources.list.d/vscode.list 并向里面添加:deb [arch=amd64] http://packages.microsoft.co ...

  6. Harry Potter and J.K.Rowling(半平面交+圆和矩形交)

    Harry Potter and J.K.Rowling http://acm.hdu.edu.cn/showproblem.php?pid=3982 Time Limit: 2000/1000 MS ...

  7. leetcode4:两个排序数组的中位数

    给定两个大小为 m 和 n 的有序数组 nums1 和 nums2 . 请找出这两个有序数组的中位数.要求算法的时间复杂度为 O(log (m+n)) . 1.我的思路:直接用sort,时间复杂度应如 ...

  8. golang项目:goa和micro

    https://github.com/goadesign/goa http://www.cnblogs.com/zhangqingping/p/5531171.html https://github. ...

  9. 关于Promise的记录和理解

    在JavaScript中,所有的代码都是单线程执行的,这就导致了其所有的网络请求,IO操作,浏览器时间等都是异步非阻塞的模式执行的,这就使得代码的执行顺序可能会超出我们的掌控. 尤其是当多个异步操作待 ...

  10. Anacond win10安装与介绍

    Anacond的介绍 Anaconda指的是一个开源的Python发行版本,其包含了conda.Python等180多个科学包及其依赖项. 因为包含了大量的科学包,Anaconda 的下载文件比较大( ...