1.基本简介
ajax是一种在浏览器异步请求网络并刷新界面元素的技术方案。ajax全名为Asynchronous Javascript And XML,即为异步的js和xml。
同时,ajax不是jQuery的技术,而是jQuery提供了一种ajax的建单的解决方案,除了使用jQuery做ajax请求之外,我们亦可以使用axios,fetch,或者原生的XMLHTTPRequest对象
本文集中探讨jQuery的使用方式
ajax的基本使用方式,最简单的ajax只需要通过options对象传入一个url参数即可(甚至url也可以不传入)
$.ajax({url : "http://localhost:8080/jsonp"})
url即为ajax请求的服务器地址参数,除了url之外,ajax还有其他几个常用的参数:
- method/type : 请求网络的方法,默认为get, 取值大致有 get, post, put, delete, head等
- async : 异步支持,默认ajax就是异步的,但是也可以将这个值设置为false转为同步请求
- data : ajax传输的数据,数据以键值对的形式传输,可以放在url的后面,也可以是放在http请求的正文中
- timeout: 超时时间,超出时间请求则在会被打断(浏览器方面)
- success/error:请求成功或者失败的回调函数
2.异步
$.ajax({ url : “http://localhost:8080/jsonp”, async : true}) // 添加async参数即为异步请求,默认为 async : true
由于浏览器的api直接支持请求可以接受同步和异步的,于是只要将异步的参数传入到xhr.open函数中即可
xhr.open(
options.type,
options.url,
options.async,
options.username,
options.password
);
3.超时失败
使用ajax时设置超时时间,如果请求的时间超过这个数值就直接失败
$.ajax({ url : "http://localhost:8080/jsonp", timeout : 8000})
// 只需要传入timeout参数即可实现超时失败,默认为永远不是失败
// Timeout
if ( s.async && s.timeout > 0 ) {
timeoutTimer = window.setTimeout( function() {
jqXHR.abort( "timeout" );
}, s.timeout );
}
- window.setTimeout会创建一个延迟执行的事件
- 在这个timeout时间里,如果请求没有完成,那么延迟任务会使用xhr.abort函数打断请求,则执行失败的回调函数
- 而在这个timeout时间之内,如果请求已经完成,那么将会通过timeoutTimer这个编号将延迟任务移除
4. ContentType文本类型
浏览器发送哦给服务器的数据类型
在使用ajax的时候,通常就是将传输的数据放在url后面,于是 contentType 默认的取值 “application/x-www-form-urlencoded"
同时如果是传输数据是json格式,也可以将 contentType 设置为json
contentType的取值有
- application/x-www-form-urlencoded 默认值,
- multipart/form-data 上传文件时用,
- application/json 用来告诉服务端消息主体是序列化后的 JSON 字符串
- text/xml 忽略xml头所指定编码格式而默认采用us-ascii编码
- text/plain 无格式正文,文本
- text/html html格式的正文
案例:使用jquery.ajax上传文件:
//js代码:
$(function () {
$("#upload").click(function () {
$.ajax({
url:"http://localhost:8080/upload",
type:"post",
data:new FormData($("#uploadForm")[0]),
contentType:false // 因为在form上设置了contentType,于是这里就要设置为false
});
});
});
<!--html代码:-->
<form id="uploadForm" enctype="multipart/form-data">
<input id="file" type="file" name="file"/>
<button id="upload" type="button">upload</button>
</form>
//java代码
@PostMapping("/upload")
public String upload(StandardMultipartHttpServletRequest request) {
var map = request.getMultiFileMap();
map.forEach((k,v) -> System.out.println(k + "" + v));
return ApiUtils.empty();
}
5.dataType数据类型
浏览器期待服务器返回的数据类型,如果明显地指明这个值,那么jQuery会根据数据返回的数据进行判断
这个参数的取值可以有:
- xml: 返回 XML 文档,可用 jQuery 处理。
- html: 返回纯文本 HTML 信息;包含的 script 标签会在插入 dom 时执行。
- script: 返回纯文本 JavaScript 代码。不会自动缓存结果。除非设置了 “cache”
参数。注意:在远程请求时(不在同一个域下),所有 POST 请求都将转为 GET 请求。(因为将使用 DOM 的
script标签来加载) - json: 返回 JSON 数据 。
- jsonp: JSONP 格式。使用 JSONP 形式调用函数时,如 “myurl?callback=?” jQuery 将自动替换
? 为正确的函数名,以执行回调函数。 - text: 返回纯文本字符串
案例:使用dataType字段实现跨域请求
基本方式
// java代码:
@RequestMapping("/jsonp")
public String test(){
var dataMap = Map.of("name","lin");
var jsonpString = "callback(%s)";
return String.format(jsonpString, JSONObject.toJSON(dataMap));
}
//js代码:
<script type="application/javascript">
function callback(data) {
console.log(data.name)
}
</script>
<script type="application/javascript" src="http://localhost:8080/jsonp"></script>
jsonp能够跨域的原因是script标签能够跨域,因此直接在script请求上面没有限制,另一方面,js上面会将从服务器返回的字符串作为一个函数调用来执行,于是就调用了页面上的callback回调代码
jquery.ajax方式
$.ajax({
url: "http://localhost:8080/jsonp",
method: "get",
dataType: "jsonp", //指定接受的数据类型为jsonp
jsonpCallback: "callback", //需要指定服务器返回的回调函数名称
success: function(response){
console.log("ajax:"+response.name)
}
})
–jquery实现代码:
if ( s.crossDomain ) {
var script, callback;
return {
send: function( _, complete ) {
script = jQuery( "<script>" ).prop( {
charset: s.scriptCharset,
src: s.url
} ).on("load error",
callback = function( evt ) {
script.remove();
callback = null;
if ( evt ) {
complete( evt.type === "error" ? 404 : 200, evt.type );
}
});
// Use native DOM manipulation to avoid our domManip AJAX trickery
document.head.appendChild( script[ 0 ] );
},
abort: function() {
if ( callback ) {
callback();
}
}
};
}
- 如果是跨域时,jQuery首先在window类中创建一个函数名为jsonpCallback参数值的函数,
- 然后通过创建一个script标签来请求数据,
- 得到返回结果之后,浏览器就会取调用window类中的回调函数,实现跨域请求
6.回调函数
beforeSend
这个回调函数在ajax请求之前执行,同时会传入 xhr的原生请求对象
/ Allow custom headers/mimetypes and early abort
if ( s.beforeSend &&
( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) {
// Abort if not done already and return
return jqXHR.abort();
}
在jQyuery内部这个函数是这样调用的,所有如果想在这个beforeSend回调函数中打断ajax的请求,那么可以在回调函数中返回false
complate
在请求结束之后执行,不论这个请求是失败还是成功,同时从这个方法中传入的参数依次是:ajax的请求对象 和 请求的完成状态
代码中会将complete函数加入了一个函数的列表,
completeDeferred.add( s.complete );
于是在请求完成之后会发射 completeDeferred 中的所有方法
// Complete
completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );
success/error
在请求 成功/失败 之后调用,如果 失败/成功 不会调用这个方法,同时这个方法传入的参数不是一个xhr请求对象,而是 服务器返回的数据的对象 和 请求的状态
在jQuery对成功和失败的回调方式上,首先现在的jQuery的xhr请求对象被初始化成为一个Promise对象了
// Attach deferreds
deferred.promise( jqXHR );
然后jQuery将 success/error 两个回调加入到 jqXHR 对象中 的 promise方法中
// Install callbacks on deferreds
completeDeferred.add( s.complete );
jqXHR.done( s.success );
jqXHR.fail( s.error );
但是计算把这些回调方法加入到jqXHR的done中也不会执行,而是在done函数执行时才会选择解析或者拒绝函数调用
// Success/Error
if ( isSuccess ) {
deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
} else {
deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
}
7.promise机制
在使用jQuery时,一个非常重要的功能就是ajax。但是jQuery的ajax一个很常用的方式会让代码可读性很差,那就是嵌套ajax使用。
比如这样的代码:
$.ajax({
url:"http://localhost:8080/jsonp",
dataType:"jsonp",
jsonpCallback:"callback",
success:function (response) {
console.log(1 + ":" + response);
$.ajax({
url:"http://localhost:8080/jsonp",
dataType:"jsonp",
jsonpCallback:"callback",
success:function (response) {
console.log(2 + ":" + response);
$.ajax({
url:"http://localhost:8080/jsonp",
dataType:"jsonp",
jsonpCallback:"callback",
success:function (response) {
console.log(3 + ":" + response);
}
})
}
})
}
});
这个ajax请求被嵌套了两次次,主要原因就是因为在下一次的ajax请求的数据依赖于上一次ajax的数据(实例中并没有体现)。因此就在回调中又调用了两次。
function ajaxDemo(success){
return $.ajax({
url: "http://localhost:8080/jsonp",
dataType: "jsonp",
jsonpCallback: "callback",
success: success
})
}
ajaxDemo(function (response) {
console.log(1 + ":" + response);
ajaxDemo(function (response) {
console.log(2 + ":" + response);
ajaxDemo(function (response) {
console.log(3 + ":" + response);
})
})
});
使用这样的方式可以稍微让代码优雅一点,但是嵌套在代码中仍然解决可能无穷无尽的嵌套代码块。于是jQuery就有引入了一种新的编程方式,使用promise进行ajax请求。比如这样:
function ajaxDemo(){
return $.ajax({
url: "http://localhost:8080/jsonp",
dataType: "jsonp",
jsonpCallback: "callback",
})
}
ajaxDemo().then((response) => {
console.log(1 + ":" + response);
return ajaxDemo()
}).then((response) => {
console.log(2 + ":" + response);
return ajaxDemo()
}).then((response) => {
console.log(3 + ":" + response);
});
这样的代码是不是看起来清爽了很多,对于每一次依赖于上一次的ajax调用的请求,只需要将其写成一个链式的调用而不是使用嵌套回调函数来实现具有数据依赖的多个ajax调用。其中promise保证了只有当前的ajax的回调函数执行完成之后才会进行下一次的ajax请求。
那么jQuery是如何实现这种调用接口方式的呢?其实就依赖于promise的机制,promise有很多中实现,jQurey也单独实现了自己的promise,而其中一个最关键的对象就是 Deferred 对象。
Defeffed对象的作用,顾名思义就是延迟地执行一个任务。
def = $.Deferred()
pro = {}
def.promise(pro)
pro.done(function(){console.log(123)})
deff.resolve()
def就是一个Deferred对象,而pro则是一个被def转化的promise对象,在对pro使用了promise函数,def就会增加几个方法:done, then, fail等函数。
done/then/fail函数会被添加到promise对象中,但是并不一定会被马上执行。如果pro对象已经执行过resolve()函数,那么这些任务在被添加之后就会马上触发。而如果resolve函数没有被调用,那么这些任务就会等待resolve调用而全部触发。
在jQuery的实现中
deferred.promise( jqXHR );
jQuery将jqXHR请求对象转换成了一个promise对象,然后将complete,success,error等回调传入到promise对象中
// Install callbacks on deferreds
completeDeferred.add( s.complete );
jqXHR.done( s.success );
jqXHR.fail( s.error );
随后在jq的执行流程中,当有结果显示完成或者失败之后,就会去触发成功或者失败的回调函数
// Success/Error
if ( isSuccess ) {
deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
} else {
deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
}
同时,在jquery的ajax函数调用之后,返回的就是一个Deferred对象,于是链式地使用ajax就可以成立。
8.核心方法
// 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";
// If we have data, let's convert it
} 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" );
}
}
}