JavaScript 之 VO(G) 与 GO

703 阅读6分钟

EC(G): Execution Context (Global) 全局执行上下文。代码提交后,在栈内存(ECStack执行环境栈)中形成的全局执行上下文。

  • VO(G):Variable Object(Global)全局变量对象

    为了可以访问到存放在GO中的属性和方法,浏览器在全局执行上下文EC(G)中,默认声明了一个变量window,存放在VO(G)中,并让window指向GO的地址,后期便可以基于window访问内置的属性和方法。

    1. 我们经常把window称为全局对象
    2. 编写代码时,我们可以省略window 如window.alert() ⇒ alert();

GO:Global Object 全局对象 全局变量window对应的堆内存地址,用来存储内置的属性和方法。

var/function/let/const在全局执行上下文中的区别:

  • 全局执行上下文中,基于var/function声明的变量是存储在GO中的,相当于给window设置属性;
  • 而基于let/const声明的变量是存储在VO(G)中的,他们才是根正苗红的全局变量。与window没有关系;
  • 没有基于任何关键字修饰/声明的变量,其实是省略了"window.",核心也是在GO中增加一个属性。

图片.png

基于以上:

  1. 如果是window.xxx,直接到GO中找即可;
  2. 如果是直接输出一个变量,则先去VO(G)中找,有的话获取的是全局变量,如果没有则到GO中找,如果有,则是全局对象属性,如果没有则报错:xxx is not defined。

变量提升机制

  1. 当前上下文中,代码执行之前,浏览器首先会把所有带var/function关键字的进行提前声明或者定义

    1. var 只声明 不定义
    2. function 既声明又定义 注意:
      1. 函数在哪个执行上下文创建,它的作用域就是哪个执行上下文;
      2. 作用域是函数所处的执行上下文,在函数创建时确定,即其上级上下文;
      3. 函数私有执行上下文在函数执行时决定;
      4. 函数的声明定义,是先定义再声明,即先创建值[堆内存],再创建变量;
      5. 为函数开辟的堆内存中存储了:
        1. 函数的作用域scope <函数的执行上下文,函数的上级执行上下文>
        2. 代码字符串
        3. 键值对 name: '函数名' length : '形参个数'
  2. let/const/import/class声明的变量不存在变量提升

  3. 私有作用域中的变量提升:

    函数的底层运行机制:

    代码运行到[函数执行]时,形成私有执行上下文EC(FN),私有变量对象AO(FN),在函数内代码执行前浏览器做了以下事情:

    1. 初始化作用域链
    2. 初始化this
    3. 初始化arguments
    4. 形参私有化并赋值
    5. 变量提升
    6. 代码执行
    7. 默认情况下,函数执行完,所形成的上下文会出栈释放掉

    闭包:函数执行,会产生一个全新的私有上下文,保护里面的私有变量,不受外界的干扰,避免了全局变量污染,我们把函数的这种保护机制称之为闭包。

  4. 推荐使用函数表达式

    1. functoin fn(){}````var fn = function(){}[函数表达式] 的区别:
      • 函数表达式的执行只能在声明后执行,普通函数可以在声明定义前执行,区别在于变量提升
      • 真实项目推荐使用函数表达式,确保函数执行只能在“创建函数代码”的下面,保证逻辑的严谨性。
  5. 匿名函数具名化[官方推荐规范]

    (function A() {
        A = 100; //无效操作 
        console.log(A); //函数本身
    })();
    
    1. 设置的名字,并不会在所处上下文中进行声明;
    2. 在函数执行形成的私有上下文中,会把这个名字作为一个私有变量「存储到AO中」,变量值是当前函数本身「堆内存地址」; 并且默认情况下,对这个变量值进行修改是无效的;
    3. 但凡函数内部,这个名字被我们手动的声明过[例如:形参/var/let/const...],以我们声明的为准。则规则2无效,浏览器将权利移交给我们。
    (function A() { 
        console.log(A); //undefined
        var A = 100;
        console.log(A); //100
    })();
    

    匿名函数:自执行函数/函数表达式/回调函数....

    匿名函数具名化的作用:

    可以在函数内部基于这个名字访问到这个函数,这样就可以实现一些不具备的能力,如:递归

    非严格模式下,也可通过arguments.callee访问当前匿名函数,arguments.callee.caller:存储函数在哪执行的,

    “use strict” //开启JS严格模式,基于webpack打包后,都是严格模式,arguments.callee、arguments.callee.caller在严格模式下不能用

  6. 块级上下文 [ES6新增]

    1. 是在代码执行期间执行的,即碰到{}才形成相应的块级上下文
    2. 除函数和对象的大括号外,如:判断体、循环体、代码块,如果在大括号中出现了let/const/function/class 等关键词声明变量,则当前大括号会产生一个“块级私有上下文”,它的上级上下文就是其所处的上下文。
      • let/const/funtion 会产生块级上下文,也会受到块级上下文的束缚
      • var不产生,也不受块级上下文的影响

图片.png 3. 函数在块级上下文中的特殊性【新版本与老版本的不同】

    老版本:不会有块级上下文,所以在大括号(除函数和对象外)中出现fuction,还是保持原始的样子,变量提升阶段是声明+定义;

    新版本,为了兼容ES5也可以兼容ES6,则全局下也要声明,私有块级上下文中也要声明。

    1. 出现在“除函数/对象”以外的大括号中的function,在最开始变量提升阶段只声明
    2. 会产生块级上下文,
    3. **遇到函数声明的那串代码时候(分界点:将之前对该函数的值给上级上下文一份,此时该函数的上级上下文变量和该函数的私有变量拥有了同一个值【堆内存地址】,但他们始终是两个变量)**,会向该函数的全局变量指向函数对象地址,以后再对函数私有变量进行赋值,与该函数全局变量无关,只与函数私有变量有关。

图片.png jsx { function foo() {} //**将之前对foo的操作同步给全局一份** foo = 1; function foo() {} //**也会将之前对foo的操作同步给全局一份,遇到一次改一次** } console.log(foo);// 1