JavaScript 执行上下文和变量对象

JavaScript 执行上下文和变量对象

我们在书写应用程序的时候,总是避免不了创建各种变量函数,那么当我们在使用他们的时候,解释器又是从哪以及怎么去找到他们?在JavaScript中,这个过程可以用执行上下文和变量对象来解释 —– [ Dmitry Soshnikov ]

执行上下文

执行上下文,又叫执行环境,它定义了变量或函数有权访问的其他数据,决定了它们各自的行为。每次当控制器转到ECMAScript可执行代码的时候,即会进入到一个执行环境。执行环境是个抽象的概念,ECMA规范没有去具体定义它的内容,它在实现逻辑上是一个堆栈的形式,在堆栈的底部永远是全局的执行环境。在堆栈的顶部是当前的执行上下文。在JavaScript中存在很多个执行环境,比如函数体或者全局执行环境以及es6新出的class,每一个执行环境又有一个与之相关联的变量对象,它也是执行环境的一个属性。环境中定义的对象和函数都保存在这个变量对象里面,可以说执行环境是通过它与之绑定的变量对象来实现的。
在ECMAScript中,程序如何知道一个变量定义是属于全局还是函数内容的局部变量,以及处于函数内容是通过什么机制来访问函数外部的全局变量。都与ECMAScript中的执行上下文相关。

变量对象(variable object)

变量对象(缩写VO)就是保存执行环境中所有变量和函数的地方。它通过它,程序在执行时才能知道程序的执行环境拥有哪些变量以及函数。变量对象主要保存一下三种内容:
函数的形参(当进入函数的执行环境中)
变量对象的一个属性,这个属性由一个形式参数的名称和值组成;如果没有对应传递实际参数,那么这个属性就由形式参数的名称和undefined值组成;
函数声明
变量对象的一个属性,这个属性由一个函数对象(function-object)的名称和值组成;如果变量对象已经存在相同名称的属性,则完全替换这个属性。
变量
变量对象的一个属性,这个属性由变量名称和undefined值组成;如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性。

简单来说,变量对象就是程序进入一个执行环境时候创建的一个执行环境的属性,它定义了执行环境里面拥有哪些内容。执行环境是按照上面三种内容的顺序依次添加到执行对象中,之后添加的不会覆盖前面的。下面以一个函数为例,我们用一个JavaScript对象VO来展示变量对象内容。

var out = "global"
function foo(x, y) {
  var a = 10;
  function b() {}
  var c = function _e() {};
  (function d() {});
}
foo(10); // call
console.log("===end");

当控制器进入执行foo(10),进入foo这个函数中时候,程序就进入了一个新的执行环境中,这个执行环境会被立马推入一个执行环境栈中。当执行完函数,环境栈将属于函数foo()的执行环境弹出,将控制权交给栈的下一个执行环境,也就是新的栈顶。下面我们看下进入foo()之后,变量对象VO的内容:

vo = {
x:10,
y:undefined,
a:undefined,
b:<reference to FunctionDeclaration "b">,
c:undefined
}

在执行环境中,仅有函数声明会被进入到VO中。对于d,是一个函数表达式,所以在VO中并不存在。而对于e,虽然也是一个函数表达式,但它被赋给了变量e,所以在函数内部任何地方都可以通过e去访问。相反的,函数表达式d只能在它自己的定义或者递归中才能调用,在其他任何地方调用d,都会报“d is not defined”的错误。
下面举一个经典例子说明变量对象生成顺序和无法覆盖的内容:

alert(x); // function x() {}
var x = 10;
alert(x); // 10 
x = 20;
function x() {}; 
alert(x); // 20

第一个alert语句返回的function,而不是10或者20。这是因为控制在进入这段上下文的时候,首先去寻找函数形参,接着去找函数声明,这里就是function x() 。最后会去找变量声明,会找到变量x。但是变量x跟已经函数声明x的名称相同,在这里变量声明不会去覆盖函数声明x。所以进入这段程序的时,VO的内容如下:

vo = {
x:<reference to FunctionDeclaration "x">,
}

虽然存在var x这样的声明,但是由于存在同名函数声明,所以变量x不会进入变量对象VO中。

执行代码

以函数为例,控制器在运行进入函数之后,发生了下面几件事:进入函数的执行环境,生成执行环境的变量对象,执行代码。在执行代码期间,变量对象也会随之修改。以下面这段代码为例。

alert(x); // undefined
var x = 10;
alert(x); // 10 
x = 20;
alert(x); // 20

进入上面这个执行环境后,生成的变量对象仅有一个属性x,值为undefined。

vo = {
x:undefined,
}

生成执行环境的变量对象后,解释器开始按顺序执行代码。执行第一个alert(x)时候,解释器从变量对象里面去取值,得到x的值为undefined。接下来执行x的赋值语句, 这时候解释器将变量对象里面x的值修改为10。

vo['x'] = 10;

第二个alert(x)又会再去当前执行环境的变量对象里面取值,这时候x已经更新为10,所以结果也是10。同理,第三个alert显示为20。

作用域链

变量对象会告诉解释器在执行环境中有权访问的变量或函数。 那么对于当前执行环境,它又是怎么去访问到其他执行环境(全局或上一层执行环境)的变量或函数?这就是由作用域链来实现的。
当进入一个执行环境并且生成变量对象后,就会创建变量对象的一个作用域链。作用域链的用途就是保证当前执行环境有权访问的所有变量和函数(包括其他执行环境)的有序访问。

var color="red";
function foo(){
   var fooColor = "pink";
   alert(color);  //red
   color = "blue";

   function otherColor(){
      var myColor = fooColor;
      color = myColor;
      //这里可以访问到color,fooColor,myColor
    }
    myColor();//这里可以访问到fooColor ,color 
}
foo();//这里只能访问到color;
alert(color);

在上面这段代码,存在三个执行环境。分别是全局环境和函数foo以及otherColor的两个函数局部环境。在这里以otherColor函数为例,我们发现在这个函数内部可以访问得到函数foo和全局环境下的变量fooColor以及color。而在foo函数中并不能访问得到otherColor中声明的myColor 变量。
在进入otherColor函数执行环境的时候,生成它的变量对象myColorAO,同时会生成otherColorAO的作用域链,作用域链包含的内容可以用下面这种方式表示:

Created with Raphaël 2.1.0 window color foo() fooColor otherColor() myColor

上图就是otherColorAO作用域链,是与otherColorAO的执行环境绑定在一起。当前环境可以通过作用域链访问到外部环境,但是外部环境不能访问内部环境中的任何变量或者函数。作用域链是线性有序的,只能通过向上搜索来查找变量或者函数而无法向下搜索。所以在otherColor()中可以向上搜索访问到color,fooColor,myColor以及函数foo(),但在foo函数中无法向下搜索找到myColor这个变量,而它能访问到otherColor()是因为这个函数在它的变量对象里面。


以上就是关于ECMAScript中执行上下文,变量对象以及作用域链的部分相关内容,夹杂个人理解和书本上的东西,以后会不断完善。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值