前言


最近闲暇时的娱乐就是不断完善“卢哥主题”,从最早的版本v1.0.0到最近的v1.1.1,很多设计在推倒重做中变得愈加成熟。在这个过程中,我也学到了很多,多到足以让我静下心来写篇心得。

回首之前所写过的两篇有关wordpress教程,心里直犯恶心,两年前的代码竟然这么挫!这么点东西也好意思拿出来卖弄。不过回想当时的情形,恰是接触php不久,前端更是基本不会。今天所写的内容,可能在职业前端的眼里一样不堪入目吧。但是话说回来,两年前写下的算法题解,现在看居然是一愣一愣的,这说明当时算法学的还行吧。哈哈。

导读


全站AJAX化实际是很简单的,实际就是反复运用AJAX函数的过程。难点在于对form标签的处理,以及解决前进后退问题。综上,可以将AJAX的运用环境简单分成三类:

  • 对a标签运用AJAX函数
  • 对form表单运用AJAX函数
    • 提交评论,POST方式传参,逻辑上可以不用记录访问历史。
    • 站内搜索,GET方式传参,url改变,逻辑上应该记录访问历史。
  • 前进后退调用AJAX函数

此外,还要为AJAX定义专用的调用方式,减少网络开销。对于某些需要对网页进行渲染的插件如代码高亮等进行简单的修改。本文将对前者进行简单介绍,后者将单独撰文。

对a标签进行AJAX化处理


对a标签进行AJAX化处理应该是AJAX函数运用时所能遇到的最简单的情形。实际原理就是通过AJAX函数发出请求,并将请求结果替换到当前页面中。

$("h1 a").live("click",//绑定h1标签中的链接的click事件,即点击链接时触发。
function() {
	$.ajax({
		url: $(this).attr("href"),//AJAX请求的是链接指向的页面
		success: function(data) {
			$(".content").replaceWith($(data).find(".content"));//将结果替换到当前页面
		}
	});
	return false
});

上面的展示的代码是对h1标签中的链接进行AJAX化,作用是单击h1标签,将请求链接指向的页面,并将结果中的类名为content的元素替换到当前页面中。这短短几行代码实际就是“卢哥主题”对站内链接处理的简化模型,单击主题最上方的博客名称将会无刷新返回主页。但实际情况往往更复杂。

上面的代码可以“悄无声息”的载入新页面。为何要说他“悄无声息”,因为他仅仅更改了页面元素,没有留下任何能让浏览器认识到页面已经更换的信息。若不向浏览器添加访问历史,那么前进后退按钮就不会生效(必要不充分条件),url也不会变更,读者无法收藏指定的网页。除此之外,页面标题也必须更改,否则放在读者收藏夹里的页面都是同一个名字。所以,我们可以总结出两条新需求,添加访问历史与更改页面标题。

$("h1 a").live("click", //绑定h1标签中的链接的click事件,即点击链接时触发。
function() {
	window.history.pushState({ //添加访问历史
		time: new Date().getTime()
	},
	"", $(this).attr("href"));
	$.ajax({
		url: $(this).attr("href"),
		//AJAX请求的是链接指向的页面
		success: function(data) {
			$(".content").replaceWith($(data).find(".content")); //将结果替换到当前页面
			document.title = $(data).filter("title").text(); //更改页面标题
		}
	});
	return false
});

上面的代码中,window.history.pushState()用来新增访问历史,为document.title赋值可以变更页面标题。

对表单提交进行AJAX化处理


对表单提交进行AJAX处理,就是用AJAX函数代替表单发送参数,但这涉及到对表单数据的处理。前人种树后人乘凉,有幸在网上搜到网友“ZjFree-自由自在”的一篇博文《AJAX提交Form》。代码思路很清晰,将发送form表单的行为封装成ajaxSubmit()函数,发送前调用getFormJson()函数将表单参数转化为Json形式。

