在AJAX编程中离不开对XMLHttpRequest对象的使用。XMLHttpRequest对象代表了浏览器和服务器之间的通讯,并可采用异步模式。浏览器通过XMLHttpRequest在后台发起请求,然后通过其属性onreadystatechange注册的回调函数来异步处理应答。编程步骤如下:

1. 创建XMLHttpRequest对象xhr;
2. 调用xhr的open方法打开资源;
3. 通过xhr的属性onreadystatechange注册回调函数;
4. 通过xhr的方法setRequestHeader设置相应请求头;
5. 调用xhr的send方法发起请求。

从上我们可以看到,编程步骤固定,在某些步骤中有些许的变化。由此我们可以联想到模板(template)和回调(callback)编程模式。在此处的回调依赖于xhr的状态变化,我们可以在模板与回调的基础之上采用观察者模式来封装,从而达到代码的从用和简化编程。

在这里,事件源实际上就是XMLHttpRequest的对象,我将它封装到Request类型;事件是XMLHttpRequest对象的状态readyState;而回调函数是事件处理器,我用RequestListener和RequestAdapter进行封装。一下是程序的代码:

/**
*用于XMLHttpRequest(xhr)回调, 根据xhr的readyState
*调用不同的函数
*/
function RequestListener() {
/**当xhr.readyState = 0时调用*/
this.uninitialized = function() {}
/**当xhr.readyState = 1时调用*/
this.loading = function(){}
/**当xhr.readyState = 2时调用*/
this.loaded = function(){}
/**当xhr.readyState = 3时调用*/
this.interactive = function() {}
/**当xhr.readyState = 4时调用*/
this.complete = function(status, statusText, responseText, responseXML){} }
/***RequestListener的子类型,用于扩展complet方法,
*当HTTP(xhr.status)应答状态为200时, 调用handleText和handleXML
*处理应答体;当状态为其它时,调用handleError.
*/

function RequestAdapter() {
	this.complete = function(status, statusText, responseText, responseXML) {
		if(status == 200) {
			this.handleText(responseText);
			this.handleXML(responseXML);
		}else{
			this.handleError(status, statusText);
		}
	}
	this.handleText = function(text) {}
	this.handleXML = function(xml) {}
	this.handleError = function(status, statusText) { 
		alert("Http status: " + status + " , " + statusText); 
	}
}
RequestAdapter.prototype = new RequestListener();
RequestAdapter.prototype.constructor = RequestAdapter;

/**
*封装XMLHttpRequest,回调RequestListener的不同方法处理
*不同的应答状态.对外提供doGet, doPost方法处理不同方式的请求.
*使用例子如下:
* var l = new RequestAdapter();
* l.handleText = function(text) {alert(text);}
* var req = new Request(l);
* req.doGet("hello.jsp");
*/
function Request(l) {
	var xhr = createXhr();
	var listener = l;
	if(!listener){ 
		listener = new RequestListener(); 
	}
	this.setListener = function(l){
		listener = l;
	}
	this.doGet = function(url, asyn) { 
		doProcess("GET", url, null, asyn); 
	}
	this.doPost = function(url, reqBody, asyn) { 
		doProcess("POST", url, reqBody, asyn); 
	}
	function doProcess(method, url, reqBody, asyn) {
		if(asyn == undefined) { 
			asyn = true;
		}
		xhr.open(method, url, asyn);
		if(asyn) { 
			xhr.onreadystatechange = callback; 
		}
		xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
		xhr.send(reqBody);
		if(!asyn) { 
			listener.complete(xhr.status, xhr.statusText, xhr.responseText, xhr.responseXML); 
		}
	}

	function callback() {
		switch(xhr.readyState) {
			case 0: listener.uninitialized(); break;
			case 1: listener.loading(); break;
			case 2: listener.loaded(); break;
			case 3: listener.interactive(); break;
			case 4: listener.complete(xhr.status, xhr.statusText, xhr.responseText, xhr.responseXML);
		}
	}
}

function createXhr() {
	if(window.ActiveXObject) {
		return new ActiveXObject("Microsoft.XMLHTTP");
	}
	else if(window.XMLHttpRequest) {
		return new XMLHttpRequest();
	}
	else{ 
		throw new Error("Does not ajax programming"); 
	}
}