对ajax回调函数的研究
1.1开发中遇到的问题
最近开发中我和同事都碰到这样的问题,我们使用jQuery的ajax方法做服务端的校验,在success方法里将验证结果存储到一个js的公共变量或者是页面里的隐藏域,接下来的代码我们会根据这个公共的js变量或者是这个隐藏域里的值判断下一步的操作,但是这样做的结果很让人失望,我们发现js公共变量的值或者是隐藏域的值并没有改变,从而导致我们下面的代码无法正常运行。下面我模拟这个问题产生的代码,代码如下:
callback.js:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>回调函数 CallBack Function Study</title>
</head>
<script type="text/javascript" src="jquery-1.7.1.js"></script>
<body>
<form>
<label for="txt">
文本框:
</label>
<input type="text" id="txt" name="txt" size="32"/>
<input type="button" id='btn01' name='btn01' value='BUTTON01'/>
</form>
</body>
</html>
<script type="text/javascript">
var outerdata = '00';
$(document).ready(function(){
$('#txt').val('000000');//给文本框初始值
$('#btn01').bind('click',function(){
$.ajax({
type: "POST",
url: "<%=request.getContextPath() %>/vumssmer/vmerservice!studyCallBack.do",
data:'',
success:function(msg){
console.log('msg.vflag:' + msg.vflag);
console.log('msg.vmsg:' + msg.vmsg);
$('#txt').val(msg.vflag);
outerdata = msg.vflag;
}
});
if (outerdata == 'true'){
console.log('文本框内容是:' + $('#txt').val());
console.log('公共变量的值是:' + outerdata);
console.log('yes');
}else{
console.log('文本框内容是:' + $('#txt').val());
console.log('公共变量的值是:' + outerdata);
console.log('no');
}
});
});
</script>
java 代码
public String studyCallBack() throws Exception{
this.vflag = "true";
this.vmsg = "Number:9999999";
return "validateServerBack";
}
注意:console.log方法只有在firebug里使用才有效】
执行结果是,如图1-1:

图1-1
服务端我设定的返回值vflag:true,vmsg:Number:9999999,success方法打印的结果是正确,但是接下来的代码却执行错误了。
以上就是我们在开发过程中遇到的问题,下面我会从这个现象一步步研究,希望最终的结论与正确的答案一致。
1.2研究“开发中遇到问题”的过程
我首先把btn01的click事件拆分为两个独立的按钮事件,大家看callback.jsp的代码:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>回调函数 CallBack Function Study</title>
</head>
<script type="text/javascript" src="jquery-1.7.1.js"></script>
<body>
<form>
<label for="txt">
文本框:
</label>
<input type="text" id="txt" name="txt" size="32"/>
<input type="button" id='btn01' name='btn01' value='BUTTON01'/>
<input type="button" id='btn02' name='btn02' value='BUTTON02'"/>
<input type="button" id='btn03' name='btn03' value='BUTTON03'/>
</form>
</body>
</html>
<script type="text/javascript">
var outerdata = '00';
$(document).ready(function(){
$('#txt').val('000000');
$('#btn01').bind('click',function(){
$.ajax({
type: "POST",
url: "<%=request.getContextPath() %>/vumssmer/vmerservice!studyCallBack.do",
data:'',
success:function(msg){
console.log('msg.vflag:' + msg.vflag);
console.log('msg.vmsg:' + msg.vmsg);
$('#txt').val(msg.vflag);
outerdata = msg.vflag;
}
});
if (outerdata == 'true'){
console.log('文本框内容是:' + $('#txt').val());
console.log('公共变量的值是:' + outerdata);
console.log('yes');
}else{
console.log('文本框内容是:' + $('#txt').val());
console.log('公共变量的值是:' + outerdata);
console.log('no');
}
});
$('#btn02').bind('click',function(){
$.ajax({
type: "POST",
url: "<%=request.getContextPath() %>/vumssmer/vmerservice!studyCallBack.do",
data:'',
success:function(msg){
console.log('msg.vflag:' + msg.vflag);
console.log('msg.vmsg:' + msg.vmsg);
$('#txt').val(msg.vflag);
outerdata = msg.vflag;
}
});
});
$('#btn03').bind('click',function(){
if (outerdata == 'true'){
console.log('文本框内容是:' + $('#txt').val());
console.log('公共变量的值是:' + outerdata);
console.log('yes');
}else{
console.log('文本框内容是:' + $('#txt').val());
console.log('公共变量的值是:' + outerdata);
console.log('no');
}
});
});
</script>
页面的效果是,如图2-1:

