js执行环境与作用域链--总结

执行环境和作用域链

一.执行环境(exection context,也有称之为执行上下文或者环境)

   所有 JavaScript代码都是在一个执行环境中被执行的。执行环境是一个概念,一种机制,用来完成JavaScript运行时在作用域、生存期等方面的处理,它定义了变量或函数是否有权访问其他数据,决定各自行为。 在javascript中,可执行的JavaScript代码分三种类型: 
      1.Global Code,即全局的、不在任何函数里面的代码,例如:一个js文件、嵌入在HTML页面中的js代码等。 
      2. Eval Code,即使用eval()函数动态执行的JS代码。 
      3. Function Code,即用户自定义函数中的函数体JS代码。 
  不同类型的JavaScript代码具有不同的执行环境,我们主要讨论全局执行环境和函数执行环境

     全局执行环境——在一个页面中,第一次载入JS代码时创建一个全局执行环境,全局执行环境是最外围的执行环境,在Web浏览器中,全局执行环境被认为是window对象。因此,所有的全局变量和函数都是作为window对象的属性和方法创建的。全局执行环境的销毁是在网页或浏览器关闭时执行的,

函数执行环境 —— 当执行一个函数时,JavaScript引擎进入执行环境。某个执行环境中的代码执行完之后,该环境销毁,保存在其中的所有变量和函数定义也随之销毁。

Eval执行环境 — Eval的执行环境和函数调用的执行环境相同。

活动的执行环境构成一个栈:栈的底部始终是全局环境,顶部是当前活动的执行环境。当执行流进入一个函数时,函数的环境被压入栈中。而在函数执行完之后,栈将其环境弹出,把控制权返回给之前的执行环境。


 

二.变量对象

变量对象的英文缩写是VOVariable Object),每个执行环境都有一个与之相关的变量对象,环境中定义的所有变量和函数都保存在这个对象中,具体包括:

1. 函数的形参  

2. var声明的变量  

   3.函数声明(但不包含函数表达式)

变量对象有两种存在方式,全局环境中的变量对象,这个对象就是全局对象,全局对象是在进入任何执行环境之前就已经创建了的对象。这个对象只存在一份,它的属性在程序中的任何地方都可以访问,全局对象的生命周期终止于程序退出的那一刻。全局对象的初始化阶段,将MathString等作为自身属性,初始化如下:

Global = {  

   Math:{...},  

   String:{...},  

   ...  

   ...  

   window:Global // 引用自身  

};

另外一种是函数执行环境中定义的变量对象当函数被调用时,一个特殊的对象——活动对象就随之创建了对象在函数的执行上下文中是不能直接访问的,被称为活动对象,英文缩写为AOActivation Object它通过函数的arguments属性初始化:

AO = {  

   arguments: {...} //参数对象,包括callee, length等属性  

};

三.执行环境的建立

和活动对象一样,我们同样也可以用一个对象来表示执行上下文:

ExecutionContextObj = {

    scopeChain: { 变量对象(variableObject+所有父执行上下文的变量对象},

    variableObject: { <arguments>对象,内部变量声明和函数声明},

    this:{}

}

每当一个函数被调用的时候,就会随之创建一个执行上下文,在 Javascript解释器内部处理执行上下文有两个步骤:

第一步:创建阶段(在函数调用之后,函数体执行之前),解释器扫描传递给函数的参数或arguments,本地函数声明和本地变量声明,并创建executionContextObj对象。扫描的结果将完成变量对象的创建及作用域链Scope Chain)的创建并确定this的值。

扫描上下文中声明的形式参数、函数以及变量,并依次填充变量对象的属性

函数的形参:形参作为属性,对应的实参作为值。对于没有实参的形参,值为undefined

函数声明(FunctionDeclaration FD:由函数对象创建出相应的名、值,名就是函数名、值就是函数体。如果变量对象已经包含了同名的属性,就会替换掉它的值。

变量声明(VariableDeclaration:属性名是变量名,值初始化为undefined。如果变量名和已经存在的属性同名,不会影响到同名的属性。

注意:函数表达式(FunctionExpression FE)不会成为变量对象的属性,也就是说函数表达式不会影响到变量对象。

第二步:代码执行阶段

这一阶段就会给第一步中初始值为 undefined的变量赋上相应的值,这时后赋的值会覆盖前面的同名的值。

所以随着代码的执行,活动对象的属性的属性值是在不断的变化的,是“活动”的。

举个例子吧:

 

(function foo(){

 

  console.log(typeof x);//"function"

  var x = 10;

  console.log(y);//undefined 而不是 “y is not defined” ,这就是变量声明提升!

  var y = 20;

  console.log(typeof x);//"number"

  function x(){}

 

})();


为什么第一次打印x的类型是函数,第二次打印x的类型又是数字呢。这是因为,根据创建上下文时的规则,函数调用之后会按照顺序依次把函数参数、函数声明、变量声明填充为VO的属性,并且填充变量声明的时候如果同名是不会造成任何影响的,x的值还是函数。

在进入上下文阶段,VO的状态:

 

VO = {

  x:pointer to function x()

}

//发现var x = 10;

//如果函数“x”还未定义,"x"的值为undefined,

//但是,在这个例子中

//变量声明并不会影响同名的值为函数的x

 

VO[‘x’] 的值仍未改变

在代码执行阶段,VO的状态:

 

VO['x'] = 10;

 

这一阶段,局部变量 x被赋值,此时之前同名的值为函数的x就会被覆盖,大家注意声明和赋值!!第一阶段,局部变量声明同名不会影响;第二阶段局部变量赋值就会产生影响了,毕竟人家是最后赋值的嘛

四.作用域链

每个环境都有自己的变量对象,作用域链正是内部环境所有变量对象(包括父变量对象)的列表。此链用来在标识符解析中查找变量。作用域链本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。对于上面的例子,bar执行环境中的作用域链包括:bar变量对象、foo变量对象和全局变量对象。

函数的作用域链是在函数调用时创建,包含这个函数的活动对象和[[scope]]属性。

示例如下:

活动的执行环境 = {  

   AO: 变量对象,  

   thisthisValue,  

   Scope: [变量对象列表] //作用域链  

};  

其中Scope =被调用函数的活动对象+被调用函数的[[scope]]属性。

这种标识符的解析过程,与函数的生命周期有关。函数的生命周期可以分为创建和激活(调用时)两个阶段。在函数创建时,函数对象的内部存在一个[[scope]]属性,[[scope]]一个包含了所有父变量对象的层级链。[[scope]]属性在函数定义的时候被创建并保存在函数中,它是函数的内部属性,一直存在直到函数被销毁。作用域链是函数被调用时创建的,是执行环境的一个属性,当函数调用结束,执行环境被销毁,它也会随之被销毁

在函数调用激活阶段,生成的活动对象和[[scope]]属性共同组成执行环境的作用域链。也就是说将活动对象添加到[[scope]]链表的最前端,在查找标识符时,首先从自身变量对象开始,逐渐向父变量查找。

另外需要特别注意的是,通过构造函数创建的函数的[[scope]]属性中仅包含全局对象。


 


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值