Lexical scope/closures in javaScript
Asked Answered
G

4

9

I understand functions in 'js' have lexical scope (i.e. functions create their environment (scope) when they are defined not when they are executed.)

function f1() {
    var a = 1;
    f2();
}

function f2() {
    return a;
}
f1(); // a is not defined

When I run just 'f()' it returns the inner function. Which I get, that's what 'return' does!

function f() {
    var b = "barb";
    return function() {
        return b;
    }
}
console.log(b); //ReferenceError: b is not defined

Why do you get 'ReferenceError: b is not defined?' But doesn't the inner function above have access to it's space, f()'s space etc. Being that 'b' is being returned to the global space, wouldn't the console.log() work?

However when I assign 'f()' to a new variable and run it:

 var x = f(); 
 x();// "barb"
 console.log(b); //ReferenceError: b is not defined

This returns 'b' which is "barb", but when you run console.log() again you'll get 'ReferenceError: 'b' is not defined'; Isn't 'b' in the global scope now since it has been returned? SO why didn't 'x()' also return the inner function just like 'f()' did?

Gemmell answered 24/6, 2013 at 15:37 Comment(2)
You don't seem to have as much of a problem with scope as you do with how return statements workAgglutinative
You're returning the value of b, b itself isn't returnedHeliopolis
F
57

You, my friend, are thoroughly confused. Your very first statement itself is wrong:

functions create their environment (scope) when they are defined not when they are executed

Actually it's the opposite. Defining a function doesn't create a scope. Calling a function creates a scope.

What's a scope?

To put it simply, a scope is the lifespan of a variable. You see, every variable is born, lives and dies. The beginning of a scope marks the time the variable is born and the end of the scope marks the time it dies.

In the beginning there's only one scope (called the program scope or the global scope). Variables created in this scope only die when the program ends. They are called global variables.

For example, consider this program:

const x = 10;       // global variable x

{                   // beginning of a scope
    const x = 20;   // local variable x
    console.log(x); // 20
}                   // end of the scope

console.log(x);     // 10

Here we created a global variable called x. Then we created a block scope. Inside this block scope we created a local variable x. Since local variables shadow global variables when we log x we get 20. Back in the global scope when we log x we get 10 (the local x is now dead).

Block Scopes and Function Scopes

Now there are two main types of scopes in programming - block scopes and function scopes.

The scope in the previous example was a block scope. It's just a block of code. Hence the name. Block scopes are immediately executed.

Function scopes on the other hand are templates of block scopes. As the name suggests a function scope belongs to a function. However, more precisely, it belongs to a function call. Function scopes do not exist until a function is called. For instance:

const x = 10;

function inc(x) {
    console.log(x + 1);
}

inc(3);         // 4
console.log(x); // 10
inc(7);         // 8

As you can see every time you call a function a new scope is created. That's the reason you get the outputs 4, 10 and 8.

Originally, JavaScript only had function scopes. It didn't have block scopes. Hence if you wanted to create a block scope then you had to create a function and immediately execute it:

const x = 10;         // global variable x

(function () {        // beginning of a scope
    const x = 20;     // local variable x
    console.log(x);   // 20
}());                 // end of the scope

console.log(x);       // 10

This pattern is called an immediately invoked function expression (IIFE). Of course, nowadays we can create block scoped variables using const and let.

Lexical Scopes and Dynamic Scopes

Function scopes can again be of two types - lexical and dynamic. You see, in a function there are two types of variables:

  1. Free variables
  2. Bound variables

Variables declared inside a scope are bound to that scope. Variables not declared inside a scope are free. These free variables belong to some other scope, but which one?

Lexical Scope

In lexical scoping free variables must belong to a parent scope. For example:

function add(x) {         // template of a new scope, x is bound in this scope
    return function (y) { // template of a new scope, x is free, y is bound
        return x + y;     // x resolves to the parent scope
    };
}

const add10 = add(10);    // create a new scope for x and return a function
console.log(add10(20));   // create a new scope for y and return x + y

JavaScript, like most programming languages, has lexical scoping.

Dynamic Scope

