从jQuery中剥离出$.ajax函数

bachue posted @ 2010年12月10日 20:41 in JavaScript with tags ajax javascript js post jquery get getjson getscript load appspot , 7438 阅读

作为开源社区的PHP框架的内置Ajax模块,我们需要一个功能超强大的Ajax函数,作为一个通用函数。Ajax模块是我负责的,这种函数如果真要我写,恐怕以我的能力是很难做到完美的,毕竟Ajax涉及的知识点非常散,还有大量HTTP协议的知识,我并不全懂,还有各种浏览器兼容问题的存在,防不胜防。就算花一个月去做,恐怕也只能做出个可能存在大量隐蔽BUG的作品。于是,我决定直接从开源产品中找,也就是jQuery的$.ajax函数。

我是个信奉极简约的人,不喜欢为了一个$.ajax就把整个jQuery文件全部包含进来,太大了,70多KB的文件,换成56K/bps的猫,全速也至少10秒(一般我不考虑什么gzip压缩,因为不想把它作为偷懒的理由,虽然号称压缩后只有24K),我不能接受这种速度,所以我就决定从中提取,把jQuery中的$.ajax和一些附属的函数剥离出来,单独形成一个ajax函数。

花了半天时间,非常顺利,jQuery源码并不难懂,至少一定比Linux Kernel源码好懂。不过首个版本没有提取所有功能。比方说Cache功能还没有,因为不了解Ajax的Cache究竟是个什么机制,全局Ajax事件没有,因为感觉意义不大,如果要,就必须剥离$.trigger,这个函数不是很小,剥离就可能导致最终文件变大,有点得不偿失。

可能存在Bug,请注意,当需要解析JSON时,本类中没有自带JSON解析函数,请务必提供JSON.parse()函数用于解码。

/*
 * Class: Ajax
 * Author: Bachue Zhou
 * Description: Deliver Ajax module from jquery 
 * Date: 12/08/2010 
 * Depend on: JSON.parse() Must be provided!
 * Version: 0.02
 */

var Ajax;
if(!Ajax||typeof(Ajax)!="object")
{
    Ajax={};
}

Ajax.ajaxSettings={ //init ajax setting
	url: location.href,
	type: "GET",
	contentType: "application/x-www-form-urlencoded",
	processData: true,
	async: true,
	/*
	timeout: 0,
	data: null,
	username: null,
	password: null,
	traditional: false,
	*/
	xhr: function() {
		//return ajax object. must be comparable with fuck ie.
		try{return new XMLHttpRequest();}
		catch(e){try{return new ActiveXObject("Msxml2.XMLHTTP");}
		catch(e){return new ActiveXObject("Microsoft.XMLHTTP");}}
	},
	accepts: {
		xml: "application/xml, text/xml",
		html: "text/html",
		script: "text/javascript, application/javascript",
		json: "application/json, text/javascript",
		text: "text/plain",
		_default: "*/*"
	}
};

Ajax.setup=function( settings ) 
{
	Ajax._extend( Ajax.ajaxSettings, settings );
};