图2-1
我们先点击BUTTON2按钮,再点击BUTTON3按钮,结果如下,如图2-2:

图2-2
这时的结果是正确的。
这到底是怎么回事了???
我们仔细看看两次代码的区别了,btn01的代码都在一个函数里,而btn02和btn03的代码分属在不同的function里,我们再看看图1-1里显示的结果,打印出来的结果并没有按照代码的顺序,if里的打印代码先打印,而ajax的success方法里的代码后打印的。这说明如果代码在一个function里,if代码会先于success里的代码被执行,代码并不是按我们书写代码的顺序执行的。
对ajax熟悉的人都应该知道,我们处理ajax请求回来的结果都要定义一个回调函数,那么产生上面现象是不是因为回调函数都会在包含它的函数里滞后执行了。为了解开这个疑问,我做了如下的测试,大家看下面的callback.jsp代码:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>回调函数 CallBack Function Study</title>
</head>
<body>
<form>
<label for="txt">
文本框:
</label>
<input type="text" id="txt" name="txt" size="32"/>
<input type="button" id='btn' name='btn' value='BUTTON' onclick="btnclick()"/>
</form>
</body>
</html>
<script type="text/javascript">
var $ = function(){
return document.getElementById(arguments[0]);
}
window.onload = function(){
$('txt').value = '11111';
}
var staticnum = '000';
function btnclick(){
usedftn('true',callback);
}
function usedftn(flag,cbftn){
cbftn(flag);
if (staticnum == 'true'){
console.log('公共变量的值是:' + staticnum);
console.log('文本框内容是:' + $('txt').value);
console.log('yes');
}else{
console.log('公共变量的值是:' + staticnum);
console.log('文本框内容是:' + $('txt').value);
console.log('no');
}
}
function callback(){
if (arguments[0] != null && arguments[0] != ''){
staticnum = arguments[0];
$('txt').value = arguments[0];
console.log('回调函数公共变量的值是:' + staticnum);
console.log('回调函数文本框内容是:' + $('txt').value);
}
}
</script>
执行的结果如下:

图2-3
执行的结果是函数是按代码顺序执行。这和jQuery的ajax执行结果不同,那是不是因为jQuery代码的写法所导致的呢? jQuery代码是通过匿名函数设计的,里面的jQuery对象是按照json的格式定义的,如是我把代码更改成这样的,代码如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>回调函数 CallBack Function Study</title>
</head>
<body>
<form>
<label for="txt">
文本框:
</label>
<input type="text" id="txt" name="txt" size="32"/>
<input type="button" id='btn' name='btn' value='BUTTON'/>
</form>
</body>
</html>
<script type="text/javascript">
window.onload = function(){
document.getElementById('txt').value = '11111';
}
var staticnum = '000';
(function(window,undefined){
var document = window.document,navigator = window.navigator,location = window.location,
$ = function(){
return document.getElementById(arguments[0]);
};
var xQuery = {
xnum:'111',
usedftn:function(flag,cbftn){
cbftn(flag);
if (staticnum == 'true'){
console.log('公共变量的值是:' + staticnum);
console.log('文本框内容是:' + $('txt').value);
console.log('xQuery内部的xnum值是:' + xQuery.xnum);
console.log('yes');
}else{
console.log('公共变量的值是:' + staticnum);
console.log('文本框内容是:' + $('txt').value);
console.log('xQuery内部的xnum值是:' + xQuery.xnum);
console.log('no');
}
if (xQuery.xnum == 'true'){
console.log('xQuery 公共变量的值是:' + staticnum);
console.log('xQuery 文本框内容是:' + $('txt').value);
console.log('xQuery xQuery内部的xnum值是:' + xQuery.xnum);
console.log('xQuery yes');
}else{
console.log('xQuery 公共变量的值是:' + staticnum);
console.log('xQuery 文本框内容是:' + $('txt').value);
console.log('xQuery xQuery内部的xnum值是:' + xQuery.xnum);
console.log('xQuery no');
}
},
callback:function(){
if (arguments[0] != null && arguments[0] != ''){
staticnum = arguments[0];
$('txt').value = arguments[0];
xQuery.xnum = arguments[0];
console.log('回调函数公共变量的值是:' + staticnum);
console.log('回调函数文本框内容是:' + $('txt').value);
}
},
xAttachBtnEvt:function(){
if (arguments[0] != null && arguments[0] != ''){
$(arguments[0]).onclick = function(){
//this.usedftn('true',this.callback);改代码会出错,因为绑定按钮事件后this的指向变为了window了,而不是xQuery
xQuery.usedftn('true',xQuery.callback);
};
}
}
};
window.xQuery = window.$$ = xQuery;
})(window);
$$.xAttachBtnEvt('btn');//为按钮绑定click事件
</script>
执行结果如下,如图2-4:

