关于javascript 回调函数中变量作用域的讨论


Posted in Javascript onSeptember 11, 2009

1、背景
Javascript中的回调函数,相信大家都不陌生,最明显的例子是做Ajax请求时,提供的回调函数,
实际上DOM节点的事件处理方法(onclick,ondblclick等)也是回调函数。
在使用DWR的时候,回调函数可以作为第一个或者最后一个参数出现,如:
JScript code function callBack(result){ } myDwrService.doSomething(param1,param2,callBack);//DWR的推荐方式 //或者 myDwrService.doSomething(callBack,param1,param2);

2、问题描述
最近在使用Dojo+Dwr的时候,碰到一个问题:
如果回调函数是属于某个对象(记为obj1)的方法,等到DWR执行该回调函数的时候,
上下文却不是obj1。
表现的现象就是在回调函数中访问obj1的任何属性都是undefined。
版本:Dojo1.3.1和dwr2
3、模拟问题的代码
下面的测试代码可以模拟这个问题:
JScript code

<html> 
<head> 
<script type="text/javascript"><!-- 
    var context="全局"; 
    var testObj={ 
        context:"初始", 
        callback:function (str){ 
            //回调函数 
            alert("callback:我所处的上下文中,context="+this.context+",我被回调的方式:"+str); 
        } 
    }; 
    //创建一个对象,作为测试回调函数的上下文 
    testObj.context="已设置"; 
    function testCall(){ 
        callMethod(testObj.callback); 
        callObjMethod(testObj,testObj.callback); 
    } 
    function callMethod(method){ 
        method("通过默认上下文回调"); 
    } 
    function callObjMethod(obj,method){ 
        method.call(obj,"指定显式对象上下文回调"); 
    } 
// --></script> 
</head> 
<body> <a href="javascript:void(0)" onclick="testCall()">调用测试</a> </body> 
</html>

在callObjMethod方法中,我用了两种方式回调“method"方法:
第一种方式:method("通过默认上下文回调");
没有指定上下文,我们发现回调函数内部访问context的值是全局变量的值,
这说明,执行该方法的默认上下文是全局上下文。
第二种方式:method.call(obj,"指定显式对象上下文回调");
指定obj为method执行的上下文,就能够访问到对象内部的context。

4、研究DWR
因为06年使用DOJO+DWR(1.0)的时候,已经遇到过这个问题,当时没做太多功课,直接改了dwr的源代码。
现在用dwr2,于是想先看看DWR是不是对这个问题有新的处理方式,
将dwr.jar中的engine.js拿出来,查看了有关回调的相关代码(_remoteHandleCallback和_execute),
发现对回调的处理方式似乎比1.0更简单,没办法将对象和方法一起传过去。
5、做进一步的研究
因为这次DWR在项目中的使用太广泛,而且我相信这样的需求应该是可以满足的,于是没有立刻修改源码,
首先,在Google上搜Dojo+dwr,没有查到什么结论,可能Dojo的用户不是太多。
于是又搜”javascript callback object context“,得到一篇文章专门介绍java回调函数的问题:
http://bitstructures.com/2007/11/javascript-method-callbacks
最重要的一句话:
When a function is called as a method on an object (obj.alertVal()),
"this" is bound to the object that it is called on (obj).
And when a function is called without an object (func()),
"this" is bound to the JavaScript global object (window in web browsers.)
这篇文章也提供了解决方案,就是使用Closure和匿名方法,
在javascript中,在function内部创建一个function的时候,会自动创建一个closure,
而这个closure就能记住对应的function创建时的上下文。
所以,如果这样:
JScript code var closureFunc=function(){ testObj.callback(); }
那么无论在什么地方,直接调用closureFunc()和调用testObj.callback()是等价的。
详情参见上面提到的文章:http://bitstructures.com/2007/11/javascript-method-callbacks。
6、改进模拟代码
模拟代码只,我们再增加一种回调方式:
JScript code

<html> 
<head> 
<script type="text/javascript"><!-- 
    var context="全局"; 
    var testObj={ 
    context:"初始", 
    callback:function (str){ 
        //回调函数 
        alert("callback:我所处的上下文中,context="+this.context+",我被回调的方式:"+str); 
        } 
    }; 
    //创建一个对象,作为测试回调函数的上下文 
    function testCall(){ 
        callMethod(testObj.callback); 
        callWithClosure(function(param){testObj.callback(param);}); 
        testObj.context="已设置"; 
        callObjMethod(testObj,testObj.callback); 
    } 
    function callMethod(method){ method("通过默认上下文回调"); } 
    function callWithClosure(method){ method("通过Closure保持上下文回调"); } 
    function callObjMethod(obj,method){ method.call(obj,"指定显式对象上下文回调"); } 
// --></script> 
</head> 
<body> <a href="javascript:void(0)" onclick="testCall()">调用测试</a> </body> 
</html>