In contrast to lexical scoping, in dynamic scoping free variables must belong to the calling scope (the scope of the calling function). For example (this is also not JS - it doesn't have dynamic scopes):

function add(y) {   // template of a new scope, y is bound, x is free
    return x + y;   // x resolves to the calling scope
}

function add10(y) { // template of a new scope, bind y
    var x = 10;     // bind x
    return add(y);  // add x and y
}

print(add10(20));   // calling add10 creates a new scope (the calling scope)
                    // the x in add resolves to 10 because the x in add10 is 10

That's it. Simple right?

The Problem

The problem with your first program is that JavaScript doesn't have dynamic scoping. It only has lexical scoping. See the mistake?

function f1() {
    var a = 1;
    f2();
}

function f2() {
    return a;
}

f1(); // a is not defined (obviously - f2 can't access the `a` inside f1)

Your second program is a very big mess:

function f() {
    var b = "barb";

    return function() {
        return b;
    }
}

console.log(b); //ReferenceError: b is not defined

Here are the mistakes:

  1. You never called f. Hence the variable b is never created.
  2. Even if you called f the variable b would be local to f.

This is what you need to do:

function f() {
    const b = "barb";

    return function() {
        return b;
    }
}

const x = f();

console.log(x());

When you call x it returns b. However that doesn't make b global. To make b global you need to do this:

function f() {
    const b = "barb";

    return function() {
        return b;
    }
}

const x = f();
const b = x();
console.log(b);

Hope this helped you understand about scopes and functions.

Fredrick answered 24/6, 2013 at 17:15 Comment(6)
I've been struggling with understanding the differentiation despite reading Eloquent Javascript. Thank you so much for the thorough explanation.Clearsighted
@Aadit M Shah: Your answer is contradictory, at best confusing. First you stated "Defining a function doesn't create a scope. Calling a function creates a scope". Fine. Then in your examples, where the function is defined, then later called, you put the same comment "create scope" for both of them. That conflicts with the statement you made "Defining a function doesn't create a scope". Then you throw in the term "calling scope" without introducing it. Is it scope of the calling function? You probably understand the topic, but I'd clean up a little bit.Ramachandra
@Khnle-Kevin When you create a function no scope is created .When you call a function then a lexical scope is created. When you create a function the function is kept inside the execution context. Execution context is in memory space created when running the program. If you still find it confusing watch the first lesson on Scopes and Closures at Udacity udacity.com/course/object-oriented-javascript--ud015Interspace
But Anand, this is what you wrote and I quote word for word, so please don't edit your answer. On the 1st sentence, "When you create a function no scope is created". On the 3rd, "When you create a function the function is kept inside the execution context". Therefore according to you "When you create a function, no scope is created and the function is kept inside the execution context". I do find what you write very confusing.Ramachandra
@Khnle-Kevin Anand did not write this answer. I did. My name is Aadit M Shah. There is only one Aadit M Shah in the world. Also, I edited my answer after your first comment (six months ago). See the revision history of my answer. Now it doesn't contradict itself. Cheers. =)Fredrick
Thought I might add that you can block scope with let now. (which is a mighty fine addition).Whereupon
I
5

You get, "ReferenceError: b is not defined" because "b" is not defined where your console.log() call is. There's a "b" inside that function, but not outside. Your assertion that "b is being returned to the global space" is false.

When you invoke the function returned by your "f()" function, that will return a copy of the value referenced by that closure variable "b". In this case, "b" will always be that string, so the function returns that string. It does not result in the symbol "b" becoming a global variable.

Imparipinnate answered 24/6, 2013 at 15:40 Comment(3)
I see, the only way "b" could have been seen by the console.log is if you omitted the 'var', right? The only question I have is why didn't 'x()' also return the inner function just like 'f()' did when I ran it? After all when I assigned 'f()' to the x variable, isn't it essentially doing the same thing as 'f()' (i.e. spitting out return function() { return b; }Gemmell
@antonio You never actually ran the function f you just declared it. When you assign it to x, you actually run f and then assign the returned element (Which happens to be an anonymous function) to x. Which is why when you run x you actually have a function.Playsuit
@Playsuit yes I assumed that he just left that step out. If the function "f" and the function it returns aren't ever invoked, well then they won't do anything :-)Imparipinnate
F
2

But doesn't the inner function above have access to it's space, f()'s space etc.

Yes it has. It accesses the b variable and returns its value from the function.

Being that 'b' is being returned to the global space

No. Returning a value from a function is not "making a variable available in the caller scope". Calling the function (with f()) is an expression whose result is the value that the function returned (in your case, the unnamed function object). That value can then be assigned somewhere (to x), a property of it can be accessed or it can be discarded.

The variable b however stays private in the scope where it was declared. It is not [getting] defined in the scope where you call console.log, that's why you get an error.

What you want seems to be

var x = f();
var b = x(); // declare new variable b here, assign the returned value
console.log( b ); // logs "barb"
Facetious answered 24/6, 2013 at 15:41 Comment(0)
A
2
    function f1() {
          var a = 1;
          f2();
          }

    function f2() {
       return a;
    }
    f1(); // a is not defined
  1. f2(); does not knows about the a,because you never passed 'a' to it,(That's Scope are created when the functions are defined).Look function f2() would have been able to acess a if it was defined inside f1();[Functions can access the variables in same scope in which they are "DEFINED" and NOT "CALLED"]

    function f() {
       var b = "barb";
       return function(){
                         return b;
                        }
    }
    console.log(b); 
    
  2. First of all You Need to Call f(); after executing f(); it would return another function which needs to be executed. i.e

    var a=f();
    a();
    

    it would result into "barb" ,In this case you are returning a function not the var b;

    function f() {
       var b = "barb";
       return b;
                 };
    
    console.log(f());
    

    This would print barb on screen

Adumbral answered 3/2, 2014 at 14:24 Comment(2)
wow thanks, your '"which they are DEFINED" and NOT "CALLED"' saved me. Im learning closure and I was so confused after reading his question until I read that line from youWhereby
You're correct on #2; however, note that you can simply execute console.log(f()()) to print out "barb" (the OP's "f" function is, of course, a bad design pattern in this case -- just pointing out a more concise syntax if one is trying to get a value from a function which returns a function).Gord

© 2022 - 2024 — McMap. All rights reserved.