Recently, I am learning some underlying mechanisms of JS, and from the perspective of these underlying mechanisms, to learn those seemingly simple knowledge, to explore their underlying implementation. This will be a series of articles, driven by a series of questions, of course, there will be questioning and expansion, the content is more systematic and easy to understand, for beginners and intermediate readers will be a sublimation, advanced readers can also review and consolidate with this series.

The sharing of the theme is JS memory mechanism and depth of copy implementation, is a logical link, simple language but the content is very deep content, I believe that different levels of players have different help.

JS memory mechanism: how is data stored?

If you put this question into Baidu, the articles on the Internet are basically like this: basic data types are stored in stack memory, reference data types are stored in heap memory. So, I don’t know if you’re thinking about the following questions:

1. Why is data stored the way it is? Who will finish it? How was it done?

2. Why is JS memory mechanism designed like this

3. Why can’t we put it all on the stack? What’s the problem with that?

4. Is this statement completely true?


First, the basics:

Basic and reference data types

In particular, the JS data types fall into two broad categories:

  • Basic data types: String, Boolean, Number, Undefined, Null
  • Reference data type: Object(Array, Date, RegExp, Function)

Differences between the two:

  • Basic data types are stored in the stack, and reference data types are stored in the heap
  • In JS, assignments for basic data types completely assign to the value of the variable, while assignments for reference types are assigned to the address

Stack memory and heap memory

In the JS memory mechanism, a stack and a heap are created in memory for storing data, as shown in the figure below:

The difference is that the heap is larger than the stack, and the stack is faster than the heap. And one of the obvious drawbacks of heap storage is that it takes time to allocate and reclaim memory.


Next, in view of the previous questions, here will be one by one:

1. How exactly are these two types of data stored in memory?

Take the following code snippet as an example:

function foo(){
    var a = 1
    var b = a
    var c = {name:'Aaron_hj'}}function bar(){}
foo()
bar()
Copy the code

Here, the function foo() is first compiled by the JS engine before it is called. What happens during compilation? If you don’t know, you can read this article: precompile JS. After compiling, the execution context of function foo() is pushed on the stack:So, you can’t see that in function foo(), the base data type is compiled and written into the execution context, and since the execution context of foo() is being pushed, we say that the base data type is stored in stack memory. For reference data types, the value is stored in heap memory, keeping only its reference address in stack memory.

2. Who is the operator? Why do you do that?

Such data storage, the operator behind the nature is JS V8 engine. After compiling the V8 engine, the data is stored as shown above. As we said above, the stack and the heap are characterized by: the heap is larger than the stack, the stack is faster than the heap. The basic data type is relatively stable, and relatively small memory, so can be placed in the stack memory, convenient direct access; The size of reference datatypes is dynamic and unlimited, so it is better to put them in the heap because they take up too much memory.

3. Can I put all the data on the stack? What’s the effect?

The answer, of course, is no. In addition to storing data, the system stack can also create and switch the execution context of functions, as shown in the following code:

var name = 'Aaron_hj';
function showName(){
    var anotherName = 'Aaron';
    function swapName(){
        var tmpName = anotherName;
        anothorName = name
        name = tmpName;
    }
    swapName();
}
showName();

Copy the code

During the execution of this code, the system stack produces the following procedure:

  • 1. Push the global context on the stack first
  • 2. Call showName(), push the context of showName, execute showName()
  • 3. Call swapName(), push the context of swapName on the stack, and execute swapName()
  • 4. After swapName is executed, the context is removed from the stack and the system stack is switched to the showName context
  • 5. After the showName command is executed, the context is removed from the stack and the system stack is switched to the global context
  • 6. The global context is unloaded after the browser window is closed

So, as we can see,If the stack is used to store memory-intensive reference data type data, then the system stack switching context becomes very expensive and the switching efficiency is significantly reduced.This is where JS’s in-memory mechanism makes sense to store data in this way.

4. Is it true that “basic data types are stored on the stack and reference data types are stored in”?

Since there is a counterexample to this question, it is necessary to dig a little deeper into closures: closure variables are stored in heap memory. Take the following code block as an example:

function foo() {
    var myName = 'Michael Owen
    let test1 = 1
    const test2 = 2
    var innerBar = {
      setName: function(newName) {
        myName = newName
      },
      getName: function() {
        console.log(test1);
        return myName

      }
    }
    return innerBar
  }
  var bar = foo()
  bar.setName('Aaron_hj') // This allows the V8 engine to know that a closure has occurred
  console.log(bar.getName) // Share a cloTrue object. This step is equivalent to putting Test1 into a CloTrue object
Copy the code

In this code block, it is clear that closures are being generated, and the workflow for the V8 engine is as follows:

  • 1. Call foo, the engine first compiles foo, pushes the execution context of foo onto the system stack and executes foo.
  • 2. Call setName, engine first compile setName, found in setName using myName, not found in the scope of setName, go to foo found, engine found there is a closure.
  • 3. When the engine detects closure, it puts the closure variable myName in a closure object, stores the object in the heap, and changes the stack value of myName to the closure object’s reference address.
  • 4. Run setName to look for myName in the closure object in the scope chain, and find the address of the reference in the context of foo.
  • 5. When calling getName again, the engine does something similar, but note that it does not create a closure object. It just adds a closure variable that is not in the closure and changes its stack value to the closure object’s reference address.

