执行环境是JavaScript中最重要的一个概念。执行环境定义了变量或者函数有权访问的其他数据,决定了他们各自的行为。每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中,虽然我们编写的代码无法访问这个对象,但解析器在处理数据时会在后台使用它。
全局执行环境是最外围的一个执行环境。根据ECMAScript实现所在的宿主环境不同,表示执行环境的对象也不同。在web浏览器中全局执行环境被认为是window对象,因此所有全局变量和函数都是作为window对象的属性和方法创建的。某个执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁。
每个执行函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行后,栈将其环境弹出,把控制权返回给之前的执行环境。
当代码在一个环境中执行时,会创建变量对象的一个作用域链。作用域链的用途是保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端,始终都是当前执行的代码所在环境的变量对象。如果这个环境是函数,则将其活动对象作为变量对象。活动对象在最开始时只包含一个变量。即arguments对象(这个对象在全局环境中是不存在的)。作用域链中的下一个变量对象来自包含(外部)环境,而再下一个变量对象则来自下一个包含环境。这样,一直延续到全局执行环境,全局执行环境的变量对象始终都是作用域链中的最后一个对象。
标示符解析是沿着作用域链一级一级的搜索表示符的过程。搜索过程始终从作用域链的前端开始,然后一次向后回朔,知道找到标示符为止(如果找不到标示符,通常会导致错误产生)。
请看下面代码:
var color = "blue";
function changeColor(){
if (color ==="blue"){
color = "red";
} else {
color ="blue";
}
}
changeColor();
alert("Color is now " + color);
在这个简单的例子中,函数changecolor()的作用域链包含两个对象,它自己的变量对象(其中定义者arguments对象)和全局环境的变量对象。可以在函数内部访问color,就是因为可以在这个作用域链中找到它。
此外在局部作用羽联中定义的变量可以砸局部环境中与全局变量互换使用。如下面这个例子所示:
var color = "blue";
function changeColor(){
var anotherColor ="red";
function swapColors(){
var tempColor =anotherColor;
anotherColor =color;
color = tempColor;
// color anotherColortempColor }
// color anotherColortempColor
swapColors();
}
// color changeColor();
以上代码共涉及3个执行环境:全局环境,change color()的局部环境和swapcolors()的局部环境。全局有一个变量color和一个函数changecolor()。Changecolor()的局部环境中有一个名为anothercolor的变量和一个名为swapcolors()的函数,但是它耶可以访问全局环境中的变量color。Swapcolors()的局部环境中有一个变量tempcolor,该变量只能在这个环境中访问。Changecolor()无权访问tempcolor。然而,在swapcolors()内部则可以访问其他两个环境中的所有变量。因为那两个环境是它的父执行环境。如图
这些环境之间的联系是线性的,有次序的。每个环境都可以向上搜索作用链,以查询变量和函数名,但任何环境都不能通过向下搜索作用域链而静茹另一个执行环境。
延长作用域链
虽然执行环境的类型总共有两种-全局和局部,但还是有其他办法来延长作用域链。这么说是因为有些语句可以在作用域链的前端临时增加一个变量对象,该变量对象会在代码执行后被移除,在两种情况下会发生这种对象。具体来说,就是当执行流进入下列任何一个语句时,作用域链就会得到加长:
try-catch语句的catch快;
with语句
这两个语句都会在作用于链的前端添加一个变量对象。对with语句来说,会讲指定的对象添加到作用域链中。对catch语句来说,会创建一个新的变量对象,其中包含的是被跑出的错误对象的声明。
function buildUrl() {
var qs ="?debug=true";
with(location){
var url = href + qs;
}
return url; }
with语句接受的是location对象。因此其变量对象中就包含了location兑现的所有属性和方法。而这个变量对象被添加到作用域的前端。Buildurl()函数中定义了qs。当在with语句中引用变量href时(实际引用的是location.href)。可以在当前执行环境的变量对象中找到。当引用变量qs时,引用的则是在buildurl中定义的那个变量,至于with语句内部,则定义了一个名为url的变量,因而url就成了函数执行环境的一部分,所以可以作为函数的值被返回。
没有块级作用域
javascript没有块级作用域经常会导致理解上的困惑。
if (true) {
var color ="blue";
}
alert(color); //"blue"
这里是在一个if语句中定义了变量color。如果在别的语言中,color会在if语句执行完毕后销毁。但是在js中,if语句中的变量声明会将变量添加到当前的执行环境中。在使用for语句尤其要牢记这一点
for (var i=0; i < 10; i++){
doSomething(i);
}
alert(i); //10
1声明变量
使用var声明的变量会自动被添加到最接近的环境中。在函数内部,最接近的环境就是函数的局部环境,在with中,最接近的环境是函数环境。如果初始化变量时没有使用var声明。改变亮会自动被添加到全局环境。如下所示:
function add(num1, num2) {
var sum = num1 + num2;
return sum; }
var result = add(10, 20); //30
alert(sum); // 由于sum不是有效的变量。因此会导致错误
sum为局部环境变量。省略sum前面的var,那么就正确了
function add(num1, num2) {
sum = num1 + num2;
return sum; }
var result = add(10, 20); //30 alert(sum); //30
在严格模式下,初始化未经声明的变量会导致错误
总结
1. 执行环境:执行环境是JS中最重要的一个概念;它定义了变量和函数有权访问的其他数据;
2. 变量对象:每个执行环境都有一个与之关联的变量对象;环境中定义的所有变量和函数都保存在这个对象中;
3. 全局执行环境:最外围的一个执行环境,根据ECMAScript实现所在的宿主环境不同而不同,在Web浏览器中,全局执行环境被认为是window对象;
4. 执行环境的销毁:在某个执行环境的所有代码都执行完毕后,环境将被销毁,意味着其中所有变量和函数定义也都随之销毁;全局执行环境的销毁是在网页或浏览器关闭时执行的;
5. 执行流:每个函数都有自己的执行环境;当执行流进入一个函数时,函数的环境就被推入到一个环境栈中;在函数执行完成,栈会将其环境弹出,再把控制权返还给之前的执行环境;
6. 作用域链:当代码在环境中执行时,会创建变量对象的一个作用链;作用域链的作用是保证对执行环境有权访问的所有变量和函数的有序访问;
7. 作用域链的前端:前端,始终都是当前执行代码所在环境的变量对象;
8. 作用域链的后端:全局执行环境的变量对象始终都是作用域链中的最后一个对象;
9. 标识符(变量、函数、参数)的解析:是沿着作用域链一级一级地搜索过程;搜索的过程,始终都是从作用域链的前端开始,逐级向后,直到找到标识符为止;搜索到最后一个环境还是找不到的话,就会导致错误发生;
10.JS没有块级作用域;
11. 变量声明:使用var关键字声明变量的时候,变量将被自动添加到距离最近的可用环境中;不使用var声明的情况下,变量会被直接添加到全局环境中;
12. 标识符查询:查询的过程,就是一个向上搜索的过程,