图2-4
结果是按代码书写顺序执行的,看来不是javascript回调函数引起的上面的问题。
如果不是回调函数那么就应该是ajax本身了。
这里我还是按照jQuery的结构来写实例代码,ajax使用原生态的方式编写,这样会让我们探讨的问题更加清晰,代码如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>回调函数 CallBack Function Study</title>
</head>
<body>
<form>
<label for="txt">
文本框:
</label>
<input type="text" id="txt" name="txt" size="32"/>
<input type="button" id='btn' name='btn' value='BUTTON'/>
</form>
</body>
</html>
<script type="text/javascript">
window.onload = function(){
document.getElementById('txt').value = '11111';
}
var staticnum = '000';
(function(window,undefined){
var document = window.document,navigator = window.navigator,location = window.location,
$ = function(){
return document.getElementById(arguments[0]);
};
var xQuery = {
xnum:'1111',
type:'GET',
url:'wwww.baidu.com',
xmlHttp:'',
createXMLHttpRequest:function(){
if (window.XMLHttpRequest){
// IE7+, Firefox, Chrome, Opera, Safari
this.xmlHttp = new XMLHttpRequest();
}else{
// IE6, IE5
this.xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
}
},
ajaxftn:function(ajaxdata){
this.type = ajaxdata.type;
this.url = ajaxdata.url;
this.createXMLHttpRequest();
this.xmlHttp.open(this.type,this.url,true);
this.xmlHttp.onreadystatechange = this.jsonCallBack;
this.xmlHttp.setRequestHeader("Content-Type","application/x-www-form-urlencoded;");//使用POST传递信息时候用到的
this.xmlHttp.send(null);
if (staticnum == 'true'){
console.log('公共变量的值是:' + staticnum);
console.log('文本框内容是:' + $('txt').value);
console.log('xQuery内部的xnum值是:' + this.xnum);
console.log('yes');
}else{
console.log('公共变量的值是:' + staticnum);
console.log('文本框内容是:' + $('txt').value);
console.log('xQuery内部的xnum值是:' + this.xnum);
console.log('no');
}
if (this.xnum == 'true'){
console.log('xQuery 公共变量的值是:' + staticnum);
console.log('xQuery 文本框内容是:' + $('txt').value);
console.log('xQuery xQuery内部的xnum值是:' + this.xnum);
console.log('xQuery yes');
}else{
console.log('xQuery 公共变量的值是:' + staticnum);
console.log('xQuery 文本框内容是:' + $('txt').value);
console.log('xQuery xQuery内部的xnum值是:' + this.xnum);
console.log('xQuery no');
}
},
jsonCallBack:function(){
if (xQuery.xmlHttp.readyState == 4){
if (xQuery.xmlHttp.status == 200){
xQuery.parseResults();
}
}
},
parseResults:function(){
var retval = eval('('+ xQuery.xmlHttp.responseText +')');
console.log('服务端返回的vflag值:' + retval.vflag);
console.log('服务端返回的vmsg值:' + retval.vmsg);
$('txt').value = retval.vflag;
staticnum = retval.vflag;
xQuery.xnum = retval.vflag;
},
xAttachBtnEvt:function(){
if (arguments[0] != null && arguments[0] != ''){
$(arguments[0]).onclick = function(){
xQuery.ajaxftn({'type':'POST','url':'<%=request.getContextPath() %>/vumssmer/vmerservice!studyCallBack.do'});
};
}
}
};
window.xQuery = window.$$ = xQuery;
})(window);
$$.xAttachBtnEvt('btn');//为按钮绑定click事件
</script>
结果如图2-5所示:

