执行环境与作用域
执行环境是JavaScript中重要的概念。执行环境定义了 变量或函数 是否有权访问其它数据,决定了他们各自的行为。每个执行环境都有一个与之相关的变量对象,变量对象保存了执行环境中定义的所有变量和函数。
全局执行环境是最外围的一个执行环境,全局执行环境被认为是window对象,所有的全局变量和函数都是作为window的属性和方法创建的。当一个执行环境中的所有代码执行完毕后,该环境被销毁,该环境中的所有变量和函数定义也会被销毁。注:全局环境 会在应用程序退出时、或关闭浏览器后被销毁。
每个函数都有一个执行环境,当执行流进入到函数时,函数的执行环境会被推入一个环境栈,地函数执行完毕后,其执行环境被推出环境栈,把控制权返回给之前的执行环境。
当代码在执行环境中执行,会创建变量对象的一个作用域链。作用域链的作用是 对执行环境有权访问变量和函数的有序访问。作用域链前端,就是当前执行的代码所在执行环境的变量对象。如果这个环境是函数,那么该函数的活动对象就是变量对象。活动对象在刚开始时,只包含一个变量对象,即argument对象,下一个变量对象来自包含环境,再下一个变量对象来自下一个包含环境,直至全局执行环境,全局执行环境的变量对象始终是作用域链的最后一个对象。(也可以说,一个函数的作用域链上,至少包含两个变量对象,即自身的变量对象和全局环境的变量对象)
标识符解析是沿着作用域链一级一级查找标识符的过程。搜索从作用域链前端开始,然后逐级身后查找 ,直至找到标识符为止,如果找不到,则报错。
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>JavaScript</title>
</head>
<body>
<script>
var color = "blue"; //全局作用域,color保存在全局执行环境的变量对象中。
function changColor () { //changColor的执行环境
if (color == "blue") {
color = "red";
} else {
color = "blue";
}
}
changColor();
console.log(color);
</script>
</body>
</html>
这个例子中,changColor()的作用域包含两个变量对象:自身的变量对象(开始时,只包含一个变量对象即argument对象)和全局执行环境中的变量对象(下一个变量对象就是包含环境中的变量对象)。这里changColor()只被一个全局环境包含,所以只有一个全局环境中的变量对象和自身的变量对象。在函数内部可以访问到color,是因为全局环境的变量对象(包含color变量)在changColor()的作用域链上。
其此,在局部作用域中定义的变量可以在局部环境中和全局变量互换使用。
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>JavaScript</title>
</head>
<body>
<script>
var color = "blue"; //全局作用域,color保存在全局执行环境的变量对象中。
function changColor () {
var color1 = "red";
function authColor () {
var color2 = color1;
color1 = color;
color = color2;
//这里面可以访问color、color1、color2
}
//这里面可以访问color1、color
}
//外面只能访问color
</script>
</body>
</html>
这个例子中有3个执行环境:全局执行环境、changColor()局部环境、authColor()局部环境。在全局环境中有一个变量color和一个changColor()函数,changColor()局部环境中有一个变量color1和一个authColor()函数,ahthColor()局部环境中有一个变量color2。
在authColor()的作用域链上有三个变量对象,分别是:自身的变量对象、changColor()局部环境中的变量对象、全局环境中的变量对象。注:变量对象保存着执行环境中定义的变量和函数 。
在changColor()的作用域上有两个变量对象,分别是:自身的变量对象、全局环境中的变量对象。
在全局环境作用域上只有有一个自身的变量对象。
从上可以说明,在authColor()函数中可以访问color2、color1和color,因为这三个变量均在authColor()函数的作用域链上。在changColor()函数中可以访问color1和color,因为这两个变量均在changColor()函数的作用域链上。在全局环境中只能访问color和changColor()函数,因为color和changColor()函数在全局作用域链上。
上例中的执行环境作用域链关系图:
从上图可以看出,内部环境可以通过作用域链访问外部环境(即环境中的变量和函数),但外部环境不能访问内部环境中的变量和函数。环境之间的关系是线性、有次序的。每个环境可以向上搜索作用域链,以查找标识符和函数,在不能向下搜索作用域链。就拿authColor()函数来说,其作用域上有三个变量对象:自身的变量对象、changColor()函数环境中的变量对象、全局环境中的变量对象,authColor()首先会在自身的环境中查找标识符,如果没有查找到就会逐级向上一个环境作用域链中查找,直至查找到为止。全局环境是它查找的终点,如果在全局环境中也没有查找到标识符,那么就会报错。
注:函数参数也会被当作变量来对待,访问规则与执行环境中的其它变量一样。
延长作用域链
执行环境有两种:全局作用和局部作用。我们使用其它方法来延长作用域链。有些语句可以在作用域链前端增加一个变量对象,该变量对象在代码执行完毕后被销毁。
有两种方法来延长作用域链:
1、try-catch语句中的catch块
2、with语句
没有块级作用域
JavaScript不像其它语言有自己的块级作用域,而JavaScript有自己的执行环境。
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>JavaScript</title>
</head>
<body>
<script>
if (true) {
var color = "blue";
}
console.log(color); //blue
</script>
</body>
</html>
上例在其它语言中,color会在if语句执行完毕后被销毁,而在JavaScript中,if语句执行完毕后被销毁,但其变量声明会把变量提到当前的执行环境中(也可以说是上一级执行环境,这里是全局执行环境,函数执行结束后,函数的执行环境被弹出环境栈,控制权已经返回给了之前的执行环境--全局执行环境)。
JavaScript中的for循环中初始化变量的表达式定义的变量也会存在于循环外部的执行环境中(for循环的上一级执行环境)。
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>JavaScript</title>
</head>
<body>
<script>
for (var i = 0; i < 10; i++) {
//执行一些代码
}
console.log(i); //10 变量i已经在for循环的外部执行环境中
</script>
</body>
</html>
声明变量
使用关键字var声明的变量,会自动被添加到最接近的执行环境中。如果在函数内部,最接近的环境就是函数局部环境,不使用var声明的变量,会被添加到全局执行环境中。
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>JavaScript</title>
</head>
<body>
<script>
function add(num1, num2) {
var sum = num1 + num2;
return sum;
}
var sum1 = add(10, 10);
console.log(sum); //报错,sum is not defined
</script>
</body>
</html>
例子中用var定义的变量会被添加到add()函数的局部环境中,会添加到add()函数的作用域链上,外部的执行环境是访问不到局部环境中的sum变量的,因此会报错。
如果不用var定义变量,那么该变量会被添加到全局执行环境中,因此add()函数的外部环境就能访问到了。
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>JavaScript</title>
</head>
<body>
<script>
function add(num1, num2) {
sum = num1 + num2;
return sum;
}
var sum1 = add(10, 10);
console.log(sum); //20
</script>
</body>
</html>
我们在写代码的过程中,第二种不用关键字var定义变量的方式不建议使用,因为这样可能会导致出错。
查询标识符
当在某个执行环境中为了读取或访问而引入一个标识符,必须通过搜索来确定这个标识符具体代表什么,或者说是否存在。搜索过程是从作用域链前端(当前执行的代码所在环境中的变量对象)开始的,然后逐级向上查找(逐级向上一执行环境查找)与给定名字相匹配的标识符。如果在局部环境中查找到了该标识符,则搜索停止,变量就绪,如果没有查找到,则继续向上一执行环境查找,直至全局执行环境。如果在全局环境中还是没有查找到,则表示给定名字的标识符没有定义。
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>JavaScript</title>
</head>
<body>
<script>
var color = "red";
function getColor () {
retrun color;
}
getColor(); //red
</script>
</body>
</html>
在getColor()函数中引用了color变量,为了确定color的值,就会从作用域链前端开始查找,首先会在getColor()函数局部环境中查找,是否有名为color的标识符,如果没有,则继续向上一级环境中查找(即在全局环境),然后在全局环境中查找到了名为color的标识符。
如果此时,我们在getColor()局部环境中定义一个名为color的变量,且赋值不同的值,那么返回的结果就不一样了。
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>JavaScript</title>
</head>
<body>
<script>
var color = "red";
function getColor () {
var color = "blue";
retrun color;
}
getColor(); //blue
</script>
</body>
</html>
此时返回的结果不再是"red',而是"blue"。因为在搜索的过程中,在局部环境中可以查找到名为color的标识符,那么搜索就会停止,并确定color的值。
垃圾收集
JavaScript具有自动回收垃圾的机制,也就是说,开发人员不用担心内存的问题,JavaScript会自动消除垃圾和释放内存。清除垃圾的方法有两种:
标记清除
当变量进入执行环境时,就将它们标记为"f进入环境",当它们离开执行环境时,就将它们标记为"离开环境"。
垃圾收集器会在运行时为内存中的变量加上标记,然后,它会去掉环境中的变量和被环境中的变量引用的变量的标记,在此以后再被加上标记的变量将被视为准备删除的变量。垃圾回收器完成清除垃圾的工作,销毁带有标记的值以及释放其所占用的内存空间。
管理内存
执行的代码中只保存必要的值,对于不必要的值或者对象,将其赋值null,这样可以做到释放内存的作用。将内存留给其它的功能使用。