Passing scope to callback function / binding

前端 未结 3 1018
梦毁少年i
梦毁少年i 2020-12-23 15:18

I am attempting to pass function scope to a callback method. The problem I am having is that I am getting object scope, which does not provide me with access to the paramet

相关标签:
3条回答
  • 2020-12-23 15:45

    btw, few words about scopes in your code:

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

    so it is the same as writing:

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

    it is so because you define lookup function globally. inside doWork function, when you write this it points to MySvcWrap object (because that function is defined inside that object).

    If your callback function has to see bar variable, it should be defined in the same scope, like that

    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();
    

    in this case you send anonymous function as callback, it is defined inside lookup function so it has access to its local variables; my console shows me in this cae:

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

    to make it easier to support, you can define myHandler inside lookup function:

    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
    }
    

    On the other hand, why one function should have access to local variables of another function? Maybe it can be redesigned...

    One more way to make your code working is to use anonymous function, instead of lookup (will work if you just declare and execute that function once):

    (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);
      }
    )();
    

    result is the same, but no lookup function anymore...

    And one more idea to make it working... Actually you need to define callback handler in the same scope where bar variable is defined, so it can be done a bit tricky, but just as alternative:

    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
    }
    

    And few resources to read about JavaScript scoping and closure:

    1. Explaining JavaScript scope and closures
    2. Picking up Javascript - Closures and lexical scoping
    3. JavaScript: Advanced Scoping & Other Puzzles - very nice presentation about topic
    0 讨论(0)
  • 2020-12-23 15:50

    I was a bit quick on my first answer and left some gapping wholes as stated by Maxym in the comments below, thanks for informing me :) That's what I get for trying to post quickly before going back to work :P

    What I was trying to get at in my first answer is if you want to use binding to access variables in function lookup from function myHandler (which is outside the scope of lookup) you would have to not use var but this instead.

    Using var would make it only accessible to lookup's private scope and any functions nested in it, which is how the other answers have demonstrated (and are probably the best route)

    Below is an updated example of how to pass lookup's scope to myHandler leaving myHandler completely out of lookup's scope. Hopefully this will help shed a bit of light on:

    "The problem I am having is that I am getting object scope, which does not provide me with access to the parameters and local variables in the original function."

    As stated in the comments about my previous answer this can get tricky and you can end up adding things to the global scope if not careful like I did in my first example. :( So I've added a bit of a hack'ish check to see what scope this is making sure it's not window for demonstration purposes...

    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);
    
    0 讨论(0)
  • 2020-12-23 15:59

    The code would work if it was structured like this.

    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();
    

    The myHandler function can access the local variables of lookup as it is enclosed by it, thus a closure.

    I'd probably try to achieve the same result, by structuring the code like this.

    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();
    

    Rather than pass the scope, I use bind inside the lookup method. Using bind I can also add the local variables I wish to pass from that scope.

    Of course as bind isn't available in older browsers, you either need to add it via a framework, or use one of the many little snippets of code that add the method if it doesn't exist.

    0 讨论(0)
提交回复
热议问题