图2-6
这个结果就和我们调用jQuery的ajax方法的结果一样了。
我的研究过程就是这样了,下面就是我的分析结果了。
1.3我的分析结果
首先我要讲讲javascript里回调函数到底是怎么回事。回调函数在编程语言里很普遍,java里面也有,百度百科里有对回调函数的定义:
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用为调用它所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
详情可以参见:
http://baike.baidu.com/view/414773.htm
我们这里不讲回调函数实际运用的场景,从编码角度,回调函数和调用回调函数的函数是一个统一的整体,他们在执行上是按照代码编写的顺序至上而下的。
我在学习ajax时候,我看的书籍上都写到onreadystatechange要赋一个回调函数,那么按照上面的结论我们在“开发问题中”写的代码应该能正常运行,但是结果却恰恰相反。
难道ajax的onreadystatechange存储的不是我们通常理解的回调函数吗?或者是ajax有自己特别的回调机制吗?
我的回答是onreadystatechange存储的是回调函数也没有什么特别的回调机制,但它不是被执行在我们所写的调用ajax方法内的回调函数,而是浏览器执行XMLHttpRequest请求里面的回调函数,我们书写的我们写的:
this.xmlHttp.onreadystatechange = this.jsonCallBack;
只是在为onreadystatechange做赋值操作。因此我们在执行我们自己编写的ajax函数时候onreadystatechange存储的函数是不会被调用的,因为这只是一个赋值操作。
那什么时候执行onreadystatechange存储的回调函数呢?当我们的ajax请求被成功的返回值以后,调用到了onreadystatechange存储的回调函数,回调函数就被执行了,这就是我们看到success函数里的代码会滞后于我们编写的ajax调用方法的原因所在。
在我写的代码里,ajax里的onreadystatechange存储回调函数我都是用xQuery.xnum、xQuery.xmlHttp调用xQuery里的方法,而不是this,大家可以试试把代码改成用this调用,最后firebug结果会表现为this.xmlHttp没有定义之类的提示,这个也反向说明了回调函数调用的时候已经脱离了原来方法而变成了一个独立的方法,因此我们存储的回调函数所使用的变量一定要在一个公共作用域里,因此使用了xQuery来存储变量。
以上的结论我们可以纠正对ajax调用几个错误的理解:
1. ajax里面的回调函数的调用机制是一种特别的机制,它与javascript普通回调函数的使用不一样;
2. ajax回调函数的作用域和ajax调用函数作用域的不同而引起的代码不能正常运行。
正确的理解应该是:
Ajax里的回调函数只是我们赋值给XMLHttpRequest对象的回调函数,它的执行和我们所写的调用ajax函数无关。
实际运用中如果我们想在执行完ajax请求后,根据请求结果执行相关的逻辑,那么请把逻辑写在ajax的回调函数里,只有这样才能让代码按业务逻辑正常运行。
对ajax回调函数的研究的更多相关文章
- 【spring 后台跳转前台】使用ajax访问的后台,后台正常执行,返回数据,但是不能进入前台的ajax回调函数中
问题: 使用ajax访问的后台,后台正常执行,并且正常返回数据,但是不能进入前台的ajax回调函数中 问题展示: 问题解决: 最后发现是因为后台的方法并未加注解:@ResponseBody,导致方法 ...
- 转: jquery中ajax回调函数使用this
原文地址:jquery中ajax回调函数使用this 写ajax请求的时候success中代码老是不能正常执行,找了半天原因.代码如下 $.ajax({type: 'GET', url: " ...
- 【springMVC 后台跳转前台】1.使用ajax访问的后台,后台正常执行,返回数据,但是不能进入前台的ajax回调函数中 ----2.前后台都没有报错,不能进入ajax回调函数
问题1: 使用ajax访问的后台,后台正常执行,并且正常返回数据,但是不能进入前台的ajax回调函数中 问题展示: 问题解决: 最后发现是因为后台的方法并未加注解:@ResponseBody,导致方 ...
- iOS下ajax回调函数里不能播放audio
iOS下audio必须监测到事件才可播放, ajax回调函数里不能播放 解决办法 在点击方法里先播放然后立即暂停,在回调函数里重新播放 onclick(function(){ $("#_wx ...
- setInterval调用ajax回调函数不执行的问题
setInterval调用ajax回调函数不执行 1.首先检查你的setInterval()函数写法是否正确 参考写法 // 检查是否支付成功 var isPayRequest=false; var ...
- ajax回调函数,全局变量赋值后,ajax外无法获取的解决
1 ajax回调函数内,function的执行与ajax外是异步的,常导致全局变量赋值后,再次使用此变量人无法获取. 所以,可以把需要的步骤,独立放在functuon中,在ajax回调函数中执行.可较 ...
- Jquery ajax回调函数不执行
ajax如下: $.post( "${pageContext.request.contextPath}/deptHead_assign.action", {"studen ...
- ajax回调函数中使用$(this)取不到对象的解决方法
如果在ajax的回调函数内使用$(this)的话,实践证明,是取不到任何对象的,需要的朋友可以参考下 $(".derek").each(function(){ $(this).cl ...
- ajax 回调函数
回调函数 如果要处理$.ajax()得到的数据,则需要使用回调函数.beforeSend.error.dataFilter.success.complete. beforeSend 在发送请求之前调用 ...
随机推荐
- bzoj 3597: [Scoi2014]方伯伯运椰子 [01分数规划 消圈定理 spfa负环]
3597: [Scoi2014]方伯伯运椰子 题意: from mhy12345 给你一个满流网络,对于每一条边,压缩容量1 需要费用ai,扩展容量1 需要bi, 当前容量上限ci,每单位通过该边花费 ...
- ZOJ1450 BZOJ1136 BZOJ1137 HDU3932[最小圆覆盖]
Minimal Circle Time Limit: 5 Seconds Memory Limit: 32768 KB You are to write a program to find ...
- 【转】PE详解
参考网址: http://blog.tianya.cn/listcate-4259222-2269876-1.shtml PE文件
- MessagePack简析
一.MessagePack是什么 先看官方的定义:MessagePack是一种高效的二进制序列化格式.它允许您像JSON一样在多个语言之间交换数据.但是,它更快并且更小. 从官方定义中,可以有如下的结 ...
- linux常用命令(不断更新)
睡眠命令(第一步可省去): 1.查看你的系统支持什么模式:cat /sys/power/state(我的系统为:freeze mem disk) 2.切换到管理员模式下,执行命令:echo " ...
- MySQL数据类型概念
关系型数据库的特点 1,数据以表格的形式出现 2,每行为各种记录的名称 3,每列为数据名称所对应的数据域 4许多的行和列组成一张table 5若干的表单组成databases 术语 数据库:关联表的集 ...
- express respond.send 和 end的区别
做个记录 res.send() will send the HTTP response. Its syntax is, res.send([body]) The body parameter can ...
- Centos启动默认打开网络
Centos打开网络 测试的时候发现网络没有打开,得到图像界面点击网络打开.比较麻烦去搜索了解决方法在此记录下来. 通过 /etc/sysconfig/network-script/, 编辑ifcfg ...
- 撸一撸Spring Cloud Ribbon的原理-负载均衡器
在上一篇<撸一撸Spring Cloud Ribbon的原理>中整理发现,RestTemplate内部调用负载均衡拦截器,拦截器内最终是调用了负载均衡器来选择服务实例. 接下来撸一撸负载均 ...
- mysql数据库外部无法访问
有以下两种情况: 1.mysql未分配访问权限 格式:grant 权限 on 数据库名.表名 用户@登录主机 identified by "用户密码"; grant select, ...