执行环境是 JavaScript 中最为重要的一个概念。执行环境定义了变量或函数有权访问的其余数据,决定了它们各自的行为。每一个执行环境都有一个与之关联的变量对象,环境中定义的全部变量和函数都保存在这个对象中。虽然咱们编写的代码没法访问这个对象,但解析器在处理数据时会在后台使用它。 全局执行环境是最外围的一个执行环境。根据 ECMAScript 实现所在的宿主环境不一样,表示执行环境的对象也不同。在 Web 浏览器中,全局执行环境被认为是 window 对象,所以全部全局变量和函数都是做为 window 对象的属性和方法建立的。某个执行环境中的全部代码执行完毕后,该环境被销毁,保存在其中的全部变量和函数定义也随之销毁(全局执行环境直到应用程序退出——例如关闭网页或浏览器——时才会被销毁)。每一个函数都有本身的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行以后,栈将其环境弹出,把控制权返回给以前的执行环境。ECMAScript 程序中的执行流正是由这个方便的机制控制着。前端
当代码在一个环境中执行时,会建立变量对象的一个做用域链。做用域链的用途,是保证对执行环境有权访问的全部变量和函数的有序访问。做用域链的前端,始终都是当前执行的代码所在环境的变量对象。若是这个环境是函数,则将其活动对象做为变量对象。活动对象在最开始时只包含一个变量,即 arguments 对象(这个对象在全局环境中是不存在的)。做用域链中的下一个变量对象来自包含(外部)环境,而再下一个变量对象则来自下一个包含环境。这样,一直延续到全局执行环境;全局执行环境的变量对象始终都是做用域链中的最后一个对象。 标识符解析是沿着做用域链一级一级地搜索标识符的过程。搜索过程始终从做用域链的前端开始,而后逐级地向后回溯,直至找到标识符为止(若是找不到标识符,一般会致使错误发生)。浏览器
var color = "blue"; function changeColor() { if (color === "blue") { color = "red"; } else { color = "blue"; } } changeColor(); alert("Color is now " + color);
在这个简单的例子中,函数 changeColor()的做用域链包含两个对象:它本身的变量对象(其中定义着 arguments 对象)和全局环境的变量对象。能够在函数内部访问变量 color,就是由于能够在这个做用域链中找到它。app
此外,在局部做用域中定义的变量能够在局部环境中与全局变量互换使用,以下面这个例子所示:函数
var color = "blue"; function changeColor() { var anotherColor = "red"; function swapColors() { var tempColor = anotherColor; anotherColor = color; color = tempColor; // 这里能够访问 color、anotherColor 和 tempColor } // 这里能够访问 color 和 anotherColor,但不能访问 tempColor swapColors(); } // 这里只能访问 color changeColor();
以上代码共涉及 3 个执行环境:全局环境、changeColor()的局部环境和 swapColors()的局部环境。全局环境中有一个变量 color 和一个函数 changeColor()。changeColor()的局部环境中有一个名为 anotherColor 的变量和一个名为 swapColors()的函数,但它也能够访问全局环境中的变量 color。swapColors()的局部环境中有一个变量 tempColor,该变量只能在这个环境中访问到。不管全局环境仍是 changeColor()的局部环境都无权访问 tempColor。然而,在 swapColors()内部则能够访问其余两个环境中的全部变量,由于那两个环境是它的父执行环境。下图展现了这个例子的做用域链。ui
其中,内部环境能够经过做用域链访问全部的外部环境,但外部环境不能访问内部环境中的任何变量和函数。这些环境之间的联系是线性、有次序的。每一个环境均可以向上搜索做用域链,以查询变量和函数名;但任何环境都不能经过向下搜索做用域链而进入另外一个执行环境。对于这个例子中的 swapColors()而言,其做用域链中包含 3 个对象:swapColors()的变量对象、changeColor()的变量对象和全局变量对象。swapColors()的局部环境开始时会先在本身的变量对象中搜索变量和函数名,若是搜索不到则再搜索上一级做用域链。changeColor()的做用域链中只包含两个对象:它本身的变量对象和全局变量对象。这也就是说,它不能访问 swapColors()的 环境。url
虽然执行环境的类型总共只有两种——全局和局部(函数),但仍是有其余办法来延长做用域链。这么说是由于有些语句能够在做用域链的前端临时增长一个变量对象,该变量对象会在代码执行后被移除。在两种状况下会发生这种现象。具体来讲,就是当执行流进入下列任何一个语句时,做用域链就会获得加长: spa
这两个语句都会在做用域链的前端添加一个变量对象。对 with 语句来讲,会将指定的对象添加到做用域链中。对 catch 语句来讲,会建立一个新的变量对象,其中包含的是被抛出的错误对象的声明。下面看一个例子。debug
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 就成了函数执行环境的一部分,因此能够做为函数的值被返回。code
JavaScript 没有块级做用域常常会致使理解上的困惑。在其余类 C 的语言中,由花括号封闭的代码块都有本身的做用域(若是用 ECMAScript 的话来说,就是它们本身的执行环境),于是支持根据条件来定义变量。例如,下面的代码在 JavaScript 中并不会获得想象中的结果: orm
if (true) { var color = "blue"; } alert(color); //"blue"
这里是在一个 if 语句中定义了变量 color。若是是在 C语言中,color 会在 if 语句执行完毕后被销毁。但在 JavaScript 中,if 语句中的变量声明会将变量添加到当前的执行环境(在这里是全局环境)中。在使用 for 语句时尤为要牢记这一差别,例如:
for (var i = 0; i < 10; i++) { doSomething(i); } alert(i); //10
对于有块级做用域的语言来讲,for 语句初始化变量的表达式所定义的变量,只会存在于循环的环境之中。而对于 JavaScript 来讲,由 for 语句建立的变量 i 即便在 for 循环执行结束后,也依旧会存在于循环外部的执行环境中。
1. 声明变量
使用 var 声明的变量会自动被添加到最接近的环境中。在函数内部,最接近的环境就是函数的局部环境;在 with 语句中,最接近的环境是函数环境。若是初始化变量时没有使用 var 声明,该变量会自动被添加到全局环境。以下所示:
function add(num1, num2) { var sum = num1 + num2; return sum; } var result = add(10, 20); //30 alert(sum); //因为 sum 不是有效的变量,所以会致使错误
以上代码中的函数 add()定义了一个名为 sum 的局部变量,该变量包含加法操做的结果。虽然结果值从函数中返回了,但变量 sum 在函数外部是访问不到的。若是省略这个例子中的 var 关键字,那么当 add()执行完毕后,sum 也将能够访问到:
function add(num1, num2) { sum = num1 + num2; return sum; } var result = add(10, 20); //30 alert(sum); //30
这个例子中的变量 sum 在被初始化赋值时没有使用 var 关键字。因而,当调用完 add()以后,添加到全局环境中的变量 sum 将继续存在;即便函数已经执行完毕,后面的代码依旧能够访问它。
2. 查询标识符
当在某个环境中为了读取或写入而引用一个标识符时,必须经过搜索来肯定该标识符实际表明什么。搜索过程从做用域链的前端开始,向上逐级查询与给定名字匹配的标识符。若是在局部环境中找到了该标识符,搜索过程中止,变量就绪。若是在局部环境中没有找到该变量名,则继续沿做用域链向上搜索。搜索过程将一直追溯到全局环境的变量对象。若是在全局环境中也没有找到这个标识符,则意味着该变量还没有声明。
经过下面这个示例,能够理解查询标识符的过程:
var color = "blue"; function getColor() { return color; } alert(getColor()); //"blue"
调用本例中的函数 getColor()时会引用变量 color。为了肯定变量 color 的值,将开始一个两步的搜索过程。首先,搜索 getColor()的变量对象,查找其中是否包含一个名为 color 的标识符。在没有找到的状况下,搜索继续到下一个变量对象(全局环境的变量对象),而后在那里找到了名为color 的标识符。由于搜索到了定义这个变量的变量对象,搜索过程宣告结束。下图展现了上述搜索过程。
在这个搜索过程当中,若是存在一个局部的变量的定义,则搜索会自动中止,再也不进入另外一个变量对象。换句话说,若是局部环境中存在着同名标识符,就不会使用位于父环境中的标识符,以下面的例子所示:
var color = "blue"; function getColor() { var color = "red"; return color; } alert(getColor()); //"red"
修改后的代码在 getColor()函数中声明了一个名为 color 的局部变量。调用函数时,该变量就会被声明。而当函数中的第二行代码执行时,意味着必须找到并返回变量 color 的值。搜索过程首先从局部环境中开始,并且在这里发现了一个名为 color 的变量,其值为"red"。由于变量已经找到了,因此搜索即行中止,return 语句就使用这个局部变量,并为函数会返回"red"。也就是说,任何位于局部变量 color 的声明以后的代码,若是不使用 window.color 都没法访问全局 color变量。