fofo

老生常谈的ajax

2017-07-29

ajax真是个老生常谈的东西,js里面的一个小东西,作为后端程序员,仅使用层面来说,ajax还是挺简单实用的。这里只能简单写写,至于XMLHttpRequest对象封装还是算了吧…
本篇博文就做两件事:

  1. 写写原生的ajax
  2. 看看Jquery里的ajax实现

原生ajax写法

function createXMLHttpRequsetObj() {
var XMLHttpRequestObj;
if (window.XMLHttpRequest) {
XMLHttpRequestObj = new XMLHttpRequest;
} else if (window.ActiveXObject) {
var activexName = [ "MSXML2.XMLHTTP", "Microsoft.XMLHTTP" ];
for (var i = 0; i < activexName.length; i++) {
try{
XMLHttpRequestObj = new ActiveXObject[activexName[i]];
if (XMLHttpRequestObj) {
break;
}
} catch (e) {
console.log(e.message);
}
}
}
}
var ajax = {
get: function(url, fn) {
var obj = createXMLHttpRequsetObj();
obj.open('GET', url, true);
obj.onreadystatechange = function() {
if(obj.readyState = 4 && obj.status ==200 || obj.status == 304) {
fn.call(this, obj.responseText);
}
}
obj.send();
},
post: function(url, data, fn) {
var obj = createXMLHttpRequsetObj();
obj.open('POST', url, true);
obj.setRequestHeader
(
"Content-type",
"application/x-www-from-urlencoded"
);
obj.onreadystatechange = function() {
if(obj.readyState = 4 && obj.status ==200 || obj.status == 304) {
fn.call(this, obj.responseText);
}
}
obj.send(data);
}
}

jQuery里的ajax实现

Jq里的ajax实现就复杂多了,先看看jQuery.get()jQuery.post()这两个方法:

jQuery.each( [ "get", "post" ], function( i, method ) {
jQuery[ method ] = function( url, data, callback, type ) {
// Shift arguments if data argument was omitted
if ( jQuery.isFunction( data ) ) {
type = type || callback;
callback = data;
data = undefined;
}
// The url can be an options object (which then must have .url)
return jQuery.ajax( jQuery.extend( {
url: url,
type: method,
dataType: type,
data: data,
success: callback
}, jQuery.isPlainObject( url ) && url ) );
};
} );

其实jQuery.get()jQuery.post()区别仅在于传给jQuery.ajax的参数对象里面的type字段不同。
再看ajax的主函数:

// Main method
ajax: function( url, options ) {
// If url is an object, simulate pre-1.5 signature
if ( typeof url === "object" ) {
options = url;
url = undefined;
}
// Force options to be an object
options = options || {};

通常我们使用jQuery.ajax()都会向里面传一个{ setting }对象,这里就给了判断。后面内容都是对传入的对象进行处理。看看回调函数处理,jQuery里有三个供回调的构造函数:

  • jqXHR.fail(function(jqXHR, textStatus, errorThrown) {});
  • jqXHR.done(function(data, textStatus, jqXHR) {});
  • jqXHR.always(function(data|jqXHR, textStatus, jqXHR|errorThrown) {});
// Install callbacks on deferreds
completeDeferred.add( s.complete );
jqXHR.done( s.success );
jqXHR.fail( s.error );

新的写法已经不是在setting对象里面写回调函数,而是在后面连缀使用$.ajax({…}).done(fn)$.ajax({…}).fail(fn)$.ajax({…}).always(fn)
继续看,前面对jqXHR对象进行了各种配置,而send步骤则在transport这里完成。

transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );
// If no transport, we auto-abort
if ( !transport ) {
done( -1, "No Transport" );
} else {
jqXHR.readyState = 1;
// Send global event
if ( fireGlobals ) {
globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
}
// If request was aborted inside ajaxSend, stop there
if ( completed ) {
return jqXHR;
}
// Timeout
if ( s.async && s.timeout > 0 ) {
timeoutTimer = window.setTimeout( function() {
jqXHR.abort( "timeout" );
}, s.timeout );
}
try {
completed = false;
transport.send( requestHeaders, done );
} catch ( e ) {
// Rethrow post-completion exceptions
if ( completed ) {
throw e;
}
// Propagate others as results
done( -1, e );
}
}

