JavaScript-详解函数执行环境及作用域链

224 阅读4分钟

执行环境及作用域链是JavaScript中一个很重要的概念。本文志在整理清楚执行环境具体是什么,以及函数执行时的作用域链是如何创建的。

执行环境:

执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。

  • 变量对象 每个执行环境都有一个与之关联的变量对象(variable object)。环境中定义的所有变量和函数都保存在这个对象中,这个对象无法访问,解析器在后台使用它。
  • 执行环境的分类 执行环境一般有全局执行环境以及函数的执行环境。
  1. 全局执行环境是最外围的一个执行环境,在 Web 浏览器中,全局执行环境被认为是 window 对象;
  2. 每个函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。

作用域链

首先说一下为什么有作用域链,作用域链保证了对执行环境有权访问的所有变量和函数的有序访问。

当代码在一个环境中执行时,会创建变量对象的一个作用域链,作用域链的前端始终都是当前执行的代码所在环境的变量对象,下一个变量来自包含(外部)环境,再下一个变量对象来自下一个包含环境...一直延续到全局执行环境(作用域链的最后一个对象)。

所以函数的局部环境不仅有权访问函数作用域的变量,而且有权访问其包含环境,直到全局环境。(这也是闭包的产生原因)反过来访问却不行。

作用域链的一些细节

当某个函数被调用时,会先初始化函数的活动对象(activation object),这个对象使用 arguments 和其他命名参数的值来初始化。前面我们说过,当代码在一个环境中执行时,会创建变量对象的一个作用域链,作用域链的前端始终都是当前执行的代码所在环境的变量对象。实际上,当环境为函数时,会将其活动对象作为变量对象,该变量对象只在函数执行的过程中存在。函数作用域链的上一层是其包含环境的变量对象。

看下面的代码(来自JS红宝书):

function compare(value1, value2){ 
    if (value1 < value2){ 
         return -1; 
    } else if (value1 > value2){ 
         return 1; 
    } else { 
        return 0; 
     } 
} 
var result = compare(5, 10);

以上代码先定义了 compare()函数,然后又在全局作用域中调用了它。当调用 compare()时,会创建一个包含 arguments、value1 和 value2 的活动对象。全局执行环境的变量对象(包含 result 和 compare)在compare()执行环境的作用域链中则处于第二位。

image.png

那么作用域链具体是如何联系起来的呢?

  • 在创建 compare 函数时,会创建一个预先包含全局变量对象的作用域链,这个作用域链被保存在内部的[[Scope]]属性中;
  • 当调用 compare 函数时,会为函数创建一个执行环境,然后通过复制函数的[[Scope]]属性中的对象构建起执行环境的作用域链;
  • 之后,又有一个活动对象(在此作为变量对象使用)被创建并被推入执行环境作用域链的前端。

对compare()函数的执行环境而言,其作用域链中包含两个变量对象:本地活动对象和全局变量对象。显然,作用域链本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。

看以下JS高级程序设计书中的例子再体会一下:

function createComparisonFunction(propertyName) { 
	 return function(object1, object2){ 
	 	var value1 = object1[propertyName]; 
	 	var value2 = object2[propertyName]; 
	 
	 	if (value1 < value2){ 
	 		return -1; 
	 	} else if (value1 > value2){ 
	 		return 1; 
	 	} else { 
	 		return 0; 
	 	} 
	 }; 
}

作用域链图解: image.png

块级作用域(拓展)

JavaScript没有块级作用域,由花括号封闭起来的块在其他类C语言中都有自己的作用域,比如下面的代码:

if(true){
    var a = 'test';
}
alert(a)//弹出test

这个代码的执行结果,依然会弹出变量a的值test。原因在于JS没有块级作用域,if语句内变量声明实际上是在全局环境中声明的,所以alert可以访问到。

以上验证了JS没有块级作用域,但是函数在执行的时候,作用域链是“从里往外”找,而不能“从外往里”找。所以,我们实际可以用函数来模仿块级作用域,通常使用匿名函数来模仿块级作用域:

(function(){

//这里是块级作用域

})();

以上立即执行的匿名函数中的变量与外部是完全隔离的,成功产生了一个块级作用域。

结束

以上关于JS中的执行环境与作用域链的学习到这里为止,记录下来作为自己的学习笔记,如果能帮到大家更好~