关于执行环境、作用域、作用域链与闭包

前言

(之前的一篇漏掉了这里补档一下~)
话不多说。这次我们谈谈JS中的关于作用域,作用域链,还有执行环境以.

执行环境与执行上下文

首先执行环境(excution context)也就是执行上下文,context有人把它翻译成上下文。我开始一直以为这两个东西不一样呢。。所以后面出现的执行上下文和执行环境就是一回事咯。

执行环境是什么

在javascript中,所有的函数都拥有自己的执行环境。执行环境定义了变量或者函数有权访问的其他数据,决定了它们各自的行为。每个执行环境都有自己的变量对象(PS:这个概念在后面会用到)环境中定义的变量与函数都保存在这个对象中。这个对象我们是访问不到的,只有解析器处理数据时会用到。(最近写ES6的时候,在for循环里面用到了let。let可以创建块级作用域。而ES5中只有函数才可以创建块级作用域)

执行环境的产生

执行环境的生成不是一个说有就有的过程,它分为几个步骤:

函数声明阶段:

在声明一个函数(对象),会为它创建一个scope属性,它指向的是当前作用域链的对象。

函数调用阶段:

生成我们的执行环境。(JS解析器首先进入的是全局的上下文,当调用一个函数的时候会创造一个新的上下文,并把它推入到上下文堆栈的顶部。首先执行的是顶部的上下文,执行完毕后会将其弹出,再执行下面的上下文,最终回到全局上下文。)
而调用阶段也分为两个过程:
1:调用函数,函数执行前
此时会建立变量,函数,arguments对象,参数;建立作用域链,确定this的指向。
2:函数执行
此时会给变量赋值,给函数引用,并执行其他代码。
第一阶段详细的过程如下:
1:找到当前上下文中调用函数代码。
2:在此调用函数执行前,开始创建新的上下文。
3:建立阶段:
   建立变量对象:
   建立arguments。检查上下文中的参数(就是function(i)中的i);建立该对象下的属性和属性值(undefined)。
   检查当前上下文中的函数声明:每找到一个函数声明,就在变量对象下用函数名建立一个属性属性值是指向该函数在内存中地址的一个引用。若上述函数名已经存在于变量对象中,则对应的属性值会被新的引用覆盖
 检查上下文中的变量声明: 每找到一个变量声明,用变量名建立一个属性,属性值为undefied。若变量已经存在,则跳过(这是为了防止指向函数的属性值被变量的undefined覆盖)
 初始化作用域链。
确定this的指定对象。
在代码执行阶段执行函数体中代码,一行一行执行代码,给变量对象中的属性赋值。

如果把执行环境当成一个对象

这是网上一个很经典的解释,它把执行环境看成一个对象,对象中包含了3个属性:

excutionContextObj = {
variableobject :{/*arguments,参数,函数声明,变量*/}
scopeChain :{/*变量对象以及父级上下文中的变量对象*/}
this:{}
}

举个栗子:

function test(i) {
 var a = 'hello';
 var b = function privateB{};
 function c(){
 }
}
test(11);

调用test(22)的时候,执行环境建立如下:

testExcutionContext = {
  variableObject : {
             arguments :{
                  0 : 22,
                  length : 1
              } ,  
            i : 22,
            c : pointer to function c(),
            a : undefined,
            b : undefined
},
 scopeChain : {.........},
 this : {   }
}

代码执行阶段:

 testExcutionContext = {
  variableObject : {
             arguments :{
                  0 : 22,
                  length : 1
              } ,  
            i : 22,
            c : pointer to function c(),
            a : hello,
            b : pointer to function privateB()
},
 scopeChain : {.........},
 this : {   }
}

看到这里我们就知道了变量声明提升的内涵了吧~
在代码执行阶段执行函数体中代码,一行一行执行代码,给变量对象中的属性赋值代码执行是按照从上之下的顺序,而解析也有其顺序。.值得一提的是,执行环境内重名时的覆盖与跳过机制,也会产生许多意向不到的后果,这些在面试题中出现的就频繁啦~

执行环境扯完了我们来继续扯作用域和作用域链。
作用域scope在声明函数的时候就被创建了。每当建立一个函数时,会自动生成一个scope并加入到scopchain中。这么一说你应该了解了, 作用域的概念与执行环境其实有所重叠。在作用上也是有相似之处的:作用域链的作用是,保证对执行环境有权访问的所有变量和函数的有序访问。执行环境作用是定义了变量和函数访问的其他数据,以及决定各自行为。而作用域链是保证访问的有序性。
在之前提到的上下文对象中我们可以看到:

scopeChain :{ /*变量对象以及父级上下文中的变量对象*/}

什么是变量对象相信已经不用再解释了吧~作用域链中的变量对象是一个包一个,最后知道全局的大包包里的。标识符的解析是沿着作用域链一级一级的搜索的过程,从目前的作用域开始一层一层向外搜索,知道找到标识符。若找不到,会导致错误发生。
对比原型链,原型链是为了找对象的属性。而作用域链是为了找标识符。而且他们的模型都是链式的。前者的链是沿着JS对象的原型,后者是沿着作用域的层级关系。

结语

写着写着发现写了好久了。有关闭包的内容牵扯到其应用,下一张我们再来详细的讲解吧~