这里先通过这个inspectPrefiltersOrTransports长长的函数做send的准备工作,后面再通过transport.send( requestHeaders, done );真正向服务器发送数据。
这里得先了解下ajax的readyState状态:

  • 0 - (未初始化)还没有调用send()方法
  • 1 - (载入)已调用send()方法,正在发送请求
  • 2 - (载入完成)send()方法执行完成,已经接收到全部响应内容
  • 3 - (交互)正在解析响应内容
  • 4 - (完成)响应内容解析完成,可以在客户端调用了

在判断存在transport对象后就设定jqXHR.readyState = 1;,说明前面那个长长的函数里已经在做send准备了,真正完成send操作则是调用transport.send()的时候。
这里还有个done回调函数,这个done跟jqXHR.done是不一样的。jqXHR.done其实是通过deferred.promise(jqXHR)实现promise接口,获取promise有限的方法和属性。

// Attach deferreds
deferred.promise( jqXHR );

transport.send( requestHeaders, done );中的回调函数done则是负责处理send之后获取服务器返回的数据时候的逻辑。

// Callback for when everything is done
function done( status, nativeStatusText, responses, headers ) {
var isSuccess, success, error, response, modified,
statusText = nativeStatusText;
// Ignore repeat invocations
if ( completed ) {
return;
}
completed = true;
// Clear timeout if it exists
if ( timeoutTimer ) {
window.clearTimeout( timeoutTimer );
}
// Dereference transport for early garbage collection
// (no matter how long the jqXHR object will be used)
transport = undefined;
// Cache response headers
responseHeadersString = headers || "";
// Set readyState
jqXHR.readyState = status > 0 ? 4 : 0;
// Determine if successful
isSuccess = status >= 200 && status < 300 || status === 304;
// Get response data
if ( responses ) {
response = ajaxHandleResponses( s, jqXHR, responses );
}
// Convert no matter what (that way responseXXX fields are always set)
response = ajaxConvert( s, response, jqXHR, isSuccess );
// If successful, handle type chaining
if ( isSuccess ) {
// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
if ( s.ifModified ) {
modified = jqXHR.getResponseHeader( "Last-Modified" );
if ( modified ) {
jQuery.lastModified[ cacheURL ] = modified;
}
modified = jqXHR.getResponseHeader( "etag" );
if ( modified ) {
jQuery.etag[ cacheURL ] = modified;
}
}
// if no content
if ( status === 204 || s.type === "HEAD" ) {
statusText = "nocontent";
// if not modified
} else if ( status === 304 ) {
statusText = "notmodified";
} else {
statusText = response.state;
success = response.data;
error = response.error;
isSuccess = !error;
}
} else {
// Extract error from statusText and normalize for non-aborts
error = statusText;
if ( status || !statusText ) {
statusText = "error";
if ( status < 0 ) {
status = 0;
}
}
}
// Set data for the fake xhr object
jqXHR.status = status;
jqXHR.statusText = ( nativeStatusText || statusText ) + "";
// Success/Error
if ( isSuccess ) {
deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
} else {
deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
}
// Status-dependent callbacks
jqXHR.statusCode( statusCode );
statusCode = undefined;
if ( fireGlobals ) {
globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError",
[ jqXHR, s, isSuccess ? success : error ] );
}
// Complete
completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );
if ( fireGlobals ) {
globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
// Handle the global AJAX counter
if ( !( --jQuery.active ) ) {
jQuery.event.trigger( "ajaxStop" );
}
}
}
return jqXHR;
},

可以看到:

  • 在done方法中通过resolveWith,rejectWith来触发success,error事件,通过fireWith来触发compelete事件。
  • state在send调用之前为1,在done方法调用的时候设置为2,默认为0。所以2表示已经回调成功了。

就写到这,如果工作中有用到复杂的ajax场景,再记。

Tags: ajax
使用支付宝打赏
使用微信打赏

若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏

扫描二维码,分享此文章