15
var inner = function() { console.log(x); }

// test 1
(function(cb) { var x = 123; cb(); })(inner);

// test 2
(function(cb) { var x = 123; cb.apply(this); })(inner);

// test 3
(function(cb) { var x = 123; cb.bind(this)(); })(inner);

// test 4
(function(cb) { cb.bind({x: 123})(); })(inner);

All tests result in: ReferenceError: x is not defined

Do someone know how it is possible to access 'x' as a local variable inside the callback?

5
  • 1
    What problem are you trying to solve? variables in a function are local to that function. Commented Oct 27, 2014 at 15:23
  • 4
    What an odd comment, why does it matter what the problem is? The OP asked a low-level JS question which has a straightforward answer. Commented Oct 27, 2014 at 15:25
  • JavaScript has lexical scope, not dynamic scope. Commented Oct 27, 2014 at 15:28
  • Pass x as an argument. Commented Oct 27, 2014 at 16:14
  • @AdamJenkins Because of the X/Y problem: meta.stackexchange.com/questions/66377/what-is-the-xy-problem . This question does not make sense to anyone who have programmed in almost any language that's not assembly in the last 20 years. Since it makes no sense it is better to find out what the OP is actually trying to do and help him do that instead of guessing Commented Jan 23, 2024 at 11:10

5 Answers 5

12

Fact: when you do var inner = function() { console.log(x); } in your first line, x is not defined. Why? Because, inside your inner function, there's no local declaration of x (which would be done with var x = something). The runtime will then look up in the next scope, that is the global scope. There isn't, also, a declaration of x, so x is also not defined there.

The only places where there is a variable called x are inside each one of your 4 IIFEs following. But inside the IIFEs, each x is a different variable, in a different scope. So, if what you want is to console.log() the x defined inside each IIFE, you are taking the wrong approach.

Keep in mind that, when you define inner, you are capturing the environment inside the function's closure. It means that, whatever value could x have there (in the declaration of the function), would be the available value to the x variable later, when the inner function would be used. The fact that your x there is not defined is only an accessory, and is not what is causing the undesired behavior.

So, what happens is that when you call your inner function inside any of your IIFEs, the x referred to inside the inner function declaration is a captured value of what x had as a value when the function was defined, not the value that x has now in the scope where the function is currently being called. This is what is called lexical scope.

To solve this, you would have to pass the value that you want to console.log() inside the inner function as a parameter to the inner function, as so:

var inner = function(x) { console.log(x); }
// test 1
(function(cb) { var x = 123; cb(x); })(inner);
Sign up to request clarification or add additional context in comments.

5 Comments

What if I am unable to change the callback function to pass in the variable because it is part of a framework? Is there a way to do so? Thanks.
You have to check the framework code to see if it allows some customisation of that callback function (passing a function of your own). Is hard to answer better than that without more context, code example etc =)
Just in case someone was wondering what an "IIFE" is: developer.mozilla.org/en-US/docs/Glossary/….
Strictly speaking I'm not sure lexical scoping is the culprit here (why you cannot access local variables inside a callback). I'm no javascript expert and I have limited understanding and experience with it so I maybe totally off the mark. But for example in Pascal you can access local variables from within locally defined functions and Pascal AFAIR has lexical scoping.
@nyholku In javascript you can also access local variables from within locally defined functions: this is called a closure. The problem is the OP is trying to access a local variable from an externally defined function. You can't do that even in Pascal (though to be fair there are mechanisms in Tcl that allow you to do it - google "tcl upvar" and "tcl uplevel")
4

The only way to access the local variable x in the callback, is to pass it as an argument:

var inner = function(some_var) { console.log(some_var); }; //logs 123
(function(cb) { var x = 123; cb(x); })(inner);

OR

var inner = function(some_var) { console.log(some_var); }; //logs 123
(function(cb) { var x = 123; cb.apply(this,[x]); })(inner);

OR

var inner = function(some_var) { console.log(some_var); }; //logs 123
(function(cb) { var x = 123; cb.call(this,x); })(inner);

FURTHER

Because JS is lexically scoped, trying to reference the local variable after the anonymous function has finished executing is impossible by any other means. If you don't pass it as an argument to make it available elsewhere, JS will see it as non-reachable and it will be eligible for garbage collection.

