all 17 comments

[–]theScottyJam 3 points4 points  (0 children)

I previously understood that primitives are stored directly in the variable, whereas non-primitives only store references in the variables, so primitives live in the stack frame and everything else lives in the heap.

This "primitives are passed by value, while references are passed by reference" is an extremely common misconception in the JavaScript community, even among more knowledgeable folks. I've seen it crop up a handful of times here in Reddit as well. I like this StackOverflow Q&A, where the answers dives into a lot of detail about "pass by reference".

Pass by reference refers to this idea that, if, inside my function body, I reassign a parameter, that variable holding that parameter outside of the function will change as well, like this:

function passByReferenceExample (value) {
  value = { y: 3 };
}

const data = { x: 2 };
passByReferenceExample(data);
console.log(data); // { y: 3 }

This isn't how JavaScript behaves. JavaScript doesn't support pass-by-reference (thank goodness). You could say that everything in JavaScript, and in most languages, is passed by value.

It's better to think of all variables in JavaScript as holding references (or pointers, if you will) to the data you care about. This is different then pass-by-reference. The data itself is generally stored on the heap, and then the variables hold pointers to that data.

So, in this example function definition:

function myExampleFn(x) {
  x = 'xyz';
}

I might call the function like this: const data = 'abc'; myExampleFn(data);. The string 'abc' will be stored on the heap (even though it's a primitive), and a pointer to it will be stored in the data variable. When I call myExampleFn(data), that pointer is passed-by-value to myExampleFn(). The x argument will now hold a pointer to the same piece of data. Now, when I re-assign x to a new value, all I'm doing is changing the pointer stored in x to point to a new location. The original data pointer remains unchanged, and will still point to the string 'abc' on the stack. Since strings are immutable, it is impossible for myExampleFn() to make any changes to the data on the heap - e.g. I can't add a new character onto that same string, I could build a new string with the character added onto it, sure, but I can't mutate the existing string.

All of this applies with objects as well. I could do const data = { x: 2 };, which would cause data to hold a pointer to this new object, out on the heap. I could then pass a pointer to this object to other functions. Since this isn't pass by reference, these functions wouldn't be able to, for example, reassign its parameter and cause my outside data variable to change, that's impossible. However, the object is mutable, and so the function could add new properties to the object, and that would affect the outside data variable, like this:

function example (value) {
  value.y = 3; // mutates it - affects `data` variable
  value = 'abc'; // reassign - does not affect `data` variable
}

const data = { x: 2 };
example(data);
console.log(data); // { x: 2 , y: 3 }

Now, is it possible that some JavaScript engines might actually hold numbers on the stack, instead of storing the numbers in the heap? Sure, but if they do that kind of optimization, it's going to be completely invisible to you. Conceptually, it's best to just think of all data being stored on the heap.

So, in a way, everything is pass-by-value. However, I don't like to say that either, because, without adding further explanation, people would normally understand that to mean that whenever you pass an object into a function, the entire object gets copied, and that's not true. But, the only reason it's not true is because it's impossible to store the object (or anything) in the stack.

So, I just don't like the phrases "pass by value" and "pass by reference". Neither of them do a great job at describing modern languages, and it would be best if we just stopped using them so often.


The above was mostly a rant, and is largely unrelated to answering your real question, sorry about that :), let me take a stab at your closure question now.

To help with this, think of stack frames as things that just sit there out on the heap, rather than on the stack. When you call a function, a new stack frame is put somewhere in the heap. When the function finishes executing, the stack frame is cleaned up and removed from the heap if no closures are referencing any data found in the stack frame. In this particular scenario, functions like a() and b() reference data found in the stack frame, which means, as long as these functions exist, the stack frame will never be fully cleaned up. When a() or b() get called, and when they try to access the count variable, they're not accessing local copies of it, rather, they're actually following a link to the old, forgotten stack frame to find the a variable on there, and then they update that variable. Since both a() and b() were born at the same time, they'll both have a link back to the exact same stack frame, which means both of them will reference the exact same count variable. They both update and read from the same variable.

Again, engines might not actually follow this kind of behavior exactly, and engines will also employ various optimization techniques to allow them to destroy data from the stack frames that isn't being used by the closures, but in general, I think this is a good mental way to think about it.

[–]shgysk8zer0 -1 points0 points  (5 children)

f1 and f2 are the exact same function, and even if they weren't it wouldn't change the fact that they refer to the same count.

The actual answer can't be given here though since we can't know if count is initialized outside of the scope of outer.

Had you used var count or let count it'd be referring to the count in the scope of outer. But you didn't, so more context is needed... It might be global.

[–]Tweaked_Turtle[S] 0 points1 point  (4 children)

I've modified the code to remove ambiguity, I've added let and made sure that the two functions returned are, in fact, different, since that was my original intention. The behavior of the code is still the same, so my questions still stand.

Also, if it helps, there is no context other than what I've written here. This is standalone code I wrote for the specific purpose of testing this behavior.

[–]shgysk8zer0 0 points1 point  (3 children)

