js学习笔记(1) 堆(heap)栈(stack)内存

186 阅读5分钟

开篇导言

做了好几年程序了,一直想这写点博客笔记什么的,一直拖拖拉拉好久没开始,最近越来越觉得有必要把以前的只是梳理一遍,给自己整理点笔记,巩固一下基础知识,仅限于小白自我学习,哈哈。

之所以浏览器堆栈内存作为我的第一篇笔记,是因为最初学习js时,有很多不知其所以然的疑惑,例如闭包产生的原理,一直没找到合理的解释,后来从浏览器运行机制中找到合理的解释,也算是解开了我这个小白一直不懂的心结。(下面笔记只以webkit的chrome浏览器容器端为参照);由于浏览器内存机制会将不被占用的空间在空闲时做销毁处理=> 出栈,(即进栈执行代码,出栈销毁代码,释放内存),下面来屡屡浏览器是怎样执行我们的代码的吧:

  1. 编译器处理

    浏览器拿到我们的资源后,会做一个编译处理(解析成浏览器看得懂的语言,生成一个AST抽象语法书)

  2. 引擎处理(v8/webkit内核)

     ECS(Execution context stack)执行环境栈
         EC(Execution context) 执行上下文(G=>global)
             VO:Varibale Object 变量对象
             AO:Activation Object 活动对象 (函数的叫做AO,理解为VO的一个分支)
     Scope:作用域,创建的函数的时候就赋予的
     Scope Chain :作用域链
    

    执行我们js代码前,浏览器会做一系列的准备才能执行我们的js代码:

    1. 引擎创建了执行上下文环境栈 ECS(Execution context stack)来管理执行上下文(执行代码,存储基本类型值)=> 栈内存

    2. 创建一个全局的执行环境上下文EC(G),压入ECS中(进栈)来执行js代码,(因为是全局EC,所以不会出栈销毁,除非浏览器窗口关闭,哈哈),执行完压缩到整个栈内存的底部

    3. 创建一个全局对VO(G)也叫(global Object),才去执行我们的js代码,申明、创建、赋值,并将VO(G)赋值给window;

      3.1 其中基本类型的定义是保存的栈内存中,引用数据类型是保存的堆内存中提供一个十六进制的地址出来,通过赋值操作让申明和创建的值联系起来,建立指针;

      3.2 定义并赋值函数(A = function(y){...};)会产生这个函数的作用域即Scope,作用域的范围取决于其定义创建的位置;例如在VO(G)中定义的函数,其作用域A[[scope]] = VO(G);函数也是保存在堆内存中,以字符串形式保存保存函数内的代码,并提供十六进制地址出来,同上;

       var和函数存在变量提升,也可以在定义函数之前就执行该函数;
      

      3.3 执行函数A(); 创建一个新的执行环境上下文EC(a),进栈执行,初始化this指向(后面做个专题总结吧),初始化作用域链【Scope Chain】,创建A0变量对象用来存储变量 执行函数内部代码;

  3. 闭包的产生

    由上面的介绍,能理解到函数在定义的时候就确定了作用域,所以,如果是在函数内部定义的函数,其作用域很明显就是定义函数的地方了;作用域链在执行这个函数的时候 也就确定了;如果再将这个内部定义的函数return,并声明一个变量去接收=> 则这个函数会被外界变量占用,所以这个函数所在的执行栈不能被销毁,产生了一个不被销毁的栈,这才是闭包产生的根本原因;这也能看到闭包的两大作用:1.保存了该执行栈中的变量不会被销毁;2.保护了该执行栈中的变量也不会污染EC(G)中的变量;闭包的思想为js的高阶编程提供了很好的思路,最初的模块化思想也是基于这样来的。

  4. 看代码实例:

let x = 1;
function A(y){
   let x = 2;
   function B(z){
       console.log(x+y+z);
   }
   return B;
}
let C = A(2);
C(3);

/*第一步:创建全局执行上下文,并将其压入ECStack中*/
ECStack = [
    //=>全局执行上下文
    EC(G) = {
        //=>全局变量对象
        VO(G):{
            ... //=>包含全局对象原有的属性
            x = 1;
            A = function(y){...};
            A[[scope]] = VO(G); //=>创建函数的时候就确定了其作用域
        }
    }
];

/*第二步:执行函数A(2)*/
ECStack = [
    //=>A的执行上下文
    EC(A) = {
        //=>链表初始化为:AO(A)->VO(G)
        [scope]:VO(G)
        scopeChain:<AO(A),A[[scope]]>
        //=>创建函数A的活动对象
        AO(A) : {
            arguments:[0:2],
            y:2,
            x:2,
            B:function(z){...},
            B[[scope]] = AO(A);
            this:window;
        }
    },
    //=>全局执行上下文
    EC(G) = {
        //=>全局变量对象
        VO(G):{
            ... //=>包含全局对象原有的属性
            x = 1;
            A = function(y){...};
            A[[scope]] = VO(G); //=>创建函数的时候就确定了其作用域
        }
    }
];

/*第三步:执行B/C函数 C(3)*/
ECStack = [
    //=>B的执行上下文
    EC(B){
        [scope]:AO(A)
        scopeChain:<AO(B),AO(A),B[[scope]]
        //=>创建函数B的活动对象
        AO(B):{
            arguments:[0:3],
            z:3,
            this:window;
        }
    },
    //=>A的执行上下文
    EC(A) = {
        //=>链表初始化为:AO(A)->VO(G)
        [scope]:VO(G)
        scopeChain:<AO(A),A[[scope]]>
        //=>创建函数A的活动对象
        AO(A) : {
            arguments:[0:2],
            y:2,
            x:2,
            B:function(z){...},
            B[[scope]] = AO(A);
            this:window;
        }
    },
    //=>全局执行上下文
    EC(G) = {
        //=>全局变量对象
        VO(G):{
            ... //=>包含全局对象原有的属性
            x = 1;
            A = function(y){...};
            A[[scope]] = VO(G); //=>创建函数的时候就确定了其作用域
        }
    }
];