Finally, the situation in memory looks like this:From this, it is not difficult to see,Closure variables, even basic data types, are stored in heap memory.


Depth copy

Shallow copy

  • Prior to ES6, the definition of a shallow copy was simple: in the case of an object, a shallow copy simply copied the reference address of the object, and the new object copied this way pointed to the same block as the original object.

Code implementation is as follows:

let obj1={count:1.name:'Aaron_hj'.age:21};
let obj2 = obj1;
obj2        //{count:1,name:'grace',age:21}
obj2.count=2;
obj1        //{count:2,name:'grace',age:21}
obj2        //{count:2,name:'grace',age:21}
Copy the code

As a result, we can see that because the new object and the original object both point to the same block, a change in the value of the memory address affects both OBJ1 and Obj2. The memory is shown in the following diagram:

  • In ES6, JS introduces the object. assign method, which is mainly used for merging objects and copying all enumerable attributes of the source Object to the target Object. For the specific usage of this method, you can go to ruan Yifeng ES6 introduction to understand, but here will not be repeated. Now, you might ask: What does this have to do with shallow copies? Look at the following code:
const obj1 = {a: {b: 1},c:2};
const obj2 = Object.assign({}, obj1);
obj1.a.b = 2;
console.log(obj2.a.b) / / 2
obj2.c = 3
console.log(obj1.c); / / 2
Copy the code

From the above output, changing the c property of obj1 itself does not affect the c property of obj2. The properties of objects nested in Obj1 are changed, and the properties of objects nested in Obj2 are changed accordingly. Here’s how I understand the shallow copy method: All variables of the copied object have the same values as the original object, and all references to other objects still point to the original object. That is, a shallow copy of an object copies only the “primary” object, but does not copy the objects in the primary object. The objects inside will be shared between the copied object and the original object. The shallow copy only copies one layer, and the nested objects only copy their reference addresses. A good example of such a shallow copy in ES6 is the object.assign method

(One thing to note here: this method can be implemented for arrays as well, but arrays are treated as objects, especially in the sense that the method’s namespaces are replaced.)

Deep copy

Deep copy not only copies each attribute of the original object one by one, but also recursively copies the object contained in each attribute of the original object to the new object by deep copy method, so the original object and the new object are not affected by each other in the real sense.

  • Manual deep copy
  1. There is no object nesting code implementation
let obj1 = {
    a: 1.b: 2
}
let obj2 = {
    a: obj1.a,
    b: obj1.b
}
obj2.a = 3;
console.log(obj1.a); / / 1
console.log(obj2.a); / / 3
Copy the code
  • Implement deep copy recursively
// Implement recursively
function deepCopy(obj) {
    var obj2 = Array.isArray(obj) ? [] : {};
    if (obj && typeof obj === "object") {
        for (var i in obj) {
            var prop = obj[i]; // Avoid endless loops caused by mutual references, such as obj1.a=obj
            if (prop == obj) {continue; }if (obj.hasOwnProperty(i)) {
                // If the child attribute is a reference data type, recursively copy
                if (prop && typeof prop === "object") {
                    obj2[i] = (prop.constructor === Array)? [] : {};arguments.callee(prop, obj2[i]); // Call recursively
                } else {
                    // If it is a basic data type, it is simply copiedobj2[i] = prop; }}}}return obj2;
}
var obj1 = {
    a: 1.b: 2.c: {
        d: 3}}var obj2 = deepCopy(obj1);
obj2.a = 3;
obj2.c.d = 4;
console.log(obj1.a); / / 1
console.log(obj2.a); / / 3
console.log(obj1.c.d); / / 3
console.log(obj2.c.d); / / 4
Copy the code

Common progress

This article mainly shares the JS data storage mechanism and depth of copy knowledge, the article adds a lot of author’s own understanding, there may be mistakes, you can be corrected in the comment area, the author will correct in time. Feel free to share your thoughts in the comments section. This article is the first article of this series, the follow-up will continue to add, the original JS series to create a more complete and system, build their own knowledge system, also welcome everyone to comment area to put forward the original JS is worth digging deep knowledge, in this front of the road to progress together.

If this article let you have some kind of inspiration to the native JS, or make up for some of your knowledge blind area, or let the original vague concept to understand clearly. So, I think the value of this article has been reflected. So stay tuned for future articles in this series.

Note: References to this article are as follows:

  • The author Object. The assign method of study is reference to nguyen other teacher’s blog, nguyen other ES6 portal: www.bookstack.cn/read/es6-3r…
  • Of deep copy of the recursive implementation code is reproduced in the bosses of the post, you can look at: www.jianshu.com/p/cf1e9d7e9…