变量对象与作用域链

82 阅读5分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第14天,点击查看活动详情

之前说,在执行代码时做的预先准备动作,这个准备动作就叫对应的执行上下文。

执行上下文有三个属性: 1.变量对象 2.作用域链 3.this

1.变量对象

变量对象:变量对象是与执行上下文相关的数据作用域,存储了在上下文中定义的变量函数声明

执行上下文分为:全局,函数,eval。

不同上下文的变量对象是不同的。

1)全局上下文

全局变量就是window.a=1或直接var a=1,都是全局下的。

且,在JS中,全局对象有window属性指向自身。

var a = 1;
console.log(window.a);
//两段代码的意义差不多,含义就是指向自身。
this.window.b=2;
console.log(this.b);

在全局上下文中的变量对象就是全局对象。

2)函数上下文

函数执行上下文分为:分析和执行两个阶段,即先进入到执行上下文中,代码执行。(我们用active object ,AO表示变量对象,通过arguments属性初始化)

以下根据这两个阶段做分析。

1---进入执行上下文

刚进入执行上下文中,此时没有执行代码

就是说,例如var a = 1时,由于没有执行,所以只定义了a的变量,取不到a的数值,即此时的a对应值为undefined。

例如var a = function(),同理,只定义了a,对应的仍然是undefined。

例子1

function foo(a) {
  var b = 2;
  function c() {}
  var d = function() {};
  b = 3;
}
foo(1);

以上分析:a的值为1先被创建;创建变量b,对应undefined;创建函数c;创建d,对应undefined。还有foo(1)函数执行上下文的变量对象arguments:{0:1,length:1}

例子二

function foo(a) {
  var b = 2;
  var d = function() {};
  c = 2;
   function c() {}
}
foo(1);

观察可知c被重复声明,但规定,如果变量对象已经存在相同名称的属性,则完全替换这个属性。

所以b最终的形式是函数。

例子三

function foo(a) {
  var b = 2;
  function c() {}
   c = 2;
  var d = function() {};
}
foo(1);

观察可知c被重复声明,但规定,如果变量名称跟已经生命的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性。

所以c最终的形式是函数。

!!!!!!!!!!!!!!!!!!!!!!!

注意,例子二与例子三在本地代码是行不通的,因为以上的环境是在刚进入函数执行上下文中的,并没有进行代码执行,所以在本地运行log输出无效,注意注意。

!!!!!!!!!!!!!!!!!!!!!!!

企业微信截图_16600370092029.png

总结:

变量对象会包括:

  1. 函数的所有形参 (如果是函数上下文)
    • 由名称和对应值组成的一个变量对象的属性被创建
    • 没有实参,属性值设为 undefined
  2. 函数声明
    • 由名称和对应值(函数对象(function-object))组成一个变量对象的属性被创建
    • 如果变量对象已经存在相同名称的属性,则完全替换这个属性
  3. 变量声明
    • 由名称和对应值(undefined)组成一个变量对象的属性被创建;
    • 如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性
2---代码执行

在这第二个阶段会按顺序执行代码了,给第一阶段声明的变量赋值。

function foo(a) {
  var b = 2;
  function c() {}
  var d = function() {};
  b = 3;
}
foo(1);

这时候的变量对象包括:

a=1;b=3;c:function c();d:FunctionExpression "d"

还有函数执行上下文的变量对象arguments:{0:1,length:1}

企业微信截图_16600370281425.png

总结:

  • 全局上下文的变量对象为全局对象。
  • 函数执行上下文的变量对象只为Arguments对象
  • 在进入执行上下文时会给变量对象添加形参,函数声明,变量声明等初始的属性值,但var变量值都为undefined。
  • 在代码执行阶段,会将undefined修改为代码中设置的真实值。

2.作用域链

在执行代码查找变量时,先会在当前上下文中查找,如果没有就去父级查找,再没有,就去全局环境下查找。这个由多个执行上下文的变量对象构成的链表就叫做作用域。

函数的生命有两个过程,先被创建,然后激活被使用。

作用域链在这过程中也会有两种链表:创建和变化。

1)函数创建

函数被创建时,会自动保存父变量到自身其中,这也是可以去父级查找的原因。例如:

function f1(){
    var a1 = 1;
    function f2(){
      var a2 = 2;
    }
}

其中f2内部的变量对象 就包含了 f1的变量对象。

f1中父级作用域链有global,

f2中父级作用域链有f1层级与global。

2)函数激活

当函数被激活时,进入函数上下文,创建变量后,就会将活动对象添加到作用链的前端。

例子:

var scope = "global scope";
function checkscope(){
    var scope2 = 'local scope';
    return scope2;
}
checkscope();

1.(函数创建)创建了checkscope(),checkscope中父级作用域链有global.

2.(创建执行上下文并压入栈)将checkscope函数创建上下文并压入执行上下文栈中,此时栈中情况:

ESC:{

checkscopeContext,
globalContext

}

3.(刚进入执行上下文,没有执行代码)

  • 创建checkcope作用域链。
  • 初始化变量对象arguments:{length:0};scope2:undefined;和上一步中的checkscope作用域链。
  • (函数激活)将活动对象参加到作用域链前端。

4.(代码执行)将属性值填入,即sope2:local scope,return寻找到scope2的值之后成功返回。函数执行完毕。

5.函数执行上下文从栈中弹出,此时栈中情况:

ESC:{

globalContext

}

至此,执行上下文的前两个属性分别讲解后又合并在一起,【变量对象与作用域链】,还有最后一个属性this下一篇再写吧。

参考博客:github大佬👉mqyqingfeng