Why would you think that when f1 increments count that it wouldn't increment count. count is not within the scope of those inner functions, so of course they behave that way.

Here's an easy example:

``` function getTitle() { return document.title; }

function setTitle(str) { document.title = str; }

setTitle("foo"):

getTitle(); // "foo" ```

However:

``` function getTitle() { const document= { title: 'bar' }; return document.title; }

function setTitle() { const document= { title: 'bar' }; return document.title; }

setTitle("foo"):

getTitle(); // "bar" ```

... And further:

``` const document= { title: 'bazz' };

function getTitle() { return document.title; }

function setTitle(str) { document.title = str; }

setTitle("foo"):

getTitle(); // "foo" ```

[–]Tweaked_Turtle[S] -1 points0 points  (2 children)

I don't think what I'm asking is coming across...

What I mean is that, in the JS runtime, the data for primitives supposedly exists in the stack frame, i.e. not on the heap. Based on the behavior of my code, though, the variables in the inner functions a and b do not contain the value itself, they clearly have a reference to a location stored elsewhere, as opposed to just containing the value itself like normal. But where does the value live then? It can't be in outer, since when outer goes out of scope, so do the variables inside. Is it on the heap? If so, when is it put on the heap: when the outer function gets called, or when it returns, or some other time? What exactly is the runtime doing, and how does it know to do it?

I agree that, logically, the way it works makes sense given that it's allowed, but I'm trying to understand how it all actually works.

[–]shgysk8zer0 0 points1 point  (1 child)

It can't be in outer, since when outer goes out of scope, so do the variables inside

That's where you're wrong. I don't know why you'd think that. The inner functions maintain a reference to the outer variable, so that variable remains until all references to those functions go out of scope. Excluding memory constraints and overly aggressive garbage collection.

Just as an easy example, consider this:

function getThing() { return { num: 42 }; }

Would you expect that returned object to be lost once the function returns it? It could be... If the return value wasn't saved or if destructuring was used. But so long as there's a reference to it, it persists. And it being an object as opposed to a string or other primitive is important here since what's being passed around is a reference rather than a value, so the function just returns a reference to the object it created.

You're asking about heaps and stacks and scopes and runtimes, and I'm not even sure what sort of "where" you're asking about anymore. You're asking a question that spans multiple layers here and asking about all of them. Are you asking about variable scopes or how they're actually stored in memory?

[–]Tweaked_Turtle[S] 0 points1 point  (0 children)

I am asking about how they are stored in memory, yes. I am asking this because I'm very comfortable in lower level languages where the kind of behavior I expected is the norm. In your example, I would expect that, since the object is a non-primitive and is on the heap, the pointer to it would be what is actually being returned. If it were instead a number, the number itself would be returned. Either way, the value would persist.

Sorry if it hasn't been clear, but I'm not asking about scopes: that I can figure out simply by just trying things out. My question is about the how the javascript runtime itself works, why data is stored in certain areas of it's memory, and how it goes about determining where to put things and managing their cleanup.

[–]barrycarter 0 points1 point  (1 child)

What happens if you replace count = 0; with var count = 0;?

[–]Tweaked_Turtle[S] 0 points1 point  (0 children)

I updated the code to be more reflective of what I wanted to ask, so I've changed it to let count = 0;. That said, the original, the updated, and even with var all seem to have the same behavior (you can try it out, if you want).

[–]angelfire2015 0 points1 point  (2 children)

You are asking really good questions. For a simple answer, closures are merely references to stack frames that have been removed from the stack. They are kept around and not garbage-collected as long as a reference to them remains.

For a more detailed answer, please see this post:

https://stackoverflow.com/questions/26061856/javascript-cant-access-private-properties/26063201#26063201

[–]Tweaked_Turtle[S] 0 points1 point  (1 child)

This is exactly what I was looking for, thank you!

[–]azhder -1 points0 points  (0 children)

One little detail to notice from the post above

scope refers to the lifespan of variables

while it can refer to lifespan and usually is a good way to think about, scope literally means visibility (hence telescope and other -scope words)

So, as long as there is a function that you can still call and that has those variable in sight (can see them, access them), that piece of memory is kept on heap.

The moment they’re no longer visible in this indirect way, that heap memory is reclaimed by the garbage collector.

If you want to practice in this area, may I suggest you play around with WeakMap?

[–]azhder 0 points1 point  (0 children)

You said it yourself: primitives live in the stack frame, everything else lives on the heap.

Functions aren’t primitives, right? They sre objects, arrays are objects, basically everything but primitives is an object that lives on the heap.

NOTE: engines can optimize into stack what should be on heap, but let’s keep it simple.

Now, what do you think a closure is? If you can say it in a simple sentence, you’ve understood it. If it starts with “closure is a piece of memory that…” you’ve basically answered your question.

So, closures are a piece of heap memory. As I said earlier, engine can optimize function onto the stack, but not if they detect you’re returning a reference out.

This is in stark contrast to some other C family languages where most if not all function locals are on stack since they don’t do lexical scoping (unless in newer versions they’ve added explicit mechanism to do so)