执行环境是JavaScript中最为重要的一个概念,每个执行环境都有一个与之关联的变量对象,执行环境中所有的变量和函数都保存在这个对象中。我们编写的代码是无法访问这个变量对象的,只有解析器在处理数据时会在后台使用它,但有个例外,因为在Web浏览器中,全局执行环境关联的变量对象是window对象,window对象是代码可访问的,所以代码是可以访问全局执行环境的变量对象的。
当某个执行环境中所有的代码执行完毕后,该环境就会被销毁,保存在其中的所有的变量和函数也会随之销毁,全局执行环境直到应用程序退出,例如关闭网页或浏览器时才会被销毁。
每个函数都有自己的执行环境,可以称之为局部执行环境,当执行流进入一个函数时,函数的环境就会被推入一个环境栈中,而在函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境。ECMAScript程序中的执行流正是由这个方便的机制控制着。
使用Chrome可以很方便地看到函数的执行环境栈进出情况,以下面代码为例:
var mName = 'Rose';
function father(){
var fName = 'Jack';
function son() {
var sName = 'Tom';
function sayWord(){
var word = 'my name: ' + sName + ', father name: ' + fName + ', mother name: ' + mName;
console.log(word);
}
sayWord();
}
son();
}
father();
father
内部声明了一个函数son
,son
的内部声明了一个函数sayWord
,如下图,代码执行到第9行时,father
函数的执行环境进栈,
随着代码依次向后执行,如下图,son
、sayWord
也依次进栈。
函数执行完毕后又依次从栈顶出栈,控制权返回给之前的执行环境。
上图中,我们看到在环境栈的下方有个Scope
区域,Scope
指的就是作用域,Global
是全局作用域,每个函数执行环境进栈时,Local
指的就是当前执行环境的作用域。函数执行时会生成一个活动对象,这个活动对象就是变量对象,函数中声明的变量就保存在这个对象中,如上图中的fName
、sName
、word
。作用域会形成作用域链,作用域链的前端是当前执行函数的作用域,末端则是全局作用域,作用域链的用途是保证对执行环境有权访问的所有变量和函数的有序访问,解析器在解析变量或函数时,会沿着作用域链从前端逐级地向后搜索,直至找到为止,如果始终找不到,通常会报错。
可以用下面这个示意图来理解执行环境、作用域和变量对象之间的关系。
执行环境可理解为一个抽象概念,里面包含了程序执行的所有元素,作用域可视为变量和函数生效的区域,每个执行环境里又有一个变量对象用于存放声明的变量和函数。
如果读者对原型比较熟悉的话,可以阅读本段,否则忽略即可。无论是在全局作用域还是局部作用域,我们定义的变量和函数都是作为作用域变量对象的属性存在的,解析器解析时是顺着作用域链逐级搜索的。而当我们访问某个对象的属性时,这个属性要么是定义在对象上,要么是定义在原型上或者原型链的某一级上,解析器解析时是顺着原型链逐级搜索的。这一点,我们可以比较着来学习。
本文参考《JavaScript高级程序设计(第3版)》