1

I got a function - lets define it as function getdata(after){} that takes as a after parameter a function. Quite natural. This function is being run from some other function that has some variables defined in its scope:

$('.button').click(function(){
   var rel = $(this).attr('rel');
   var mooo = $(this).parent().offset().left;
   getdata(function(){alert(rel);console.log(moo)});
});

How do I pass those variables right way, so it would really be the same thing as it was when clicking the button? It cannot be global variable, because user can spam-click button and it should keep the variables as they exactly were during clicking, even when after function would be called 10 minutes later in kind of random order, because as name says - it is being called AFTER ajax request.

This is only an example - the idea is that this after function would be variable too, called from totally different places.


Ok, got -1, because it is unclear what I am asking for, so I try to explain the problem. I don't really understand when variable is passed as variable and when it is passed as pointer of variable that may change before it will get used by function it was passed to.

When I define variable in one function and pass it to another, is it a pointer or a stand-alone variable? When I will call origin function again, and those variables will change - will it affect the function that was "sheduled" before? Or a word var before variable means - define it as totally fresh variable, and every time the function with var is being called, it allocates this variable in memory as totally new one, undependend of anything that happens?

I am generally a PHP, C and Delphi progammer and it is a bit hard for me to understand how those variable scopes work, especially when everything is being async and callbacked.

Flash Thunder
  • 11,672
  • 8
  • 47
  • 91
  • Unless the parameter sent to the function is an object, it will pass its value instead of its reference. As you are sending a string (`$(this).attr('rel')` attribute) and an integer (`$(this).offset().left`), you don't need to worry about the getdata possible delay to invoke the callback. The variables printed inside your callback will contain the values at the moment the button was clicked. – Guilherme Sehn Oct 25 '13 at 18:16
  • How do I determinate if it is passed as string or an object? `$(this).offset()` is an object and `$(this).offset().left` is stand-alone string? And what if I would like to pass `$(this)` sender object, to be able to change the object after function is done? – Flash Thunder Oct 25 '13 at 18:21
  • @FlashThunder Check out my updated answer. I don't suggest you use it, but it gives you insight into variable scoping and JS's dynamic nature. – Ruan Mendes Oct 25 '13 at 21:06

3 Answers3

2

To explain simply, in javascript, simple types (strings, numbers, booleans) are passed by value while objects are passed by reference. see this question for a real detailed answer.

If you don't know how many arguments are you going to deal with (or their types), then you can use the arguments array

var getdata = function(){
    //your function logic

    //arguments is an array with all the args
    //the last one is the "after" function
    var after = arguments[arguments.length-1];
    return after(arguments);
};

$('.button').click(function(){
   var rel = $(this).attr('rel');
   var mooo = $(this).parent().offset().left;
   getdata(rel, moo, /*add all the arguments you want here...*/, function(args){
       alert(args[0]);
       console.log(args[1])
   });
});
Community
  • 1
  • 1
rvignacio
  • 1,710
  • 1
  • 18
  • 33
  • But those variables may totally vary in type and count. – Flash Thunder Oct 25 '13 at 18:17
  • 1
    @FlashThunder You cannot divine all the variables from a scope in JS. You have to specify which variables will be available to functions that aren't lexically scoped, that is, literaly within the source code of the outer function. See http://stackoverflow.com/questions/1047454/what-is-lexical-scope – Ruan Mendes Oct 25 '13 at 18:34
  • @FlashThunder The best that you can do is pass a single object that you can add as many properties, with whatever type you want – Ruan Mendes Oct 25 '13 at 18:36
  • @FlashThunder, you can use the arguments array to read a variable number of arguments, or an object like Juan Mendes pointed out. – rvignacio Oct 25 '13 at 18:41
1

I'm unsure what you're asking, but this is a variation of rvignacio's answer, allowing for any number of parameters to getdata and after:

var getdata = function(after){
    //your function logic
    return after.apply(null, [].slice.call(arguments, 1));
};

$('.button').click(function(){
   var rel = $(this).attr('rel');
   var mooo = $(this).parent().offset().left;
   getdata(function(rel_param, moo_param){
       console.log(rel_param);
       console.log(moo_param)
   }, rel, moo);
});
bfavaretto
  • 71,580
  • 16
  • 111
  • 150
  • OP wants the same variable scope to be available to the function that is passed in, it's impossible in JS, there's only lexical variable scoping. – Ruan Mendes Oct 25 '13 at 18:35
1

You cannot do it exactly as you've described. What you are trying to do is to pass the alll the associated closures to the callback. In JavaScript, lexical scoping of variables is used. That means that

function outer (callback) {
  var a = 1, b = 2, c = 3;
  var inner = function () {
     // This works because inner is defined inside of outer
     // and has access to a,b,c
     console.log(a,b,c); 
  }      
  inner();
  callback();
}

outer(function() {
    // This won't work because it doesn't know where to get a,b,c from
    // Some languages may(could/allow?) this, JS doesn't
    console.log(a,b,c);
});

Therefore, to solve your problem, you have to specify what variables you want made available to your callback by passing them as arguments. rvignacio and bfavaretto already showed you how you can do that

eval() for the win (for the loss, really)

OK, there's a way, but it requires eval, so you should't use it. Here it is for fun anyway. eval() does get executed in the same scope, so by adding an eval within the lexical scope and a callback to invoke it, you can achieve what you'd like. Again, I'm not suggesting you do this. It breaks encapsulation and a callback should not know about the privates of its caller. But just for the fun of it.... http://jsfiddle.net/zbwd7/1/

/**
 * The callback will accept a single argument, it's a function that takes the name
 * of the variable you want to use and returns its value. DON'T EVER DO THIS IN REAL CODE
 * @callback {function(string): *} getVar The function that gives you access to the
 * caller's scope
 * @callback.varName {string} The name of the variable to retrieve
 * @callback.return {*} The value of the variable
 */
function outer (callback) {
  var a = 1, b = 2, c = 3;

  function getVar(varName) {
      return eval(varName);
  }      
  callback(getVar);
}


outer(function(getVar) {
    // outputs one, two, three
    console.log(getVar("a"), getVar("b"), getVar("c") );
});
Community
  • 1
  • 1
Ruan Mendes
  • 90,375
  • 31
  • 153
  • 217