You Don't Know JavaScript, Scope and Closures Chapter 5 Question

Hello,

I’m having some trouble grasping a particular example of closures from the YDKJS series of books. In chapter 5 of the book Scope & Closures, Kyle Simpson defines closures as:

Closure is when a function is able to remember and access its lexical scope even when that function is executing outside its lexical scope.

Under the section “Nitty Gritty”, I think I see how closures are happening in the first and third examples of the section. However, his second example is the following:

function foo() {
var a = 2;

function baz() {
    console.log( a ); // 2
}

bar( baz );
}

function bar(fn) {
 fn(); // look ma, I saw closure!
}

I am having a hard time in seeing how closures are working in this case. I understand that baz() does indeed have a closure over the lexical scope of foo() , and thus can still access the variable a even when executed outside of its lexical scope (per the definition). I also understand that baz is being passed as an argument to bar(), which just serves to actually execute the function that was passed (i.e. baz).

I suppose my confusion is: is baz really being executed in the lexical scope of bar? Isn’t baz still being executed within the lexical scope of foo (where it was declared originally), making this a strange example of closure? I guess I am confused by the wording of the definition “…outside its lexical scope”. I feel like I am missing something important in terms of verbage that is tripping me up. Can anyone help step me through this?

Thanks!

Here is the link for reference:
Link to book on GitHub

2 Likes

My understanding here is, a function’s call site has nothing to do with its closure over some lexical scope. While inside foo you call bar(baz), baz here is essentially executed in a different scope from foo.

This simply refers to where it was defined/created and what was visible to it at the time.

By passing it to bar, where it is executed, the code is executed outside of its lexical scope. From MDN:

This is an example of lexical scoping: in JavaScript, the scope of a variable is defined by its location within the source code (it is apparent lexically) and nested functions have access to variables declared in their outer scope.

Lexical simply means:

of or relating to the words or vocabulary of a language

So lexical scope is the scope present where the function was written in the code - it is called lexical scope because you can derive what the scope is by reading the words of the source code.

When you execute it via bar, it is executed there, but but only has available to it what was present in its own lexical scope.

Take what I say with a grain of salt, since I just finished this book myself a few days ago, but I might be able to help. Maybe someone with more knowledge will chime in and explain it better, or tell me I’m wrong, but I’ll take a shot.

The way I understand it is that the function bar() is not declared in the scope of the function foo(). If you execute the function foo() above with the function bar(fn) part deleted, it returns undefined, because bar was never actually declared in the foo() function. Because of that, baz() is not actually being executed inside foo(), even though it was passed to bar(). It’s only executed afterward (as fn) by the bar(fn) declaration.

The reason this is an example of closure is because the baz() function is only being executed after the declaration of bar(), outside of the lexical scope of foo().

Does that make any better sense? Also, anyone with a better understanding, please correct me if I’m wrong, it’ll be helpful for all of us!

2 Likes

Thank you for your help everyone!

So I think I am starting to get it. Let me see if I can type it out to see if I understand correctly. I think what is tripping me up is determining where a function actually executes versus where it is called.

Lexical scope, according to the YDKJS, is the scope created at lex time and is formed based on where you declare your functions (and blocks of code, in the case of block scoping) when you write your program. Since bar() is declared outside of foo(), the “scope bubble” (see this ) around bar() is separate from the “scope bubble” around foo.

When you call bar() within foo, foo looks for bar in its own lexical scope first, but doesn’t find it. It then looks in the global scope and finds the bar declaration. I guess my question is, is baz executed in the global scope (since that is where bar is declared) or is it executed in the scope of bar, since bar takes baz as an argument. I guess this part is academic, since the key point is that it is NOT executed in the lexical scope of foo but the closure baz has around the scope of foo allows it to still have access to the parameter a.

Am I close here? Am I overcomplicating or overthinking this?

Thanks!

baz is indeed executed in the scope of bar, but it doesn’t matter where baz is called/executed. It will always have closure over the scope of foo, since baz was declared/written in that scope.

thanks @kevcomedia

I understand the existence of the closure here, it’s seeing it “in-action” that is confusing to me in this example.

@fiftyfivebells

thanks for your explanation! So functions are executed where they are declared and NOT where they are called. Is that the basic idea?

1 Like

No, the basic idea is that closure does not depend at all on where the function is called. The closure (or lack of thereof) depends solely on where the function is declared.

Thanks @KamilCybulski
I’m not confused about whether closure exists or not. I understand that the inner function has a closure over the scope of the enclosing function.

What I am confused about is how we end up seeing/leveraging the closure in action here.

In any event, it seems the answer is that because bar was declared outside of foo, and baz is being passed to bar, baz is executed in bar and can still remember the lexical scope of foo (which allows it to access a ) because it has a closure over the scope of foo

Thanks everyone!

1 Like

Your summary on Dec 31st is correct.

1 Like

I can recommend you this resource in addition. In order to really grasp some new concept usually you have to explore different origins. It took a lot of time to truly understand that. http://javascriptissexy.com/understand-javascript-closures-with-ease/

