【 introduction 】
In previous Java lessons, we learned that Java divides memory into two types: stack memory and heap memory. Some basic types of variables defined in functions and reference variables to objects are allocated in the stack memory of the function. Just in the recent learning process, I know something about the memory mechanism in JavaScript, so here to do a share, welcome you to exchange comments in the comments section.
This article mainly talks about the following contents:
- About JavaScript language types
- What is an execution context
- JavaScript’s memory mechanism
- Why stack and heap?
- The relationship between stacks and closures
[text]
About JavaScript language types
Before we talk about JavaScript’s memory mechanism, let’s talk about JavaScript as a language type. We all know that language is divided into strong language, weak language, dynamic language, static language, they have their own characteristics. In a static language, you need to check the type of the variable before using it. In a dynamic language, you need to check the type of the data during execution. You can use a variable to hold different types of data. And languages that support implicit type conversions are called weakly typed languages. What is implicit type conversions? For example, C can convert an int type to a Boolean type, which is called implicit conversion. JavaScript, like C, is a weak language. In JavaScript, we do not need to declare the type of a variable, because the engine (V8) automatically checks its data type and identifies it when it is running. Here we refer to a diagram to make it easier to understand
What is an execution context
An execution context is an abstraction of the context in which code in JavaScript is parsed and executed; any code is run in the execution context. There are three main types of execution contexts in JavaScript:
- Global execution context: The underlying context, where all code that is not inside a function is in the global context. A program has only one global context
- Function context: When a function is called, a new context is created for the function. Each function has its own execution context, and a program can have multiple function contexts
- Eval execution context: Code executed inside the Eval function also has its own execution context, which is not explained in this article
We will use the concept of execution context a lot in the following sections
JavaScript’s memory mechanism
In the introduction to this article, we mentioned that the Java language’s memory mechanism is divided into heap and stack, and JavaScript’s memory mechanism is also divided into heap and stack, but it is different from Java. Stacks in JavaScript only store small data of primitive types, such as Undefined, Null, Boolean, Number and String, Symbol, etc. Stacks in JavaScript are used to store reference types. For example, Object, Array, Function, etc., whose data is stored in the heap space, only the corresponding reference address is stored in the stack space.
Let’s look at the call stack process in JavaScript with code and images:
Function foo(){var a =1 var b =a a = 2 console.log(a); //2 console.log(b); //1} foo() function bar(){var c = {name:' console.log '; //1} foo() {var c = {name:' console.log '; / / li si console. The log (d); } bar()Copy the code
We can see that in the first piece of code, we assign b to a and then change the value of A, but in the output, b is still a value of 1. In the second code, the assignment of c is changed to Li Si, but the output of C and D are the same value. Why? Let’s analyze it through the picture below:
First, when the JavaScript engine first encounters your code, it creates a global execution context and pushes it onto the current execution stack. Every time the engine encounters a function call, it creates a new execution context for that function and pushes it to the top of the stack. A program has only one global execution context, but it can have multiple function execution contexts, all of which follow the “last in, first out” rule in the stack.
As you can see from the diagram, the engine encounters our code, creates a global execution context and pushes it to the bottom of the stack as required. Then it goes down and encounters foo and bar respectively, generating two function execution contexts, which we write together in the diagram (I don’t want to redraw the diagram, There are really two separate the function of the execution context and the bar above the foo), with a value of 1, it is a primitive type, so the data is stored in a stack, we will b to assignment is actually a deep copy, all assignments are deep copy of the original value, at this point b got a value of 1, get a value of 1 b and a doesn’t matter at this time, They’re independent entities, but they’re going to the same address so they’re both going to get a 1, so we’re going to change the value of A to a 2, and now A is going to go to a new address and get the value of 2, and B is going to go to the same address, which is where the 1 was, so let me draw a picture here to make sense of it.
C is the object type, and its value is stored in the heap. Only the reference address 1002 is kept in the stack. At this point, d is assigned a value of the object type, which is called a shallow copy. The shallow copy copies its reference address 1002. The address 1002 of c and D has not been changed, so in the end, c and D both output Li Si.
This is the memory mechanism in JavaScript, putting different types of data in different places, so what’s the point of doing that?
Why stack and heap?
We can’t help but ask, what is the point of this, even if we understand JavaScript’s memory mechanism? Can all data be stored directly on the stack?
The answer is clearly no. JavaScript engines need to use the stack to maintain state during program execution context (environment), if the stack is too big, all data on the stack, stack data access is particularly convenient, will affect the efficiency of context switching, because the data is stored in a stack of, first compiled into the stack, after compiled into the stack, performed to destroy operation, But in this case, the function execution context of the advanced stack can only be destroyed after the function execution context of the advanced stack is destroyed. If the stack is too large, the context switching efficiency will be affected, and then the execution efficiency of the whole program will be affected.
The relationship between stacks and closures
We can see the following code directly:
Function foo() {var innerBar = {setName: const test2 = 2; var innerBar = {setName: Function (newName) {myName = newName // reference external variables}, getName: function() {console.log(test1); Return myName}} return innerBar} var bar = foo() bar.setName(' tao ') console.log(bar.getName());Copy the code
Here we can see very clearly that in this code, there’s a closure. So, how does the allocation of worth space within the code differ in this closure case from the normal case? As you can see, myName, test1, and test2 are all primitial-type variables in the code, which should be stored on the stack according to the JavaScript allocation mechanism we mentioned earlier, but they are not. Of the three variables, only test2 is stored on the stack. This is because variables referenced by closures go to a closure object in the heap called closure and store the data there, leaving only a reference address in the stack to the closure object in the heap. Because of the closure, foo’s execution context can’t be destroyed, so test2 is still on the stack, but we can’t call it from outside, but it does exist.
That’s it for now, and I’ll write another detailed article on closures next time, when I have a better understanding of closures.