- 全局执行环境是最外围的执行环境。
- 在 web 浏览器中,全局执行环境是 window 对象。
- 全局执行环境直到应用程序退出才会被销毁。
- 每个函数都有自己的执行环境。在执行一个函数时,函数的环境会被推入环境栈中,函数执行后,函数的环境会从当前栈中弹出,把控制权返回给之前的执行环境。
- 当代码在一个环境中执行时,会创建变量对象的作用域链。它的用途是保证执行环境有权访问所有变量和函数。
- 全局执行环境的变量对象是作用域链的最后一个对象。
- 标识符的解析是沿着作用域链一级一级搜索,搜索过程从作用域链的前端开始,然后逐级向后回溯,直到找到标识符为止(找不到会报错):
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>函数内部可以在作用域链中找到全局环境的变量</title>
</head>
<body>
<script type="text/javascript">
var color = "blue";
function changeColor(){
if (color === "blue"){
color = "green";
} else {
color = "blue";
}
}
changeColor();
console.log("Color is now " + color);//green
</script>
</body>
</html>
- 函数内部可以在作用域链中找到祖先环境下的变量:
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>函数内部可以在作用域链中找到祖先环境下的变量</title>
</head>
<body>
<script type="text/javascript">
var color = "blue";
function changeColor(){
var anotherColor = "green";
function swapColors(){
var tempColor = anotherColor;
anotherColor = color;
color =tempColor;
// 可以访问 color、anotherColor 和 tempColor
}
swapColors();
// 可以访问 color、anotherColor
}
changeColor();
console.log("Color is now " + color);//green
// 可以访问 color
</script>
</body>
</html>
- 作用域链示意图:
- 函数的内部环境可以通过作用域链访问所有的外部环境,这些环境之间的联系是线性、有次序的。
- 每个环境都可以向上搜索作用域链。
- 函数参数也被当做变量来对待,所以其访问规则与执行环境中的其他变量相同。
1 延长作用域链
当执行流进入下列语句时,作用域链会延长:
* try-catch 语句中的 catch 块
* with 语句
这些语句会在作用域的前端临时增加一个变量对象,该变量对象会在代码执行后被移除。
- 对于 catch 语句,会创建一个新的变量对象,其中包含被抛出的错误对象的声明。
- 对于 with 语句:
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>延长作用域链</title>
</head>
<body>
<script type="text/javascript">
function buildUrl(){
var qs = "?debug=true";
with (location){
var url = href + qs;
}
return url;
}
console.log(buildUrl());
</script>
</body>
</html>
- 例子中的 with 语句接收的是 location 对象,所以其变量对象就包含了 location 对象的所有属性和方法。
在 IE8及之前的版本中, catch 语句捕获的错误对象会被添加到执行环境的变量对象,而不是 catch 语句的变量对象,IE9 修复了这个问题
2 没有块级作用域
- 其他类 C 的语言中,由花括号封闭的代码块都有块级作用域,但 JavaScript 没有!
if (true){
var color = "blue";
}
console.log(color); //"blue"
- if 语句中声明的变量会将变量添加到当前的执行环境中!!在使用 for 语句时尤其要牢记这一点:
for (var i=0; i < 10; i++){
doSomething(i);
}
alert(i);//10
2.1 声明变量
- 使用 var 声明的变量会被自动添加到最接近的环境中。在函数内部,到最接近的环境是函数局部环境;在 with 语句中,是函数环境。
- 函数的局部变量,在函数外部无法访问:
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>函数的局部变量,在函数外部无法访问</title>
</head>
<body>
<script type="text/javascript">
function add(num1, num2){
var sum = num1 + num2;
return sum;
}
console.log(add(10,20));//30
console.log(sum);//错误
</script>
</body>
</html>
- 如果初始化变量没有使用 var 声明,变量会被自动添加到全局环境中:
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>函数内定义的变量(省略了 var),在函数外部可以访问</title>
</head>
<body>
<script type="text/javascript">
function add(num1, num2){
sum = num1 + num2;
return sum;
}
console.log(add(10,20));//30
console.log(sum);//30(全局变量)
</script>
</body>
</html>
- 建议在初始化变量之前,一定要先声明,这样可以避免这些问题。
- 在严格模式下,初始化未经声明的变量会报错。
2.2 查询标识符
- 搜索会从作用域链的前端开始,向上逐级查询标识符。如果在全局环境下也没有找到,即意味着这个变量还未声明:
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>沿作用域链向上搜索标识符定义</title>
</head>
<body>
<script type="text/javascript">
var color = "blue";
function getColor(){
return color;
}
console.log(getColor());//blue
</script>
</body>
</html>
- 如果局部环境中存在同名的标识符,就不会使用父环境中的标识符:
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>沿作用域链向上搜索标识符定义,找到立即停止搜索</title>
</head>
<body>
<script type="text/javascript">
var color = "blue";
function getColor(){
var color = "green";
return color;
}
console.log(getColor());//green
</script>
</body>
</html>