9、JS底层运行机制:闭包机制

181 阅读4分钟

知识点复习:

  1. GO全局对象和VO(G)的区别
    1. GO != VO
    2. GO全局对象:指向全局对象window,是一个堆内存,存放的是浏览器内置的API
    3. VO(G)全局变量对象:上下文中的空间,存放的是全局上下文中创建的变量
  2. GO和VO的关联:
    1. 基于VAR/FUNCTION在全局上下文中声明的全局变量也会给GO赋值一份(存在映射机制:一个修改,另一个也修改)但是:基于LET/CONST/等ES6方式在全局上下文中创建的全局变量和GO没有关系

验证:

p1-jj.byteimg.com/tos-cn-i-t2…
3. var/function 存在变量提升 let/const不存在

浏览器的垃圾回收机制(自己内部处理):

  1. 谷歌等浏览器是“基于引用查找”来进行垃圾回收的
    1. 开辟的内存,浏览器自己默认会在空闲的时候,查找所有内存的引用,把那些不被引用的内存释放掉
    2. 开辟的栈内存(上下文)一般在代码执行完都会出栈释放,如果遇到上下文中的东西被外部占用,则不会释放
  2. IE等浏览器是“基于计数器”机制来进行内存管理的
    1. 创建的内存被引用一次,则计数1,再被引用一次,计数2... 移除引用减去1...当减为0的时候,浏览器会把内存释放掉 =》 只不过,真实项目中,某些情况导致计数规则会出现一些问题,造成很多内存不能被释放,产生““内存泄漏”
      查找引用的方式如果形成相互引用,也会导致内存泄漏,但大多数情况还是发生在IE浏览器中

通过练习题来逐步讲解

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)

  1. 所有代码执行,都要放到栈内存中执行,所以浏览器在刚开始加载页面的时候会形成一个栈内存(或者说在内存空间里分配出一块内存来)供代码执行,称之为执行环境栈(ECStack)
  2. 代码首先要执行全局代码,所以会形成一个EC(G)全局执行上下文
  3. 执行上下文进栈执行
    1. 执行上下文中,会创建VO(G)全局变量对象(文章开始提到过AO与GO的联系和区别),用来存储全局变量
    2. 变量提升,所有带var和function的都存在变量提升(带var的只声明不定义,带function的声明加定义,特殊情况:当遇到判断体不管条件是否成立,都会进行变量提升,此时function只声明不定义,且判断体中会把function这行代码之前的操作(包括变量提升/代码等)映射倒全局)。 let和const不存在变量提升
      1. EC(G)变量提升
        1. function A(y){...}
        2. A是一个函数,所以创建一个堆内存,地址是:0x0000,作用域是:EC(G)
          1. 这个堆内存存得是函数的代码字符串:"let x = 2....return B;"
          2. 有个形参:y
          3. 对象键值对:name:'A', llength:1(形参个数),prototype...,__proto__等
        3. 函数创建完毕,会将函数的地址0x0000存到VO(G)中,(创建一个变量分三步:1创建值,2声明变量,3变量喝值关联)
        4. 创建变量A
        5. 变量和值关联 A -->0x0000
      2. 变量提升完毕,全局代码执行,执行的就是以下代码
          let x = 1; // 创建值1,创建变量x,存到VO(G)中,x和1关联
          function A(y) { // 整个函数已经在变量提升阶段操作过,此时便不在操作
            let x = 2;
            function B(z){
              console.log(x + y + z)
            }
            return B;
          }
          let C = A(2)  // 把函数执行的返回结果当作值赋给C
          C(3)
        
      3. A(2)执行 =》 0x0000(2)执行
        1. 形成私有的上下文,取名: EC(A)
        2. EC(A)进栈执行,降EC(G)压倒底部
        3. EC(A)中创建 AO(A)私有变量对象
        4. 代码执行前要做的事: 1.

函数执行会形成全新的私有上下文,这个上下文可能被释放,也可能不被释放,不论是否释放,它的作用是:

  1. 保护:划分一个独立的代码执行区域,在这个区域中有自己私有变量存储的空间,而用到的私有变量会和其他区域中的变量不会有任何的冲突(防止全局变量污染)
  2. 保存:如果私有上下文不被销毁那么存储的私有变量的值页不会被销毁,可以被其下级上下文中调取使用
  3. 我们把函数执行形成私有上下文来保存和保护私有变量机制,称之为闭包(闭包不是一种代码,是一种机制而已)
  4. 其实函数一执行就形成私有上下文,能够保存和保护私有变量,但是市面上一般认为只有形成的私有上下文不被释放才算是闭包(因为如果一旦释放,之前的东西也就不存在了);还有人认为,只有下级上下文中到了此上下文中的东西 才算闭包