Ajax真的只能用来局部刷新吗?

cover-image
端午六一休息三天宅了一天, 独自去市中心里转了一天, 就剩下一天了[惊恐]. 压根就没有马上要考试了的感觉嘛! 文章也是好久没更新了, 浑浑噩噩过下去曾的好嘛>.O. 好嘛, 那就来写点干货好了, 也是最近在搞Ghost, 把这个主题的逻辑 (原先是 jQ ) 用原生 js 重写了一遍 (还顺便搞了个小项目 GhostBot 来处理站内搜索), 总结总结研究出了些什么好了, 废话讲得多, 直接进正题吧.

##PJAX
关于题目抛出的这个问题, 我既然都这么问了, 正常人都知道答案肯定是不是咯. 其实AJAX这个东西一直以来都是被当做只能处理小范围的, 异步的页面更新, 它的优点就在于可以实现页面的无刷新操作. 但是这也会造成一些问题, 比如对搜索引擎兼容性不好啦 (抓不到 ajax 载入的信息), 还有无法后退神马的!

对于无法前进后退, 邪恶的 W3C 就在 HTML5 中搞了一个叫history对象的东西, 使用 history 对象的pushState方法给浏览器的浏览历史中塞入一个地址, 返回的时候再借助popstate事件来还原到原先的模样, 这样一来 AJAX 和 pushState 就开始名正言顺地搞基了! 于是我们就把这俩基友称为PJAX.

##(正常情况下) 它是如何工作的
这个东西其实不是什么新生事物, 国内在 2012 年就已经对它有一些提及, 只是没有流行开而已, 经常上 GitHub 和 Youtube 的人不知道有没有发现这俩其实早就使用PJAX了.

HTML5 新增的方法在这里:

  • history.pushState(state, title [, url]) : 向历史记录(把它看做一个栈)的顶端加入一条记录, state 是一个指明状态的对象, 如 {url: 'xxxxx', title: 'yyyyy', myInfo: 'zzzzz'};
  • history.replaceState(state, title [, url]) : 跟上面差不多, 功能就是替换历史记录顶上的记录;
1
2
3
4
5
//当页面准备改变时
window.history.pushState({
title: 'Home Page',
url: 'http://balabala.com'
}, document.title, location.href);

响应前进后退的事件:

  • window.onpopstate : 上面传递的state对象会成为event的子对象,这样就可以拿到存储的title和URL了. 这个事件在我所测试的 Chrome 36dev 和 Safari 7 中表现有差异, 具体在页面首次载入时, Chrome 不会触发这一事件而 Safari 会触发.
1
2
3
4
5
6
window.onpopstate = function (ev) {
var state = ev.state;
if (state) {
//do ajax here;
}
}

国内也早就有很多开源的 PJAX 插件, 基于 jQuery, qwrap, kissy 什么的, 但是貌似我没有找到原生的啊, 反正到最后还是自己写了个渣渣的.

###前端处理
首选我们需要一个容器 (container) 来做页面中需要改变的部分. 然后将不需要改变的部分, 比如 footer, header 放在容器的外面.

1
2
3
4
5
<style>
#container{height: 100%;width: 100%;}
</style>

