原生JS之JS运行的底层机制

168 阅读5分钟

JS 之所以能够在浏览器中运行,是因为浏览器给 JS 提供了执行的环境 => 栈内存(Stack)

  • ECStack(Execution Context Stack) => 执行环境栈
    • 浏览器会在计算机内存中分配一块内存,专门用来供代码执行
  • EC => 执行上下文,作为代码执行所在的环境;代码执行之前,当前上下文会先进栈(ECStack),然后执行
    • 全局的执行上下文 EC(G)
    • 函数中的代码会在一个单独的私有的执行上下文中处理
    • 块级的执行上下文
  • GO => 全局对象
    • 浏览器把内置的一些属性和方法放到一个单独的内存中,堆内存(Heap)
    • 浏览器会让 window 指向全局对象 GO
  • VO(Varible Object) => 变量对象
    • 在当前的上下文中,用来存放创建的变量和值的地方(每一个执行上下文中都会有一个自己的变量对象)
    • 函数私有上下文中叫做AO(Activation Object)活动对象,也是变量对象

定义变量 (分为三步) => 所有的变量赋值都是指针的关联指向

  • 创建一个值
    • 基本类型值: 直接存储在 栈内存 (VO / AO) 中
    • 引用类型值: 开辟一个堆内存,把东西存储进去,最后把堆的地址存在栈内存 (VO / AO) 中
      • 对象的创建:
      • 1.开辟一个堆内存
      • 2.把键值对存储到堆中
      • 3.堆内存的地址放到栈中,供变量调用
  • 创建一个变量
  • 让变量和值关联在一起

/*
 *  GO 全局对象: 是一个堆内存,存储的是浏览器内置的API属性方法,浏览器端的 window 指向了 GO
 *
 *  浏览器提供一个代码执行的环境 => 栈内存
 *  执行环境栈 ECStack
 *      1.供代码执行
 *      2.存储基本类型值 / 堆内存地址 / 变量
 *      3.JS 中存在多种作用域(全局,函数私有,块级私有),为了保证这种机制,代码执行之前,首先会形成自己的执行上下文,然后把上下文进栈,进栈后在当前上下文中去依次执行代码
 *  
 *  EC(G) 全局执行上下文 => 进栈执行
 *      每一个执行上下文中一定存在一个空间,用来存储创建的变量,叫做全局变量对象
 *
 *  VO(G) 全局变量对象
 *      1.全局上下文中用来存储全局变量的空间,在某些情况下 VO(G) 会和 GO 中的东西有所关联(映射机制)
 *      2.所有的赋值操作: (1)创建值 (2)创建变量 (3)变量和值指针关联
 *      3.基本类型值直接存在栈内存中,引用值是先开一个堆,将对象的键值对存储到堆中,最后把堆的16进制地址放到栈中供变量调用
 *
 *  => 创建 AAAFFF000 堆; 创建 a ; a 指向 AAAFFF000 { n: 1 }
 *  => 创建 b ; b 指向 AAAFFF000 { n: 1 }
 *  => a.x 成员访问,优先级 > a
 *  => 创建 AAAFFF111 堆; a.x 指向 AAAFFF111 { n: 2 } ; AAAFFF000 { n: 1, x: AAAFFF111 }
 *  => a 指向 AAAFFF111 { n: 2 }
 *
 *  ... ...
 *  关闭页面,EC(G) => 出栈释放
 */

var a = { n: 1 };
var b = a;
a.x = a = { n: 2 };
console.log(a.x); //=> undefined
console.log(b); //=> { n: 1, x: { n: 2 } }


/*
 *  ECStack (此处不讨论变量提升)
 *  EC(G) => 进栈执行
 *  VO(G)
 *  => 创建堆 AAAFFF000; 创建 a; a 指向 AAAFFF000 [10, 23]
 *  => 创建堆 AAAFFF111; 创建 fn; fn 指向函数 AAAFFF111 (函数堆)
 *      1.把函数体中的代码当作代码字符串存储到堆中,"代码字符串"
 *      2.函数也是对象,有自己的键值对: name fn, length: 1, prototype, __proto__, ...
 *      3.创建函数的同时,就已经定义了函数的作用域,当前创建函数所在的上下文 [[scope]]: EC(G)
 *  => 函数执行,把全局下 x 的值作为是残传递给函数的形参
 *      1.执行函数的目的: 让存储在堆中的代码字符串执行
 *      2.代码执行会形成自己的执行环境
 *  => EC(FN) fn(x) => 进栈执行  AAAFFF111(AAAFFF000) 
 *      1.把全局的上下文压缩到栈的底部
 *      2.新进来的上下文放到栈的顶部
 *      AO(FN)
 *          => 代码执行之前
 *          (函数中的变量对象)活动对象,是 VO 的一个分支,都是变量对象
 *          1.初始化作用域链 [[scopeChain]]: <EC()FN, EC(G)>  (<执行时自己所在上下文, 函数的作用域>)
 *          * 私有上下文中执行代码时遇到变量按照链式查找来操作
 *          2.初始化 this 指向: window
 *          3.初始化实参集合 ARGUMENTS
 *          4.形参赋值: y = AAAFFF000 (形参变量也存放到自己上下文中的私有变量对象中,是私有变量)
 *          5.变量提升: --
 *          => 代码开始执行: y 是私有变量
 *          => y[0] = 100; // AAAFFF000: [100, 23]
 *          => y = [100]; // 创建堆 AAAFFF222; y 指向 AAAFFF222 [100]
 *          => y[1] = 200; // AAAFFF222: [100, 200]
 *          console.log(y); // AAAFFF222 [100, 200]
 *  =>EC(FN) 上下文中代码执行结束,没有东西被外部占用,出栈释放
 *  => console.log(x); // AAAFFF000 [100, 23]
 *
 *  ... ...
 * 关闭页面,EC(G) => 出栈释放
 */
var x = [12, 23];
function fn(y){
    y[0] = 100;
    y = [100];
    y[1] =200;
    console.log(y);
}
fn(x);
console.log(x);