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

时间:2011-02-18 17:56:14

标签: javascript scope closures

我试图将函数范围传递给回调方法。我遇到的问题是我得到了对象范围,它不能让我访问原始函数中的参数和局部变量。我对“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
}

我需要有人来俱乐部我在这里头......路过的时候“这”在我的第一种情况,当然这是在全球范围内(我在一个全局定义的函数)...我的问题是,我在通过范围时我想到了我可以访问本地定义的变量和参数w /在那个上下文中......我是否摇摆了我之前对JS的理解?如何完成这项任务?

3 个答案:

答案 0 :(得分:16)

顺便说一句,关于代码中范围的几句话:

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范围和结束的阅读资源很少:

  1. Explaining JavaScript scope and closures
  2. Picking up Javascript - Closures and lexical scoping
  3. JavaScript: Advanced Scoping & Other Puzzles - 关于主题的非常好的演示文稿

答案 1 :(得分: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我也可以添加我希望从该范围传递的局部变量。

当然,由于旧版浏览器中没有绑定,您需要通过框架添加它,或者使用添加该方法的许多小代码片段中的一个(如果不存在)。

答案 2 :(得分:0)

我的第一个答案有点快,并且Maxym在下面的评论中留下了一些间隙整体,感谢通知我:)这就是我在回去工作之前快速发布的内容: p

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

使用var只会使lookup's私有作用域和嵌套在其中的任何函数都可以访问,这是其他答案所展示的(并且可能是最佳路径)

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

  

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

正如我之前回答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);