//将发送表单行为封装成ajaxSubmit函数
function ajaxSubmit(frm, fn, errorfn) {
	var dataPara = getFormJson(frm);
	$.ajax({
		url: frm.action,
		type: frm.method,
		data: dataPara,
		success: fn,
		error: errorfn
	})
}
//处理表单参数,转化成Json形式。
function getFormJson(frm) {
	var o = {};
	var a = $(frm).serializeArray();
	$.each(a,
	function() {
		if (o[this.name] !== undefined) {
			if (!o[this.name].push) {
				o[this.name] = [o[this.name]]
			}
			o[this.name].push(this.value || '')
		} else {
			o[this.name] = this.value || ''
		}
	});
	return o
}

上述代码展示了封装表单发送行为的函数。调用时只需将精力放在AJAX success的逻辑上即可。若表单为GET传参,且在逻辑上需要添加访问历史,则要对访问历史中的url添加参数模拟真实情景。下面展示用于wordpress内置搜索小工具AJAX化的代码。

$('.widget_search form').bind('submit',//绑定GET表单submit事件
function() {
	window.history.pushState({
		time: new Date().getTime()
	},
	"", '/?s=' + $(this).find("#s").val());//访问历史中的url应该如实记录,模仿真实GET表单在url中加入参数。
	ajaxSubmit(this,
	function(data) {
		$(".content").replaceWith($(data).find(".content"));
		document.title = $(data).filter("title").text()
	});
	return false
});

在wordpress中,表单POST传参通常用于发布评论,且跳转页面与当前页面相同,即url不会改变,因此在逻辑上也不需要添加访问历史与替换页面标题,因此更加简单。

解决浏览器前进后退失效问题


通过实践发现,即使添加了访问历史,前进后退按钮依然无用,仅仅改变url与页面标题而不跳转。经过一番google,得知单击前进后退按钮会触发popstate事件,将其绑定即可。唯一需要注意的是,不需要再为其人为添加访问历史了。

$(window).bind("popstate",//绑定popstate事件
function() {
	$.ajax({
		url: document.location.href,//当前窗口url
		success: function(data) {
			$(".content").replaceWith($(data).find(".content"));
		}
	})
});

AJAX性能优化与SEO


我们常能听到这样一种论调,那就是AJAX对SEO不友好。实际这是一种误区,原因是很大一部分人利用a标签onclick事件调用专为AJAX准备的方法,但却未给href属性赋上有效链接。而搜索引擎在爬一个页面时是不加载css与js的,这就造成链接指向页面无法收录,即对SEO不友好。

AJAX给人第一印象是性能好,事实上也确实减少了网络开销。网站利用AJAX技术,可以有效减少重复的http请求数,css与js等静态文件通常请求一次即可。但是优化仍可更进一步。之前介绍的几种AJAX调用方式仅仅是绑定a或form标签,请求的依然是一个完整的页面,虽对SEO完全无影响,却依然存在网络资源浪费。因为绝大多数情况下我们仅仅需要所请求页面的某个元素,元素的长度也许仅为页面总长度的几十分一。举个例子:某博客评论采取分页形式,5评论1页,当用户翻取某长篇文章的评论时,每次换页都需重复加载整篇文章及其它页面元素,而用户真正需要的仅仅是5条评论而已。

因此,我们既要为AJAX定义专门的调用方式,又需要确保a标签href属性指向一个有效链接。“卢哥主题”采用的解决方法是在目标页面的url上加上参数用以区分普通调用与AJAX调用,当检测到参数时,主题只生成特定部分的结果。下面代码是以“卢哥主题”首页文件“index.php”为原型抽象出的模型。

<?php if($_GET['target'] != 'post') { ?>//参数target值不为post时加载
<!DOCTYPE html>
<html>
//...
<?php } ?>
	<div class="content">
	//...
	</div>
<?php if($_GET['target'] != 'post') { ?>
//...
</html>
<?php } ?>

当AJAX函数请求的url中带参数target且其值为post时,仅会请求到类名为content的元素,其他元素被过滤且不会参与计算。当去掉参数target或赋其他值时,将会请求到整个页面。以此类推,还可以为诸如文章页面文件“single.php”设定专门的参数值如target=comments,表示仅请求其评论部分,文件的逻辑与上述代码类似,过滤掉非评论部分即可。