Jonas: To finish this section,
we're gonna do one more deep dive
into yet another fundamental aspect
of how JavaScript works behind the scenes,
and that's memory management.
In this first of two parts of memory management,
we're gonna learn all about primitives, objects,
and more importantly, object references.
Now, first of all,
what does memory management actually mean?
Well, memory management in the context of JavaScript
is how the JavaScript engine allocates space in memory
for creating variables and later frees up that memory space,
which was taken up by variables that are no longer needed
so that our applications run smoothly
and efficiently without running out of memory.
So basically, this lecture tries to answer the question,
how and where are variables created in JavaScript?
Now, unlike lower level programming languages
like C or C++ where developers actually have
to manually reserve pieces of memory for their variables,
JavaScript automatically manages the memory
behind the scenes for us.
This makes writing code a lot easier and faster,
and it reduces the risk of introducing bugs
like memory leaks.
So this sounds great, but how does it all work?
Well, to give you a broad overview, every single value
that we create in our applications goes through something
that we call a memory lifecycle.
So in this lifecycle, first, a piece of memory is allocated
whenever we create a new value.
For example, to assign that value
to a new variable in our code.
So essentially, each time we declare a variable
with a new value, no matter if it's a single value
or a huge object, the JavaScript engine
will automatically reserve a piece of memory
to store that value.
So the value that is being held by the variable
that we just created.
In this example, it's to store 23.7.
But again, it could be an object or a function
or whatever we want.
Then as the app runs in the user's browser,
the allocated piece of memory is used
whenever the stored value is being accessed,
for example, to write, read, or update the value.
So this part is pretty obvious,
but it's important to mention that
this is also part of the memory lifecycle.
And finally, what happens when the value
is no longer needed?
Well, in that case, at the end of a value's lifecycle,
the memory it occupies gets released.
Or in other words, when the value is not necessary anymore,
it's deleted from memory, and the freed up memory
can then be used for new future values.
And we're gonna learn later how exactly
that happens behind the scenes.
But for now, in this lecture,
let's start to look at the first step in more detail,
which is memory allocation.
Now, the main thing to understand about memory allocation
in JavaScript is the fact that
for different types of values,
memory is actually allocated in different places
in the JavaScript engine.
But what are those different types of values?
Well, remember from earlier in the course
that in JavaScript, values are either
a primitive value or an object.
So primitive data types are numbers, strings, Booleans,
undefined, null symbols, and BigInts.
Then everything else is objects.
So that includes objects created with the object literal,
arrays, and even functions.
So all those are in fact objects.
So those are the two types of values
that exist in JavaScript.
Next, we need to remember about
the JavaScript engine itself.
So the engine has two main components,
the call stack, where functions are executed,
and the heap where objects are stored in memory.
And from this, we can already start to understand
how memory is allocated for different data types.
So again, as I just mentioned,
all our objects will get stored right in the memory heap,
or in other words, it's the memory heap
where memory is allocated for objects.
On the other hand, all primitive values are gonna be stored
in the call stack where our functions run.
And in detail, primitive values are actually stored
in the execution context in which they are created.
Now, there might be some exceptions to those rules
because modern JavaScript engines are highly sophisticated
and dynamic so that for example,
they might store an extremely long string,
which is actually a primitive in the heap
so that the call stack where strings are usually placed
can be optimized.
But in general, as a mental model,
this is how memory allocation works,
and it's really all you need to know.
So in summary, memory for objects is allocated in the heap
and for primitives in the call stack,
but that's actually not all.
So besides primitives and objects,
there are also something that we call references to objects,
and these are also stored in the call stack.
Now what do I mean by object references?
Well, let's find out because references to objects
are one of the most important things
that you need to understand from this video.
And I would actually say the most important thing.
So here we have a piece of code that
we'll go through together and analyze
exactly what's gonna happen in both the call stack
and the heap as the code runs.
So when the code starts executing in the first line,
the global execution context is placed on the call stack
and the value of the variable name is set to Jonas
right in the variable environment of this execution context.
That's because Jonas is just a string, which is a primitive,
and so therefore this value is stored
right here in the call stack.
Now technically, these primitive values are stored
in the variable environments of execution contexts,
but since these actually live in the call stack,
we just say that primitive values are stored in the stack.
Next up, the age is calculated by calling calcAge,
which is gonna create and place a new execution context
on the top of the stack as always.
And so now the values created in this function
will be stored in their corresponding execution context.
So again, in the call stack, because the variables
now and x both contain numbers,
and therefore primitive values.
And primitive values belong in the stack as we already know.
Then the function returns and its execution context
pops off the stack.
And with it, the values that it was holding
in its variable environment are also gone.
The return value of 46 is stored as the age variable
in the global execution context as well.
Next, we declare yet another variable,
this one called newAge, which is simply set
to the same value as age.
So the value of 46 will also get stored
in the stack as newAge here.
Then in the next line, we increase the value of newAge
by one, making it 47.
Now as we update newAge from 46 to 47,
the primitive value stored in age stays of course at 46.
So again, age, which is where
the original value came from, remains unchanged.
So each of the variables holds its own copy
of the value at all times.
Now, this is pretty intuitive,
but it's very different from how objects work
as we'll see in a minute.
And so speaking of objects,
we create an object right here
in the next line called location.
And where are objects stored in the engine?
That's right, this object will live in the heap.
So that's where the engine stores objects.
But now if we think about this,
how will our code actually know
that this object is supposed to be called location?
Or in other words, how will we be able
to use this object in the rest of our code?
Well, that's where the concept of reference comes into play.
So the way this works is that in the execution context,
the variable location will actually hold
a reference to the object.
So again, location will not be the object itself,
but just a reference to the object in the heap.
In technical terms, it's the memory address of the object
that will be stored as the value of location.
But that's not really that important.
What you need to understand is that variables
in a call stack do not hold objects themselves,
but references pointing to these objects.
Now, to us developers, this mechanism is completely hidden.
So on the surface, it does look as if a variable
is actually holding the object itself,
when in fact it's only storing the reference
to the object behind the scenes.
And that's actually a huge difference as we're about to see,
because as we move on to the next line, here,
we create a copy of the location object called newLocation.
And so here is where it gets really interesting.
So if a variable like location simply contains a reference
to the object, then as we copy the variable,
what we're actually copying is only the reference
to the object, but not the object itself.
Or in other words, when we set newLocation
to the same value as location,
then we're simply copying the reference
because it's the reference
that is stored in the variables in the stack.
So in this case, this means that newLocation and location
are pointing to the exact same object in the heap.
And this is very different from how primitives work,
where each variable actually holds its own copy
of the primitive value
because there are no references involved at all.
And this idea of object references has huge implications.
So check this out.
In the next line of code,
we're mutating the new location object
by setting its city property to Lisbon.
So this is what that looks like in the heap.
So far, so good, right?
Well, not so fast because what happens if we try
to check out the original location object?
And I think you can already start to see the result here
just by looking at the heap and the references.
So as we log the original location to the console,
we now get the mutated object here
where the city property is also set to Lisbon.
And this actually makes sense, right?
If both location and newLocation contain
the exact same reference that points to
the same object in the heap,
then it makes perfect sense that changing the object
through one of the variables will also get
reflected in the other one.
Again, because location and newLocation
are in fact the same object.
In our case here, it means that mutating newLocation
will destroy the value of the city property
in the original location object.
And I hope that you are still able
to follow me at this point,
but if this is super confusing, then don't worry.
We'll actually explore this exact concept in practice
by writing some code in the next lecture.
But now just for the sake of completeness,
the calcAge function is actually behind the scenes
also just an object, which means that this function
also gets stored in the heap.
And so then the variable calcAge
will simply hold a reference to this object as well.
So basically exactly the same thing
that we just discussed a moment ago.
Now, this function had actually already been hoisted,
so it actually was already in the heap all this time.
Otherwise we couldn't have used this function
right in line two.
But we're just putting it down here at the end
for educational purposes.
Now, right, now to make sure that you really understood
this super important part, as I just mentioned,
let's jump back to our code editor
and play around with this idea of object references
and also learn about shallow and deep object copies.