知识点
- 作用域链是一个对象列表或者链表。该列表或者链表,第一个添加的对象是最后一个被查找,最新添加的对象是最先被查找。
- 作用域链上的对象有两种。第一种就是全局对象。第二种就是活动对象(activation object),活动对象中存储着:a、调用函数时传递的参数所组成的类数组对象(arguments)。b、所调用函数中的局部变量。
- 当JavaScript查找某个变量时,是从作用域链上最新添加的对象开始查找,有就获取变量值,没有就往下查找,如果作用域链上不存在该变量,就抛出一个引用错误异常。
- 当定义了一个函数的时候,会先创建出该函数的对象,并且产生一个作用域链,这个作用域链会被引用到函数对象中被保存下来。
- JavaScript函数对象的内部状态包含函数的代码逻辑和还引用了当前函数的作用域链。所以我们可以认为执行环境关联的变量对象就是所调函数的函数对象。
- JavaScript解释器执行代码,每次调用函数都会创建一个新的执行环境,每个执行环境都会关联了一个变量对象,环境中定义的所有变量和函数都保存在这个对象中。 我们可以认为该关联变量对象就是所调用函数的函数对象。
- 当JavaScript解释器初始化执行代码时,它首先默认进入全局执行环境(全局执行环境被认为关联window对象)。
- 调用函数的时候,会先创建一个新的活动对象,并将函数中的局部变量和类数组对象(arguments)存储进去,然后将新的活动对象添加到定义函数时保持的作用域链上。
作用域链
以代码和图解的形式走一遍作用域链在代码执行中的流程步骤。
被执行的代码:
var globalVariate = "Global_Variate";
function functionOne(parameterOne){
var parameterTwo = "代码解析";
function functionTwo(parameterThree){
return parameterOne + parameterTwo + parameterThree;
}
return functionTwo("示例");
}
functionOne("作用域链");
var scope = "Global_Scope";
function checkscope(){
var scope = "Local_Scope";
function functionThree(){ return scope; }
return functionThree;
}
checkscope()();
当JavaScript解释器执行代码前。因为我们可以认为整个顶层的代码(也就是不包含任何函数定义内的代码)就是在一个全局定义的函数内,所以JavaScript解释器会先创建一个该函数的对象,也就是全局对象。同时也产生了一个新的作用域链。将新的作用域链的引用赋值给全局对象进行保存,如图:
顶层代码函数定义完后,接着JavaScript解释器将调用执行该函数,所以产生的全局执行环境并将全局环境压入环境栈底部,且全局执行环境关联全局对象。同时因为调用了函数,所以需要创建一个新的活动对象存储变量数据后添加到之前保存的作用域链上。又因为这是顶层函数,所以新的活动对象由全局对象替代。JavaScript语法规则中,有声明提前这么一个概念(执行代码中如果有函数声明语句,那都会提前到执行代码的顶部,先进行执行)。如此代码中的 functionOne
和checkscope
函数声明都将被先执行,这就会创建出两个新的函数对象和两个新的作用域链。如图:
由于声明提前规则,顶层代码中一部分代码已经被执行,已经执行的代码(注意函数声明语句中的函数体暂时不会去执行,只有当调用时才会执行):
function functionOne(parameterOne){
var parameterTwo = "代码解析";
function functionTwo(parameterThree){
return parameterOne + parameterTwo + parameterThree;
}
return functionTwo("示例");
}
function checkscope(){
var scope = "Local_Scope";
function functionThree(){ return scope; }
return functionThree;
}
var globalVariate = "Global_Variate";
var scope = "Global_Scope";
还剩如下代码等着JavaScript解释器去执行:
functionOne("作用域链");
checkscope()();
当解释器执行functionOne("作用域链");
代码时,首先会根据调用函数时的arguments和变量创建新的活动对象,并将新的活动对象添加到被调函数的作用域链中,如此这个新的作用域链就代表了functionOne
函数被执行时的作用域。在functionOne
函数中有一个嵌套函数,如此也会产生新的函数对象和新的作用域链,如图:
上面的图片是代码还没有执行return functionTwo("示例");
,当解释器执行了这段代码时情况又不一样了,如图:
上面的图片是代码还没有执行functionTwo
函数中的return
,如果执行了return
那么执行functionTwo
函数被调用所代表的作用域链中的执行functionTwo("示例");代码时的活动对象
将被从作用域链中删除,又因为该对象没有在其他地方被引用,所以会被当作垃圾回收掉。