测试以上代码,我们可以发现,通过Closure和通过显示指定对象得到的效果一致。
7、模拟更加真实的调用情景
但是以上代码还有一个问题,通常在真实环境中,如果回调函数是对象中方法,那么发起请求的方法也处在同一个对象,
在javascript中,this也可以代表当前对象,但不能直接用在匿名function中用,比如:
JScript code var testObj={ context:"初始", callback:function (str){//回调函数 alert("callback:我所处的上下文中,context="+this.context+",我被回调的方式:"+str); }, caller:function(){ callWithClosure(function(param){this.callback(param);}); } };//创建一个对象,作为测试回调函数的上下文
以上代码中的this指的不是testObj,而是全局上下文,
需要在closure外写一个临时变量来代表this,完整的代码如下:
JScript code
<html> 
<head> 
<script type="text/javascript"><!-- 
var context="全局"; 
var testObj={ 
    context:"初始", 
    callback:function (str){ 
        //回调函数 
        alert("callback:我所处的上下文中,context="+this.context+",我被回调的方式:"+str); 
    }, 
    caller:function(){ 
        callWithClosure(function(param){this.callback(param);}); 
        var temp=this; 
        callWithClosure(function(param){temp.callback(param);}); 
    } 
}; 
//创建一个对象,作为测试回调函数的上下文 
testObj.context="已设置"; 
function testCall(){ 
    //callMethod(testObj.callback); 
    testObj.caller(); 
    //callWithClosure(function(param){testObj.callback(param);}); 
    //callObjMethod(testObj,testObj.callback); 
} function callObjMethod(obj,method){method.call(obj,"指定显式对象上下文回调"); } 
function callMethod(method){ method("通过默认上下文回调"); } 
function callWithClosure(method){ method("通过Closure保持上下文回调"); } 
function callback(str){ alert("callback:我是定义在外部的全局函数。"); } 
// --></script> 
</head> 
<body> 
<a href="javascript:void(0)" onclick="testCall()">调用测试</a> 
</body> 
</html>

8、什么是Closure
Two one sentence summaries:
a closure is the local variables for a function - kept alive after the function has returned,
or
a closure is a stack-frame which is not deallocated when the function returns. (as if a 'stack-frame' were malloc'ed instead of being on the stack!)
Javascript 相关文章推荐
在b/s开发中经常用到的javaScript技术
Aug 23 Javascript
JavaScript检查弹出窗口是否被阻拦的方法技巧
Mar 13 Javascript
node.js 使用ejs模板引擎时后缀换成.html
Apr 22 Javascript
在JavaScript中操作时间之setYear()方法的使用
Jun 12 Javascript
超漂亮的Bootstrap 富文本编辑器summernote
Apr 05 Javascript
谈一谈JS消息机制和事件机制的理解
Apr 14 Javascript
KnockoutJS 3.X API 第四章之click绑定
Oct 10 Javascript
使用BootStrap和Metroui设计的metro风格微网站或手机app界面
Oct 21 Javascript
数组Array的一些方法(总结)
Feb 17 Javascript
JavaScript之生成器_动力节点Java学院整理
Jun 30 Javascript
常用的 JS 排序算法 整理版
Apr 05 Javascript
浅谈Vue路由快照实现思路及其问题
Jun 07 Javascript
javascript 一些用法小结
Sep 11 #Javascript
JS 日期验证正则附asp日期格式化函数
Sep 11 #Javascript
jquery 简单导航实现代码
Sep 11 #Javascript
禁止JQuery中的load方法装载IE缓存中文件的方法
Sep 11 #Javascript
一个用javascript写的select支持上下键、首字母筛选以及回车取值的功能
Sep 09 #Javascript
用Javascript 和 CSS 实现脚注(Footnote)效果
Sep 09 #Javascript
Javascript 获取滚动条位置等信息的函数
Sep 08 #Javascript
You might like
PHP采集类snoopy详细介绍(snoopy使用教程)
2014/06/19 PHP
php简单实现无限分类树形列表的方法
2015/03/27 PHP
laravel如何开启跨域功能示例详解
2017/08/31 PHP
Mootools 1.2教程 同时进行多个形变动画
2009/09/15 Javascript
WEB页子窗口(showModalDialog和showModelessDialog)使用说明
2009/10/25 Javascript
菜鸟学习JavaScript小实验之函数引用
2010/11/17 Javascript
css值转换成数值请抛弃parseInt
2011/10/24 Javascript
js/html光标定位的实现代码
2013/09/23 Javascript
通过遮罩层实现浮层DIV登录的js代码
2014/02/07 Javascript
9款2014最热门jQuery实用特效推荐
2014/12/07 Javascript
js实现带关闭按钮始终显示在网页最底部工具条的方法
2015/03/02 Javascript
详解JavaScript中void语句的使用
2015/06/04 Javascript
图解JavaScript中的this关键字
2020/05/28 Javascript
简介AngularJS中$http服务的用法
2016/02/06 Javascript
javascript 中的try catch应用总结
2017/04/01 Javascript
随机生成10个不重复的0-100的数字(实例讲解)
2017/08/16 Javascript
vue路由嵌套的SPA实现步骤
2017/11/06 Javascript
bootstrap中的导航条实例代码详解
2019/05/20 Javascript
Vue 401配合Vuex防止多次弹框的案例
2020/11/11 Javascript
[07:25]DOTA2-DPC中国联赛2月5日Recap集锦
2021/03/11 DOTA
python递归查询菜单并转换成json实例
2017/03/27 Python
Python使用剪切板的方法
2017/06/06 Python
python实现人人自动回复、抢沙发功能
2018/06/08 Python
Python之时间和日期使用小结
2019/02/14 Python
python聚类算法解决方案(rest接口/mpp数据库/json数据/下载图片及数据)
2019/08/28 Python
python 计算方位角实例(根据两点的坐标计算)
2020/01/17 Python
python re模块匹配贪婪和非贪婪模式详解
2020/02/11 Python
Python Django2 model 查询介绍(条件、范围、模糊查询)
2020/03/16 Python
Pycharm添加虚拟解释器报错问题解决方案
2020/10/13 Python
顶丰TOPPIK台湾官网:增发纤维假发,告别秃发困扰
2018/06/13 全球购物
莫斯科隐形眼镜网上商店:Linzi
2019/07/22 全球购物
新闻学专业个人求职信写作
2014/02/04 职场文书
遵纪守法演讲稿
2014/05/23 职场文书
专项法律服务方案
2014/06/11 职场文书
2014年世界艾滋病日演讲稿
2014/11/28 职场文书
Vue接口封装的完整步骤记录
2021/05/14 Vue.js