To really appreciate why closures are so remarkable, you need to know how JavaScript JIT compiler works. But this knowledge is pretty useless for programmers, for the same reason as the knowledge how CPU works internally is pretty useless for a PC user.

The practical knowledge is enough. If you can explain why this code outputs 4 instead of 7 it’s just what a JavaScript developer should know about closures.

    var watcher = {}

    function add(n) {
    	var state = 0
      
    	function addToState() {
      	    state = state + n
        }
      
        watcher.showState = function () {
      	    console.log(state)
        }
      
        return addToState
    }

    var addOne = add(1)
    var addTwo = add(2)

    addOne()
    addTwo()
    addOne()
    addTwo()
    addOne()

    watcher.showState() // outputs 4 instead of 7, why???
    // how to fix this code?
    // which functions are closures?
    // which variables are closed in a given closure?
    // how many closures were created during the runtime?

jsfiddle

If anyone would like to answer, please use [spoiler]This text will be blurred[/spoiler] tags .

3 Likes

[spoiler]Okay, after playing around with the code for a bit, I think I have my guess as to why it outputs 4 instead of 7

When you invoke add(1) and assign the returned function to the variable addOne, a new scope is created where state = 0. The tricky part seems to be the watcher.showState function. When addOne is first declared, the watcher.showState function is added to watcher and has a scope closure over the scope of addOne = add(1).

However, on the next line, you declare a new variable var addTwo = add(2). Now, within add(2), you are now referencing watcher.showState. Now watcher.showState has a scope closure over addTwo = add(2) (where the state is 0 as well) instead of add(1).

Thus, even when you call addOne(), the state variable in that scope is being incremented by one, while addTwo() increments (by 2) the state variable within a different scope!

Since addTwo was declared second, the watcher.showState() call exercises its scope closure over the state variable in add(2), which returns 4 since addTwo has been invoked twice (2+2=4).

Sorry for the long and convoluted explanation, I am still learning how to communicate this stuff.

Cliffs:
-add(1) and add(2) create two different scopes to close over
-add(2), being declared second, has within it the watcher.showState function
-since it is declared second, add(2) makes watcher.showState close over add(2) and
NOT add(1)

Am I close?
[/spoiler]

BTW thanks for the exercise, this was fun!

You’re right. Because of closures, at some point in the script execution there are two independent state variables there at the same time:

  • one can be accessed by addOne() and a function that was initially assigned to watcher.showState
  • and the other can be accessed by addOne() and a function that was assigned to watcher.showState replacing the previous function.

Both states can be shown by slightly modifying the code:

    function add(n) {
    	var state = 0
      
    	function addToState() {
      	    state = state + n
        }
      
        addToState.showState = function () {
      	    console.log(state)
        }
      
        return addToState
    }

    var addOne = add(1)
    var addTwo = add(2)

    addOne()
    addTwo()
    addOne()
    addTwo()
    addOne()

    addOne.showState() // outputs 3
    addTwo.showState() // outputs 4

The simplest way to make this code output 7 is to move state variable to the main scope:

    var watcher = {}
    var state = 0

    function add(n) {

      function addToState() {
        state = state + n
      }

      watcher.showState = function() {
        console.log(state)
      }

      return addToState
    }

    var addOne = add(1)
    var addTwo = add(2)

    addOne()
    addTwo()
    addOne()
    addTwo()
    addOne()

    watcher.showState() // outputs 7

Because in JS functions are first-class objects, they can be executed out of their lexical scope.
I like to call a function that has been moved out of its lexical scope an escapee function.
Here’s an example of escapee:

    function prisonbreak() {

      function prisoner() {
        console.log("I'm free!")
      }

      return prisoner
    }

    // calling prisoner() here would throw an error because it's out of scope

    var escapee = prisonbreak() // prisoner() breaks out of its scope
                                // and becomes known as escapee
    escapee() // now it can be accessed

escapee is always accompanied by at least one survivor scope. By default, when a function is invoked a new scope is created and lasts till the function completes its work. survivor scopes outlive their functions because they are being attached to escapee functions.
Example:

function annihilation() {
   console.log('annihilation started')
   var father = 'Anthony'
   var mother = 'Alice'
   var child = 'Jon'
  
  function rescueSquad() {
  	console.log(father, 'survived')
    console.log(mother, 'survived')
    console.log(child, 'survived')
  }
  
  return rescueSquad
}

var escapee = annihilation()
console.log('annihilation completed')

// even though annihilation has completed
// and therefore all its local variables should be destroyed
// thanks to rescueSquad() that escaped from annihilation()
// all locals are still alive
escapee()
3 Likes

back to the question you asked, this is what I think happened:

  1. the function declaration is getting hoisted to the top of the code snippet, which means this is what the code actually looks like:

function bar(fn){fn();}
function foo(){
    var a =2;
    function baz(){console.log(a);}
    bar(baz);}

  1. We displayed the value of a using function baz() then we passed function baz() into function bar() and because we’ve already declared what function bar() does , we got the end result.

Is this the correct understanding of this code snippet?

Wait… i think i am wrong… function foo(){...} is a function declaration too… so… ignore 1.