<div id="container">{{#content}}</div>
<div class="footer">{{#footer}}</div>

对于页面上所有的超链接 (a标签), 我们都需要采取 hijack(劫持) 的方式来处理点击事件.

1
2
3
4
5
6
7
8
9
var a = document.getElementsByTagName('a');
Array.prototype.slice.call(a).forEach(function (_a) {
_a.onclick = hijackLinks; //hijackLinks为链接处理函数
});
function hijackLinks (ev) {
if(/* 外域链接 */) return;
ev.preventDefault();
// 处理PJAX
}

差异于一般的 AJAX 返回 JSON, PJAX 的后端返回的一般是 HTML 片段. 前台再直接使用 innerHTML 来完成页面无刷. 对于同一个 URL, 如何告诉后端这是一个 PJAX 请求呢? So easy! 直接在 HTTP 请求头上带上标记就好了. 在前端 Ajax 中带上

1
setRequestHeader(‘PJAX’, ‘true’);

###后端处理
接上面的话, 直接在后台判断 HTTP 头就行了 (以 PHP 为例):

1
2
3
4
5
6
<?php 
if(array_key_exists('HTTP_X_PJAX', $_SERVER) && $_SERVER['HTTP_X_PJAX'] === 'true'){
//在这里输出你的 HTML 片段
}else{
//正常输出
}

##为什么要用它呢
简而言之, PJAX = AJAX + history.pushState. 这货不仅可以无刷新地改变页面内容, 还可以改变 URL 和浏览器历史记录, 使得前进后退的体验一体化.

Wow, 不明觉厉的样子也.

好吧, 那我们上个图来看看, 以 GitHub 为例.

如下图, 我首先打开了一个 (我的0.0) GitHub 主页, 可以看到浏览器的历史记录是空的 (灰色);
pjax-before

然后我点击了 Contributions 按钮. 页面改变了, 但是页面并没有刷新, 看样子是 PJAX 基佬起作用了! 浏览器的历史记录里面出现了刚才访问的位置, 地址栏也变化了, 下面的调试窗口显示有三条 XHR (XMLHttpRequest) 请求成功.
pjax-after

还是不明白? 自己去 GitHub.com 看个究竟把!

噢对了, 还没说为什么要用 PJAX 呢.

  1. 无刷新, 速度快 (快就是王道)
  2. 无刷新意味着可以在页面改变的时候施展各种绚丽的动画 (漂漂的, 暖暖的)
  3. 对搜索引擎支持好, 有利于 SEO
  4. 我™就是喜欢它怎么着, 打我呀

顺便说说弊端.

  1. 逻辑比较复杂. (跟正常方式不一样的都叫复杂)
  2. 需要前后台配合. (这点后面再讲, 其实可以克服)
  3. 跨域限制. (其实这是安全原则, 也可以不算弊端拉)
  4. 兼容性什么就不提了, 不用优雅的 Chrome 的都别来我的 blog 就对了.

##我的方案 (不正常情况)
本站就使用了全站内链的 PJAX 无刷, 并开启了 10 分钟的 localStorage, 以尽量减少 HTTP 请求.

因为Ghost的主题只是提供了 hbs 模板文件和静态资源文件, 我并不能通过修改 Ghost 内核的方式来使得输出 HTML 片段. 所以只能通过解析 html 的方式来进行 (电脑牛逼, 不怕卡, 打我呀).

1
2
3
4
5
6
7
8
var ajax = new Ajax(); //我封装的一个Ajax类
ajax.get(url, function (doc) {
var _html = document.createElement('html');
_html.innerHTML = doc; //不用append到文章中即可在内存中解析HTML
var newContainer = _html.querySelector('#container');
document.querySelector('#container').innerHTML = newContainer.innerHTML;
//do others.
});

这样就减少了一个弊端, 就是可以不修改后台直接在前台做好所有的工作.

##兼容性

直接看图吧

兼容性

  • IE10+
  • Chrome 5+
  • Safari 5+
  • Opera 11.5+
  • Firefox 4+

协议性URL

如果你的浏览器正在访问一个 HTTPS 协议的站点, 如果没有被特别指定, 那么它也会通过 HTTPS 协议来请求当前域下的静态文件. 这个策略会导致该死的 IE 浏览器弹出此页包含安全和不安全的项目警告, 所以最佳的方案是尽量让你的所有静态资源保持在同一种协议下.

当然, 如果你正在浏览本地的文件, 那么浏览器会试图使用file:///协议来请求文件.

我们通常会使用下面这样的代码来获取 Google CDN 提供的的 jQuery:

1
2
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.js"></script>
<script>!window.jQuery && document.write(unescape('%3Cscript src="js/libs/jquery-1.4.2.js"%3E%3C/script%3E'))</script>

我们可以看到 src 并没有指定协议名称.

从技术上而言, 这种方式在 RFC 的 3986 标准中被定义为网络路径引用(network-path reference). 对了, 准确地讲, 我们在讨论URL的时候, 尽量使用术语scheme来代替protocol(协议).

这种技巧在也可以在 css 中使用:

1
.omgomg { background: url(//websbestgifs.net/kittyonadolphin.gif); }

当然这基于你的网站能通过 HTTP 和 HTTPS 协议中的任何一种获取到网络资源.

警告!: 当你使用 link 标签或者 @import 语法获取样式表的时候, 该死的 IE7IE8 会下载两次 css 文件, 除了这俩逗比其它浏览器还算正常.

好了, 那么我们就可以理解百度统计的监视代码默认是这样的:

1
2
3
4
<script type="text/javascript">
var _bdhmProtocol = (("https:" == document.location.protocol) ? " https://" : " http://");
document.write(unescape("%3Cscript src='" + _bdhmProtocol + "hm.baidu.com/h.js%3F
{{My_Baidu_Tongji_Hash}}' type='text/javascript'%3E%3C/script%3E"));
</script>

这里的私有变量_bdhmProtocol就是当前页面使用的协议的判断.

这样我们就可以对这行代码进行优化了: 如果你的站点只支持 http 协议(当然这在国内应该是绝大部分), 不妨把上面的 js 代码改成这样:

1
2
3
<script type="text/javascript">
document.write(unescape("%3Cscript src='http%3A//hm.baidu.com/h.js%3F
{{My_Baidu_Tongji_Hash}}' type='text/javascript'%3E%3C/script%3E"));
</script>


本文翻译自4年前的一篇博文, 偶尔逛站遇到的.

原文是: The Protocol-relative URL

独立博客为我们在茫茫网海中占据一粒尘的位置, 不求访问量, 只求内心舒坦.

解决在Parallels中无法使用Ubuntu 14.04的问题

cover-image
上个月就听说Ubuntu 14.04 LTS版发布了, 从12, 13一直用ubuntu做我的主用Linux系统, 长支持版本想必是应该比较稳定了, 于是乎下了一个丢到Parallels去, 装完tools之后死活进不去桌面, 一直保持假死状态, 重装几遍都是一样的, 于是暂时退回13.10用着, 等Parallels的版本升级.

然后也就是今天听说官方给出了暂时的解决办法. 我就给翻译翻译, 以下是方法.


  1. 在Parallels里启动你的Ubuntu 14.04, 然后按下Ctrl+Alt+F2激活控制台
  2. 输入你的用户名和密码
  3. 找到 xorg.conf.XXXXXXXX (XXXXXXXX 指的是这个文件被创建的时间)
1
ls -l /etc/X11/

检查这个文件是否包含Parallels字段

1
cat /etc/X11/xorg.conf.XXXXXXXX

check

然后输入命令(注意替换下面的XXXXXXXX)

1
2
3
4
5
sudo mv /etc/X11/xorg.conf.XXXXXXXX /usr/share/X11/xorg.conf.d/xorg.conf

export DISPLAY=:0

gsettings reset org.compiz.core:/org/compiz/profiles/unity/plugins/core/ active-plugins

Ctrl+Alt+F7退出控制台

重启你的虚拟机就Ok了.