Comments

0

You could redefine the callback function in the current scope:

var inner = function() { console.log(x); }

(function(cb) { var x = 123; eval('cb = ' + cb.toString()); cb(); })(inner);

// or

(function(cb) { var x = 123; eval('(' + cb.toString() + ')')(); })(inner);

This will not work if the function relies on anything in the scope in which it was originally defined or if the Javascript file has been minified. The use of eval may introduce security, performance, and code quality issues.

1 Comment

This code need to die in a fire
0

Have you tried using events? Emit an event inside the anonymous function, then subscribe to it in your own function somewhere else that carries out your logic.

Comments

0

This helped me understand var more (var vs let in the context of closure) :

Example 1: Using let

function makeFunctionArray() {
  const arr = [];
  for (let i = 0; i < 5; i++) {
    arr.push(function () {
      console.log(i);
    });
  }
  return arr;
}

const functionArr = makeFunctionArray();
functionArr[0]();

Iterations:

  1. Iteration 1 (i = 0):

    arr.push(function () { console.log(i); }); (pushes a function that logs i when called, where i is captured as 0 during this iteration).

  2. Iteration 2 (i = 1):

    arr.push(function () { console.log(i); }); (pushes a function that logs i when called, where i is captured as 1 during this iteration).

  3. Iteration 3 (i = 2):

    arr.push(function () { console.log(i); }); (pushes a function that logs i when called, where i is captured as 2 during this iteration).

  4. Iteration 4 (i = 3):

    arr.push(function () { console.log(i); }); (pushes a function that logs i when called, where i is captured as 3 during this iteration).

  5. Iteration 5 (i = 4):

arr.push(function () { console.log(i); }); (pushes a function that logs i when called, where i is captured as 4 during this iteration).

Return and Execution: const functionArr = makeFunctionArray(); (function array is created and assigned to functionArr). functionArr0; (calls the first function in the array, which logs the captured value of i during its iteration, i.e., 0).

Example 2: Using var

unction makeFunctionArray() {
  const arr = [];
  for (var i = 0; i < 5; i++) {
    arr.push(function () {
      console.log(i);
    });
  }
  return arr;
}

const functionArr = makeFunctionArray();
functionArr[0]();

Iterations:

  1. Iteration 1 (i = 0): arr.push(function () { console.log(i); }); (pushes a function into the array that references the shared variable i).

  2. Iteration 2 (i = 1):

arr.push(function () { console.log(i); }); (pushes another function into the array that still references the shared variable i).

  1. Iteration 3 (i = 2):

arr.push(function () { console.log(i); }); (similarly, pushes another function into the array that still references the shared variable i).

  1. Iteration 4 (i = 3):

arr.push(function () { console.log(i); }); (continues the pattern, pushing a function that references the shared variable i).

  1. Iteration 5 (i = 4):

arr.push(function () { console.log(i); }); (pushes the final function into the array, all functions still referencing the shared variable i). Return and Execution:

Return and Execution: const functionArr = makeFunctionArray(); (function array is created and assigned to functionArr). functionArr0; (calls the first function in the array, which references the shared variable i, whose final value after the loop is 5). Output: 5


My stupid explanation : var i is not evaluated and left until after the calling of the function in the array, it is referenced as i and not assigned a value in the loop(unlike the const it is assigned a value then and there), the value var is assigned is the latest change we make to i, which is finishing the for loop at 4 and then it is dead by the end of the function = 5; it exits the loop at 5; so basically var is the value at which it dies. that's Javascript behavior right there don't blame me.

Practically : if you have a function that references a var variable declared outside of it, you should consider the latest assignment or change to that variable to determine its value when the function is called. This is particularly important in scenarios like loops where the variable may be modified multiple times before the function is invoked. Explanation : With var, there is only one variable i that is shared among all functions pushed into the array. All the functions inside the array form closures that reference the same i. When any function in the array is called (e.g., functionArr0) after the loop has completed, it will print the current value of the shared variable i, which is 5. This behavior occurs because var has function scope rather than block scope, and it does not create a new i for each iteration of the loop. Instead, there's a single i that gets updated in each iteration, and all functions in the array reference the same i.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.