Ajax.send=function(origSettings)
{
	function getType( obj ) 
	{
		return obj === null ? String( obj ) : class2type[ toString.call(obj) ] || "object";
	}
	
	function isFunction( obj )
	{
		return getType(obj) === "function";
	}

	function isArray( obj ) 
	{
		return getType(obj) === "array";
	}
	
	function isEmptyObject( obj )
	{
		for ( var name in obj ) 
		{
			return false;
		}
		return true;
	}
		
	function param(a)
	{
		var s = [],name,
			add = function( key, value ) {
				// If value is a function, invoke it and return its value
				value = isFunction(value) ? value() : value;
				s[ s.length ] = encodeURIComponent(key) + "=" + encodeURIComponent(value);
			};
		
		// If an array was passed in, assume that it is an array of form elements.
		if ( isArray(a) ) 
		{
			// Serialize the form elements
			for(name in a)
			{
				add( name, a[name] );
			}
		}
		else
		{
			// If traditional, encode the "old" way (the way 1.3.2 or older
			// did it), otherwise encode params recursively.
			for ( var prefix in a ) 
			{
				buildParams( prefix, a[prefix], add );
			}
		}

		// Return the resulting serialization
		return s.join("&").replace(r20, "+");
	}
	
	function buildParams( prefix, obj, add )
	{
		if ( isArray(obj) && obj.length )
		{
			// Serialize array item.
			for(var i in obj)
			{
				var v=obj[i];
				if ( rbracket.test( prefix ) ) 
				{
					// Treat each array item as a scalar.
					add( prefix, v );
				} 
				else 
				{
					// If array item is non-scalar (array or object), encode its
					// numeric index to resolve deserialization ambiguity issues.
					// Note that rack (as of 1.0.0) can't currently deserialize
					// nested arrays properly, and attempting to do so may cause
					// a server error. Possible fixes are to modify rack's
					// deserialization algorithm or to provide an option or flag
					// to force array serialization to be shallow.
					buildParams( prefix + "[" + ( typeof v === "object" || isArray(v) ? i : "" ) + "]", v, add );
				}	
			}	
		} 
		else if ( obj !== null && typeof(obj) === "object" ) 
		{
			if ( isEmptyObject( obj ) ) 
			{
				add( prefix, "" );
			} 
			else 
			{
			// Serialize object item.
				for(var k in obj)
				{
					var v=obj[k];
					buildParams( prefix + "[" + k + "]", v, add );
				}
			}
		} 
		else 
		{
			// Serialize scalar item.
			add( prefix, obj );
		}
	}

	function globalEval( data )
	{
		if ( data && rnotwhite.test(data) ) {
			// Inspired by code by Andrea Giammarchi
			// http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html
			var head = document.getElementsByTagName("head")[0] || document.documentElement,
				script = document.createElement("script");

			script.type = "text/javascript";

			if ( scriptEval ) {
				script.appendChild( document.createTextNode( data ) );
			} else {
				script.text = data;
			}

			// Use insertBefore instead of appendChild to circumvent an IE6 bug.
			// This arises when a base node is used (#2709).
			head.insertBefore( script, head.firstChild );
			head.removeChild( script );
		}
	}
	
	//return current time
	function now()
	{
		return (new Date()).getTime();
	}
	
	function handleError( s, xhr, status, e ) {
		// If a local callback was specified, fire it
		if ( s.error ) {
			s.error.call( s.context, xhr, status, e );
		}
	}
	
	function handleSuccess( s, xhr, status, data )
	{
		// If a local callback was specified, fire it and pass it the data
		if ( s.success )
		{
			s.success.call( s.context, data, status, xhr );
		}
	}

	function handleComplete( s, xhr, status ) 
	{
		// Process result
		if ( s.complete )
		{
			s.complete.call( s.context, xhr, status );
		}
	}
	
	// Determines if an XMLHttpRequest was successful or not
	function httpSuccess( xhr )
	{
		try {
			// IE error sometimes returns 1223 when it should be 204 so treat it as success, see #1450
			return !xhr.status && location.protocol === "file:" ||
				xhr.status >= 200 && xhr.status < 300 ||
				xhr.status === 304 || xhr.status === 1223;
		} catch(e) {}

		return false;
	}

	// Determines if an XMLHttpRequest returns NotModified
	function httpNotModified( xhr, url ) 
	{
		var lastModified = xhr.getResponseHeader("Last-Modified"),
			etag = xhr.getResponseHeader("Etag");

		if ( lastModified ) {
			lastModified[url] = lastModified;
		}

		if ( etag ) {
			etag[url] = etag;
		}

		return xhr.status === 304;
	}

	function httpData( xhr, type, s ) 
	{
		var ct = xhr.getResponseHeader("content-type") || "",
			xml = type === "xml" || !type && ct.indexOf("xml") >= 0,
			data = xml ? xhr.responseXML : xhr.responseText;

		if ( xml && data.documentElement.nodeName === "parsererror" ) 
		{
			throw "parsererror";
		}

		// Allow a pre-filtering function to sanitize the response
		// s is checked to keep backwards compatibility
		if ( s && s.dataFilter ) 
		{
			data = s.dataFilter( data, type );
		}

		// The filter can actually parse the response
		if ( typeof data === "string" ) 
		{
			// Get the JavaScript object, if JSON is used.
			if ( type === "json" || !type && ct.indexOf("json") >= 0 ) 
			{
				data = JSON.parse( data );
			}
			else if ( type === "script" || !type && ct.indexOf("javascript") >= 0 ) 
			{
				globalEval( data );
			}
		}

		return data;
	}
	
	function noop()
	{
	}
	
	var root = document.documentElement,
		script = document.createElement("script"),
		id = "script" + now(),
		scriptEval=false;
	
	script.type = "text/javascript";
	try {
		script.appendChild( document.createTextNode( "window." + id + "=1;" ) );
	} catch(e) {}

	root.insertBefore( script, root.firstChild );

	// Make sure that the execution of code works by injecting a script
	// tag with appendChild/createTextNode
	// (IE doesn't support this, fails, and uses .text instead)
	if ( window[ id ] ) {
		scriptEval = true;
		delete window[ id ];
	}
	root.removeChild( script );
	
	var class2type = {
			"[object Array]":"array",
			"[object Boolean]":"boolean",
			"[object Date]":"date",
			"[object Function]":"function",
			"[object Number]":"number",
			"[object Object]":"object",
			"[object RegExp]":"regexp",
			"[object String]":"string"
			},
				
	jsc = now(),
	rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
	rselectTextarea = /^(?:select|textarea)/i,
	rinput = /^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,
	rnoContent = /^(?:GET|HEAD)$/,
	rbracket = /\[\]$/,
	jsre = /\=\?(&|$)/,
	rquery = /\?/,
	rts = /([?&])_=[^&]*/,
	rurl = /^(\w+:)?\/\/([^\/?#]+)/,
	r20 = /%20/g,
	rhash = /#.*$/,
	rnotwhite = /\S/,
	
	lastModified={},
	etag={},

	s=Ajax._extend(Ajax.ajaxSettings, origSettings),
		jsonp, status, data, type = s.type.toUpperCase(), noContent = rnoContent.test(type);
	
	//erase some meaningless symbol
	s.url = s.url.replace(rhash,"");
	
	// Use original (not extended) context object if it was provided
	s.context = origSettings && origSettings.context !== null ? origSettings.context : s;
	
	// convert data if not already a string
	if ( s.data && s.processData && typeof s.data !== "string" ) 
	{
		s.data = param( s.data );
	}
	
	if ( s.dataType === "jsonp" )
	{
		if ( type === "GET" )
		{
			//test whether there is '=?' in URL, if not, splice it
			if ( !jsre.test( s.url ) ) 
			{
				s.url += (rquery.test( s.url ) ? "&" : "?") + (s.jsonp || "callback") + "=?";
			}
		} 
		//test data
		else if ( !s.data || !jsre.test(s.data) ) 
		{
			s.data = (s.data ? s.data + "&" : "") + (s.jsonp || "callback") + "=?";
		}
		//set datatype to json
		s.dataType = "json";
	}
	
	if ( s.dataType === "json" && (s.data && jsre.test(s.data) || jsre.test(s.url)) ) 
	{
		jsonp = s.jsonpCallback || ("jsonp" + jsc++);

		// Replace the =? sequence both in the query string and the data
		if ( s.data )
		{
			s.data = (s.data + "").replace(jsre, "=" + jsonp + "$1");
		}

		s.url = s.url.replace(jsre, "=" + jsonp + "$1");

		// We need to make sure
		// that a JSONP style response is executed properly
		s.dataType = "script";

		// Handle JSONP-style loading
		var customJsonp = window[ jsonp ];

		window[ jsonp ] = function( tmp ) 
		{
			if ( isFunction( customJsonp ) ) 
			{
				customJsonp( tmp );
			} 
			else 
			{
				// Garbage collect
				window[ jsonp ] = undefined;

				try
				{
					delete window[ jsonp ];
				}
				catch( jsonpError ){}
			}

			data = tmp;
			handleSuccess( s, xhr, status, data );
			handleComplete( s, xhr, status, data );
			
			if ( head ) 
			{
				head.removeChild( script );
			}
		};
	}
	
	// If data is available, append data to url for GET/HEAD requests
	if ( s.data && noContent )
	{
		s.url += (rquery.test(s.url) ? "&" : "?") + s.data;
	}
	
	// Matches an absolute URL, and saves the domain
	var parts = rurl.exec( s.url ),
		remote = parts && (parts[1] && parts[1].toLowerCase() !== location.protocol || parts[2].toLowerCase() !== location.host);
	
	if ( s.dataType === "script" && type === "GET" && remote )
	{
		var head = document.getElementsByTagName("head")[0] || document.documentElement;
		var script = document.createElement("script");
		if ( s.scriptCharset )
		{
			script.charset = s.scriptCharset;
		}
		script.src = s.url;

		// Handle Script loading
		if ( !jsonp )
		{
			var done = false;

			// Attach handlers for all browsers
			script.onload = script.onreadystatechange = function() 
			{
				if ( !done && (!this.readyState ||
						this.readyState === "loaded" || this.readyState === "complete") ) 
				{
					done = true;
					handleSuccess( s, xhr, status, data );
					handleComplete( s, xhr, status, data );

					// Handle memory leak in IE
					script.onload = script.onreadystatechange = null;
					if ( head && script.parentNode )
					{
						head.removeChild( script );
					}
				}
			};
		}

		// Use insertBefore instead of appendChild  to circumvent an IE6 bug.
		// This arises when a base node is used (#2709 and #4378).
		head.insertBefore( script, head.firstChild );

		// We handle everything using the script element injection
		return undefined;
	}
	
	var requestDone = false;

	// Create the request object
	var xhr = s.xhr();

	if ( !xhr ) 
	{
		return;
	}

	// Open the socket
	// Passing null username, generates a login popup on Opera (#2865)
	if ( s.username )
	{
		xhr.open(type, s.url, s.async, s.username, s.password);
	} 
	else
	{
		xhr.open(type, s.url, s.async);
	}
	
	// Need an extra try/catch for cross domain requests in Firefox 3
	try
	{
		// Set content-type if data specified and content-body is valid for this type
		if ( (s.data !== null && !noContent) || (origSettings && origSettings.contentType) ) 
		{
			xhr.setRequestHeader("Content-Type", s.contentType);
		}

		// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
		if ( s.ifModified ) 
		{
			if ( lastModified[s.url] )
			{
				xhr.setRequestHeader("If-Modified-Since", lastModified[s.url]);
			}

			if ( etag[s.url] )
			{
				xhr.setRequestHeader("If-None-Match", etag[s.url]);
			}
		}

		// Set header so the called script knows that it's an XMLHttpRequest
		// Only send the header if it's not a remote XHR
		if ( !remote ) 
		{
			xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
		}

		// Set the Accepts header for the server, depending on the dataType
		xhr.setRequestHeader("Accept", s.dataType && s.accepts[ s.dataType ] ?
			s.accepts[ s.dataType ] + ", */*; q=0.01" :
			s.accepts._default
		);
	} catch( headerError ) {}
	
	// Allow custom headers/mimetypes and early abort
	if ( s.beforeSend && s.beforeSend.call(s.context, xhr, s) === false ) 
	{
		// close opended socket
		xhr.abort();
		return false;
	}
	
	var onreadystatechange = xhr.onreadystatechange = function( isTimeout ) 
	{
		// The request was aborted
		if ( !xhr || xhr.readyState === 0 || isTimeout === "abort" )
		{
			// Opera doesn't call onreadystatechange before this point
			// so we simulate the call
			if ( !requestDone )
			{
				handleComplete( s, xhr, status, data );
			}

			requestDone = true;
			if ( xhr )
			{
				xhr.onreadystatechange = noop;
			}

		// The transfer is complete and the data is available, or the request timed out
		} 
		else if ( !requestDone && xhr && (xhr.readyState === 4 || isTimeout === "timeout") ) 
		{
			requestDone = true;
			xhr.onreadystatechange = noop;

			status = isTimeout === "timeout" ?
				"timeout" :
				!httpSuccess( xhr ) ?//really success?
					"error" :
					s.ifModified && httpNotModified( xhr, s.url ) ?
						"notmodified" :
						"success";

			var errMsg;

			if ( status === "success" ) 
			{
				// Watch for, and catch, XML document parse errors
				try 
				{
					// process the data (runs the xml through httpData regardless of callback)
					data = httpData( xhr, s.dataType, s );//judge responseXML or responseText
				} 
				catch( parserError ) 
				{
					status = "parsererror";
					errMsg = parserError;
				}
			}

			// Make sure that the request was successful or notmodified
			if ( status === "success" || status === "notmodified" ) 
			{
				// JSONP handles its own success callback
				if ( !jsonp ) 
				{
					handleSuccess( s, xhr, status, data );
				}
			} 
			else 
			{
				handleError( s, xhr, status, errMsg );
			}

			// Fire the complete handlers
			if ( !jsonp )
			{
				handleComplete( s, xhr, status, data );
			}

			if ( isTimeout === "timeout" ) {
				xhr.abort();
			}

			// Stop memory leaks
			if ( s.async ) {
				xhr = null;
			}
		}
	};

	// Override the abort handler, if we can (IE 6 doesn't allow it, but that's OK)
	// Opera doesn't fire onreadystatechange at all on abort
	try 
	{
		var oldAbort = xhr.abort;
		xhr.abort = function() 
		{
			if ( xhr ) {
				// oldAbort has no call property in IE7 so
				// just do it this way, which works in all
				// browsers
				Function.prototype.call.call( oldAbort, xhr );
			}

			onreadystatechange( "abort" );
		};
	} catch( abortError ) {}

	// Timeout checker
	if ( s.async && s.timeout > 0 ) 
	{
		setTimeout(function()
		{
			// Check to see if the request is still happening
			if ( xhr && !requestDone ) 
			{
				onreadystatechange( "timeout" );
			}
		}, s.timeout);
	}

	// Send the data
	try {
		xhr.send( noContent || s.data === null ? null : s.data );

	} 
	catch( sendError ) 
	{
		handleError( s, xhr, null, sendError );

		// Fire the complete handlers
		handleComplete( s, xhr, status, data );
	}

	// firefox 1.5 doesn't fire statechange for sync requests
	if ( !s.async )
	{
		onreadystatechange();
	}

	// return XMLHttpRequest to allow aborting the request etc.
	return xhr;
};

//merge multi objects into the first object and return
Ajax._extend=function ()
{
	var target = arguments[0] || {},i,length = arguments.length,options,src,copy,clone,name;
	for(i=1;i<length;++i)
	{
		// Only deal with non-null/undefined values
		if ((options=arguments[i])!==null)
		{
			// Extend the base object
			for(name in options)
			{
				src=target[ name ];
				copy=options[ name ];
				
				// Prevent never-ending loop
				if (target===copy)
				{
					continue;
				}
				
				// Recurse if we're merging objects
				if(typeof(copy)=="object")
				{
					clone=(src && typeof(src)=="object"?src:{});
					target[name]=Ajax._extend(clone,copy);
				}
				// Don't bring in undefined values
				else if(copy !== undefined)
				{
					target[name ] = copy;
				}
			}
		}
	}
	// Return the modified object
	return target;
};

最终文件共8.4K,56K/bps猫只需1秒多一点,显然是可以接受的。min版本下载

这个函数其实还有一些附带函数,比方说post,get,getScript,getJSON,load之类的,我全让我的组员去完成了,其实非常简单。至于Cache机制的剥离,也让他们作为一个附加题去做了,我寝室最近网速很糟,笔记本电脑有线网卡损坏,总之RP不够,收集这方面的资料很有困难。

最后缅怀被墙的AppSpot,上推再一次变的困难,希望有个男人能勇敢的站出来为此事负责!

java程序员 说:
2011年7月02日 19:12

你好!我也是一个程序员,但是对js一点也不太懂,最近我也在做一个普通的网站,你能不能把你们剥离的成果给我一份啊。

java程序员 说:
2011年7月02日 19:15

我的email是422053362@qq.com.我们能不能多交流一下?

Avatar_small
bachue 说:
2011年7月05日 12:03

@java程序员: 真糟糕,我的组员没有按照我的要求完成任务。因此所有代码均已经列在上面,你可以任意修改使用这些代码。我的Gmail是bachue.shu#gmail.com,你可以发邮件给我。

有意义 说:
2013年1月26日 21:02

还是谷歌好啊,我百度了半天也没找到如何剥离jquery的文章,更比直接找剥离好ajax的了。。
使用别人的研究成果,是站在巨人肩上的表现。。感谢老大了。

Avatar_small
bachue 说:
2013年1月27日 09:39

@有意义: 天哪 这是我几年前写的东西啊 AJAX的实现后来jQuery好像完全重写了 恐怕你要重新来了 话说现在其实时代也改变了 你整个jQuery放进去 再加上所有基于jQuery的库 加个min 加个gzip 其实也不怎么慢 而且只要下载一次 以后全部依靠缓存 没必要搞什么剥离了

MPBSE HSSC Model Pap 说:
2022年8月24日 15:48

MP Board HSSC 12th Question Paper 2023, The Board Of Higher Secondary Education Madhya Pradesh MPBSE. It conducts three board examinations every year; the Middle School Exam for Standard VIII Which is nothing but 12th class School, MPBSE HSSC Model Paper 2023 PDF High School Certificate Examination for standard XII Also Knows as 12th Class and the Higher Secondary School Certificate HSSC Exam for standard XII Also Known as 12th class Or intermediate, which is a school-leaving examination. Soon, it will release the MP Board 12th New Question Paper 2023. Candidates can check the Question Paper and download from the direct link which is provided below.

civaget 说:
2023年12月14日 03:19

Let's uncover the secrets of 구글 상위노출 together, as we explore strategies and techniques for online dominance.

civaget 说:
2023年12月17日 23:12

강남휴게텔 is a hidden gem in Gangnam. The sauna and massages provide unparalleled relaxation. Don't miss out on this extraordinary experience.

civaget 说:
2023年12月21日 21:30 해외축구중계 takes soccer fans on a global adventure.
civaget 说:
2023年12月23日 15:22

I love the convenience of 천안출장마사지. It's a fantastic way to relax on your terms.

civaget 说:
2024年1月15日 21:57

You really should be a part of a contest for just one of the greatest blogs on the net. I am going to suggest this great site! ufabetคืนยอดเสีย


登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter