将范围传递给回调函数/绑定

Posted

技术标签:

【中文标题】将范围传递给回调函数/绑定【英文标题】:Passing scope to callback function / binding 【发布时间】:2011-06-30 00:15:24 【问题描述】:

我正在尝试将函数范围传递给回调方法。我遇到的问题是我得到了对象范围,它不能让我访问原始函数中的参数和局部变量。我对“this”的理解是指当前上下文(无论是窗口还是某个对象)除了本地声明的变量和参数。 [在“执行上下文”部分引用 Richard Cornford 在http://jibbering.com/faq/notes/closures/ 的出色工作]。我也了解 javascript 中的变量具有函数范围(如果它们在函数内声明,则只能从该函数内访问)。

有了这种理解,在一个新的环境中,我正在尝试编写一个模式,我为我以前的雇主做了很多工作,调用一个异步方法,指定一个回调处理程序并传递我当前的范围,期望它可以在回调方法。在我当前的环境中,我发现情况并非如此。 (披露:我在以前的环境中使用 ExtJS...现在让我觉得我对框架有点太舒服了,对正在发生的事情做出假设)。

我的简单测试代码演示了我正在尝试做什么以及什么不起作用。

function myHandler(data, ctx) 
    console.log('myHandler():  bar: ' + bar);  // <- prob: bar undefined 
    console.log(JSON.stringify(data));

MySvcWrap = 
    doWork: function(p1, callback, scope) 
        var result = colors: ['red', 'green'], name:'Jones', what: p1;
        if (callback) 
            callback.call(scope||this,result, scope);
         
    

function lookup() 
    var bar = 'food'; // local var
    MySvcWrap.doWork('thang', myHandler, this); // scope object is this


lookup();

这里的问题是传递给 MySvcWrap.doWork 的“this”在这种情况下是 Window 全局对象。我的意图是将函数的执行上下文传递给 myHandler。

我尝试过的。如果我传递一个对象而不是“this”,那么它可以工作,例如:

function myHandler(data, ctx) 
    console.log('myHandler():  this.bar: ' + this.bar);  // <- no prob: this.bar 
    console.log(JSON.stringify(data));


function lookup() 
    var bar = 'food'; // local var
    MySvcWrap.doWork('thang', myHandler, bar: bar); // scope object is just object

我需要有人把我放在头上......在我的第一种情况下传递“this”时,当然这是全局范围(我在一个全局定义的函数中)......我的问题是我认为在传递范围时我可以在该上下文中访问本地定义的变量和参数...我是否在动摇我以前对 JS 的理解?如何完成这项任务?

【问题讨论】:

【参考方案1】:

顺便说一句,关于代码中的作用域的几句话:

function lookup() 
    var bar = 'food'; // local var
    MySvcWrap.doWork('thang', myHandler, this); // this here points to window object and not to the function scope

所以和写法一样:

function lookup() 
    var bar = 'food'; // local var
    MySvcWrap.doWork('thang', myHandler, window); 

之所以如此,是因为您在全局范围内定义了查找函数。在doWork 函数内部,当您编写this 时,它指向MySvcWrap 对象(因为该函数是在该对象内部定义的)。

如果你的回调函数必须看到bar变量,它应该被定义在同一个范围内,就像那样

MySvcWrap = 
    doWork: function(p1, callback, scope) 
        var result = colors: ['red', 'green'], name:'Jones', what: p1;
        if (callback) 
            callback.call(scope||this,result, scope);
         
    

function lookup() 
    var bar = 'food'; // local var
    MySvcWrap.doWork('thang', 
        function (data, ctx) 
            console.log('myHandler():  bar: ' + bar); 
            console.log(JSON.stringify(data));
        , 
        this); // scope object is this


lookup();

在这种情况下,您将匿名函数作为回调发送,它是在查找函数内部定义的,因此它可以访问其局部变量;我的控制台显示我在这个 cae 中:

myHandler(): bar: food
"colors":["red","green"],"name":"Jones","what":"thang"

为了更容易支持,你可以在查找函数中定义myHandler:

function lookup() 
    var bar = 'food'; // local var
    var myHandler = function(data, ctx) 
        console.log('myHandler():  bar: ' + bar);
        console.log(JSON.stringify(data));
    ;
    MySvcWrap.doWork('thang', myHandler, this); // scope object is this

另一方面,为什么一个函数应该可以访问另一个函数的局部变量?也许可以重新设计...

让您的代码正常工作的另一种方法是使用匿名函数,而不是查找(如果您只声明并执行一次该函数就可以工作):

(function() 
   var bar = 'food';

   function myHandler(data, ctx) 
       console.log('myHandler():  bar: ' + bar);  
       console.log(JSON.stringify(data));
     
    MySvcWrap = 
       doWork: function(p1, callback, scope) 
           var result = colors: ['red', 'green'], name:'Jones', what: p1;
           if (callback) 
               callback.call(scope||this,result, scope);
            
       
    
    MySvcWrap.doWork('thang', myHandler, this);
  
)();

结果是一样的,但是没有查找功能了……

还有一个让它工作的想法......实际上你需要在定义 bar 变量的同一范围内定义回调处理程序,所以它可以做得有点棘手,但作为替代方案:

function myHandler(bar)  // actually in this case better to call it createHandler 
    return function(data, ctx) 
        console.log('myHandler():  bar: ' + bar); 
        console.log(JSON.stringify(data));
     

MySvcWrap = 
    doWork: function(p1, callback, scope) 
        var result = colors: ['red', 'green'], name:'Jones', what: p1;
        if (callback) 
            callback.call(scope||this,result, scope);
         
    

function lookup() 
    var bar = 'food'; // local var
    MySvcWrap.doWork('thang', myHandler(bar), this); // scope object is this

关于 JavaScript 作用域和闭包的阅读资源很少:

    Explaining JavaScript scope and closures Picking up Javascript - Closures and lexical scoping JavaScript: Advanced Scoping & Other Puzzles - 非常好的主题介绍

【讨论】:

我喜欢参数化处理程序方法。【参考方案2】:

如果代码是这样的结构,它就会工作。

MySvcWrap = 
    doWork: function(p1, callback, scope) 
        var result = colors: ['red', 'green'], name:'Jones', what: p1;
        if (callback) 
            callback.call(scope||this,result, scope);
         
    

function lookup() 
    var bar = 'food'; // local var

    function myHandler(data, ctx) 
        console.log('myHandler():  bar: ' + bar);  // <- prob: bar undefined 
        console.log(JSON.stringify(data));
    

    MySvcWrap.doWork('thang', myHandler, this); // scope object is this


lookup();

myHandler 函数可以访问查找的局部变量,因为它被它包围,因此是一个闭包。

我可能会尝试通过像这样构造代码来达到相同的结果。

function myHandler(data, bar, ctx) 
    console.log('myHandler():  bar: ' + bar);  // <- prob: bar undefined 
    console.log(JSON.stringify(data));

MySvcWrap = 
    doWork: function(p1, callback) 
        var result = colors: ['red', 'green'], name:'Jones', what: p1;
        if (callback) 
            callback(result);
         
    

function lookup() 
    var bar = 'food'; // local var
    MySvcWrap.doWork('thang', myHandler.bind(this, bar)); // callback function is bound to the scope


lookup();

我没有通过范围,而是在查找方法中使用绑定。使用 bind 我还可以添加我希望从该范围传递的局部变量。

当然,由于旧版浏览器中不提供 bind,您要么需要通过框架添加它,要么使用许多小 sn-ps 代码之一来添加该方法(如果它不存在)。

【讨论】:

【参考方案3】:

我的第一个答案有点快,并留下了一些空白的整体,正如 Maxym 在下面的 cmets 中所说,感谢您通知我 :) 这就是我在回去工作之前尝试快速发布的结果: P

我在第一个答案中试图得到的是,如果你想使用绑定从函数 myHandler 访问函数 lookup 中的变量(这超出了 lookup 的范围),你必须不改用varthis

使用 var 将使其只能被 lookup's 私有范围和嵌套在其中的任何函数访问,这就是其他答案所展示的方式(并且可能是最佳途径)

下面是一个更新示例,说明如何将查找范围传递给 myHandler,使 myHandler 完全超出查找范围。希望这将有助于阐明一点:

“我遇到的问题是我得到了对象范围,它不能让我访问原始函数中的参数和局部变量。”

如 cmets 中所述,我之前的回答 this 可能会变得很棘手,如果不小心就像我在第一个示例中所做的那样,您最终可能会将内容添加到 global 范围内。 :(所以我添加了一些hack'ish检查以查看this的范围确保它不是window用于演示目的......

Live Example

function myHandler(data, ctx) 
    console.log('myHandler():  bar: ' + this.bar); // <- must use `this`
    console.log('myHandler():  privateBar: ' + this.privateBar); // <- undefined because it's privately scoped
    console.log(JSON.stringify(data));

MySvcWrap = 
    doWork: function(p1, callback, scope) 
        var result = 
            colors: ['red', 'green'],
            name: 'Jones',
            what: p1
        ;
        if (callback) 
            callback.call(scope || this, result, scope);
        
    


function lookup() 
    if(this == window) return lookup.call(lookup); // <- my hack'ish check

    var privateBar = 'private food'; // private local var
    this.bar = 'food'; // public local var
    MySvcWrap.doWork('thang', myHandler, this); // scope object is this


lookup();

console.log('global space - bar: ' + this.bar);

【讨论】:

var bar = this.bar = 'food'; - 在这种情况下,您在窗口对象中创建bar 变量,因此它与在查找函数之外声明此变量并在此处定义它相同,或者在没有bar = 'food' 的情况下定义,我的意思是没有 var... 现在这段代码非常棘手,并且隐藏了变量 bar 现在也是全局的事实

以上是关于将范围传递给回调函数/绑定的主要内容,如果未能解决你的问题,请参考以下文章

了解嵌套回调和范围?

将回调函数作为道具传递给孩子

将额外变量传递给回调函数

如何将内部回调的结果传递给其父函数? [复制]

怎样给回调函数绑定this

如何将变量从 .map() 传递给回调函数