今天学习到了《JavaScript高级程序设计》第四章环境执行及作用域,这是2个非常非常重要和基本的概念,希望总结下能让自己更好的理解。以下是整理的一些资料
参考文章:by Aaron:http://www.cnblogs.com/aaronjs/articles/2167431.html
一、执行环境
执行环境定义了变量或者函数有权访问的其他数据,决定了他们各自的行为。每个执行环境都有一个与之关联的变量对象。环境中定义的所有变量和函数都保存在这个对象中。虽然我们在编写代码的时候无法访问这个对象,但解析器在处理数据时会在后台用到它。
在javascript中,可执行的JavaScript代码分三种类型:
Global Code,即全局的、不在任何函数里面的代码,例如:一个js文件、嵌入在HTML页面中的js代码等。
Eval Code,即使用eval()函数动态执行的JS代码。
Function Code,即用户自定义函数中的函数体JS代码。
不同类型的JavaScript代码具有不同的执行环境,这里我们不考虑evel code,对应于global code和function code存在2种执行环境:全局执行环境和函数执行环境。
1、全局环境:
全局环境是最外围的一个执行环境。全局执行环境被认为是window对象。因此所有全局变量和函数都是作为window对象的属性和方法创建的。代码载入浏览器时,全局执行环境被创建(当我们关闭网页或者浏览器时全局执行环境才被销毁)。比如在一个页面中,第一次载入JS代码时创建一个全局执行环境。
这也是为什么闭包有一个内存泄露的缺点。因为闭包中外部函数被当成了全局环境。所以不会被销毁,一直保存在内存中。
2、函数执行环境
每个函数都有自己的执行环境,当执行进入一个函数时,函数的执行环境就会被推入一个执行环境栈的顶部并获取执行权。当这个函数执行完毕,它的执行环境又从这个栈的顶部被删除,并把执行权并还给之前执行环境。这就是ECMAScript程序中的执行流。
也可以这样解读:当调用一个 JavaScript 函数时,该函数就会进入与该函数相对应的执行环境。如果又调用了另外一个函数,则又会创建一个新的执行环境,并且在函数调用期间执行过程都处于该环境中。当调用的函数返回后,执行过程会返回原始执行环境。因而,运行中的 JavaScript 代码就构成了一个执行环境栈。
二、作用域/作用域链
当代码在一个环境中执行时,都会创建一个作用域链。作用域链的用途是保证对执行环境有权访问的所有变量和函数的有序访问。整个作用域链是由不同执行位置上的变量对象按照规则所构建一个链表。作用域链的最前端,始终是当前正在执行的代码所在环境的变量对象。
如果这个环境是函数,则将其活动对象(activation object)作为变量对象。活动对象在最开始时只包含一个变量,就是函数内部的arguments对象。作用域链中的下一个变量对象来自该函数的包含环境,而再下一个变量对象来自再下一个包含环境。这样,一直延续到全局执行环境,全局执行环境的Variable Object始终是作用域链中的最后一个对象。
如图所示:
《javascript高级程序设计》中提到的例子
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函数的局部环境和swapColor局部环境。
全局环境有1个变量color和1个函数changeColor()。
changeColor()函数的局部环境中具有1个anotherColor属性和1个swapColors函数,当然,changeColor函数中可以访问自身以及它外围(也就是全局环境)中的变量。
swapColor()函数的局部环境中具有1个变量tempColor。在该函数内部可以访问上面的2个环境(changeColor和window)中的所有变量,因为那2个环境都是它的父执行环境。
该段代码的作用域链如下图所示:
通过上面的分析,我们可以得知内部环境可以通过作用域链访问所有的外部环境,但外部环境不能访问内部环境中的任何变量和函数。这些环境之间是线性、有次序的。每个环境都可以向上搜索作用域链,以便查询变量和函数名;但任何环境不能通过向下搜索作用域链条而进入另一个执行环境。
关于作用域总结以下几条:
1、javascript 没有块级作用域。
代码:
for(var i=0;i<10;i++){
doSomething(i);
}alert(i); // 10
上述代码运行后会返回10,为什么呢?对于有块级作用域的语言来说,比如java或是c#代码,i做为for初始化的变量,在for之外是访问不到的。因为i只存在于for循环体重,在运行完for循环后,for中的所有变量就被销毁了。而在javascript中则不是这样的,在for中的变量声明将会添加到当前的执行环境中(这里是全局执行环境),因此在for循环完后,变量i依旧存在于循环外部的执行环境。因此,会输出10。
2、声明变量
使用var声明变量时,这个变量将被自动添加到距离最近的可用环境中。对于函数而言,自然声明的变量就会被添加到函数的局部环境中,变量在整个函数环境内都是可用的。
但是,如果变量没有是用var进行声明,将会被添加到全局环境,也就是说成位全局变量了。
所以在函数体内,进行声明时,一般要在开头用var进行声明。
var x = 1;
function rain(){
alert( x ); //undefined
var x = '10';
alert( x ); //10
}
rain()
为什么会是代码中所说明的结果呢?
我认为和2个事情有关:作用域和预解析。我们可以很容易得出上述代码的作用域链。
window全局环境和rain()函数局部环境。window全局环境中存在全局变量x和rain,而rain()函数的局部环境中包括:局部变量x。因此,在执行rain()函数时,不可能去访问全局变量x的了,因为在当前的rain()函数内已经有局部变量x。所以alert出10,但为什么第一次alert出undefined呢?这可能和预解析有关。javascript解析器,进入一个函数执行环境,先对var 和 function进行扫描。
相当于会把var或者function声明提升到执行环境顶部。
也就是说,进入我们的rain函数的时候,标识符查找机制查找到了var,查找的x是局部变量,而不是全局的x,因为函数里面的x被提升到了顶部。
在代码执行时,首先会解析出变量和函数的定义,上述代码等价于下面的代码:
function rain(){
var x;
alert( x );
x = '10';
alert( x );
}