浅析列表页请求优化(history API)
最近搞了下列表页请求的功能,并做了一下调研整理,记此文备忘。
列表页请求的功能到处可见,比如在博客园。
点击相应的页码,页面返回相应的内容,看上去似乎大同小异,但是一些小的细节还是可以区分优劣。
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 搜索制定了一套规则。
- 网站提交 sitemap 给 Google;
- Google 发现 URL 里有 #! 符号,例如example.com/#!/detail/1,于是 Google 开始抓取 example.com/?_escaped_fragment_=/detail/1;
_escaped_fragment_ 这个参数是 Google 指定的命名,如果开发者希望把网站内容提交给 Google,就必须通过这个参数生成静态页面。
也就是说,每个 ajax 请求的内容,都需要提供一个相同内容的静态页面,供爬虫爬取。
随着 web 的发展,这一切已经成为了历史,现在的爬虫已经可以执行 JavaScript,爬取 ajax 请求的内容了!这部分的内容,就不展开了,有兴趣的可以参考下以下链接。
- Understanding web pages better
- Updating our technical Webmaster Guidelines
- 我们将弃用 AJAX 抓取方案
- AJAX Crawling (Deprecated)
- Making AJAX applications crawlable
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.pushState
,history.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 插件。
- https://github.com/defunkt/jquery-pjax (GitHub 使用)
- https://github.com/welefen/pjax (welefen 基于上面那个插件改造的,不过好像已经不维护了)
使用方式可以参照相应的 README,不多做介绍了。
read more
- Window.history
- Manipulating the browser history
- history 对象
- PJAX 的实现与应用
- pjax:ajax 和 pushState 结合的 js 库
- ajax 与 HTML5 history pushState/replaceState 实例
- 使用 h5 的 history 改善 ajax 列表请求体验
浅析列表页请求优化(history API)的更多相关文章
- 关于帝国cms 列表页SEO优化的问题
一般列表页面中,我们都需要带分页信息区分当前页号,为区分第一页,和第一页后的其他所有分页页面.我们推荐的做法为:第一页显示正常的标题,从第二页开始便显示xxxxx-第2页-xxxx网.做法是.修改帝国 ...
- 转: html5 history api详解~很好的文章
从Ajax翻页的问题说起 请想象你正在看一个视频下面的评论,在翻到十几页的时候,你发现一个写得稍长,但非常有趣的评论.正当你想要停下滚轮细看的时候,手残按到了F5.然后,页面刷新了,评论又回到了第一页 ...
- 有关html5的history api
从Ajax翻页的问题说起 请想象你正在看一个视频下面的评论,在翻到十几页的时候,你发现一个写得稍长,但非常有趣的评论.正当你想要停下滚轮细看的时候,手残按到了F5.然后,页面刷新了,评论又回到了第一页 ...
- HTML5 history API,创造更好的浏览体验
HTML5 history API有什么用呢? 从Ajax翻页的问题说起 请想象你正在看一个视频下面的评论,在翻到十几页的时候,你发现一个写得稍长,但非常有趣的评论.正当你想要停下滚轮细看的时候,手残 ...
- 转:HTML5 History API 详解
从Ajax翻页的问题说起 请想象你正在看一个视频下面的评论,在翻到十几页的时候,你发现一个写得稍长,但非常有趣的评论.正当你想要停下滚轮细看的时候,手残按到了F5.然后,页面刷新了,评论又回到了第一页 ...
- 大熊君学习html5系列之------History API(SPA单页应用的必备)
一,开篇分析 Hi,大家好!大熊君又和大家见面了,(*^__^*) 嘻嘻……,这系列文章主要是学习Html5相关的知识点,以学习API知识点为入口,由浅入深的引入实例, 让大家一步一步的体会" ...
- HTML5 History API让ajax能回退到上一页
HTML5 History API提供了一种功能,能让开发人员在不刷新整个页面的情况下修改站点的URL.这个功能很有用,例如通过一段JavaScript代码局部加载页面的内容,你希望通过改变当前页面的 ...
- 大熊君学习html5系列之------History API(SPA单页应用的必备------重构完结版)
一,开篇分析 Hi,大家好!大熊君又和大家见面了,(*^__^*) 嘻嘻……,这系列文章主要是学习Html5相关的知识点,以学习API知识点为入口,由浅入深的引入实例, 让大家一步一步的体会" ...
- python测试开发django-23.admin列表页优化和排序
前言 列表页优化和排序 ModelAdmin django的options.py里面 ModelAdmin类定义的参数可以设置admin后台列表页面,相关的参数如下 class ModelAdmin( ...
随机推荐
- python中logging模块
1. 日志的等级 DEBUG.INFO.NOTICE.WARNING.ERROR.CRITICAL.ALERT.EMERGENCY 级别 何时使用 DEBUG 详细信息,典型地调试问题时会感兴趣. 详 ...
- wsl ubuntu 配置c++环境
1.sudo apt-get install build-essential 更新 配置源 2.sudo apt install gcc-8 3.sudo apt install g++-8 cd ...
- 【CPU微架构设计】利用Verilog设计基于饱和计数器和BTB的分支预测器
在基于流水线(pipeline)的微处理器中,分支预测单元(Branch Predictor Unit)是一个重要的功能部件,它负责收集和分析分支/跳转指令的执行结果,当处理后续分支/跳转指令时,BP ...
- 解决Oracle数据库空间不足问题
//查询表空间的大小以及文件路径地址select tablespace_name, file_id, file_name,round(bytes/(1024*1024),0) total_space ...
- Ubuntu 通过apt安装VSCode
1. sudo vim /etc/apt/sources.list.d/vscode.list 并向里面添加:deb [arch=amd64] http://packages.microsoft.co ...
- 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 ...
- leetcode4:两个排序数组的中位数
给定两个大小为 m 和 n 的有序数组 nums1 和 nums2 . 请找出这两个有序数组的中位数.要求算法的时间复杂度为 O(log (m+n)) . 1.我的思路:直接用sort,时间复杂度应如 ...
- golang项目:goa和micro
https://github.com/goadesign/goa http://www.cnblogs.com/zhangqingping/p/5531171.html https://github. ...
- 关于Promise的记录和理解
在JavaScript中,所有的代码都是单线程执行的,这就导致了其所有的网络请求,IO操作,浏览器时间等都是异步非阻塞的模式执行的,这就使得代码的执行顺序可能会超出我们的掌控. 尤其是当多个异步操作待 ...
- Anacond win10安装与介绍
Anacond的介绍 Anaconda指的是一个开源的Python发行版本,其包含了conda.Python等180多个科学包及其依赖项. 因为包含了大量的科学包,Anaconda 的下载文件比较大( ...