javascript高级程序第三版学习笔记【执行环境、作用域】

执行环境 

 执行环境定义了变量和函数有权访问的其他数据,决定了他们各自的行为。

每个执行环境都有一个与之关联的变量对象(variable object),环境中定义的所有变量和函数都保存在这个对象上,虽然我们在编写代码的时候无法访问这个对象,但解析器在处理数据时会在后台用到它。

ECMAScript实现的宿主环境不同,表示的执行环境对象也不一样。

在javascript中,可执行的JavaScript代码分三种类型:
1. Global Code,即全局的、不在任何函数里面的代码,例如:一个js文件、嵌入在HTML页面中的js代码等。
2. Eval Code,即使用eval()函数动态执行的JS代码。
3. Function Code,即用户自定义函数中的函数体JS代码。
不同类型的JavaScript代码具有不同的执行环境,这里我们不考虑evel code,对应于global code和function code存在2种执行环境:全局执行环境和函数执行环境。

 

全局执行环境

 在一个页面中,第一次载入JS代码时创建一个全局执行环境,全局执行环境是最外围的执行环境,在Web浏览器中,全局执行环境被认为是window对象。因此,所有的全局变量和函数都是作为window对象的属性和方法创建的。

某个执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁,全局执行环境直到应用程序退出后---例如关闭浏览器和网页---时才被销毁。

 

函数执行环境

 每个函数都有自己的执行环境,当执行进入一个函数时,函数的执行环境就会被推入一个执行环境栈的顶部并获取执行权。当这个函数执行完毕,它的执行环境又从这个栈的顶部被删除,并把执行权并还给之前执行环境。这就是ECMAScript程序中的执行流。

 

【定义期】

函数定义的时候,都会创建一个[[scope]]属性,这个对象对应的是一个对象的列表,列表中的对象仅能javascript内部访问,没法通过语法访问。

我们定义一全局函数A,那么A函数就创建了一个A的[[scope]]属性。此时,[[scope]]里面只包含了全局对象【Global Object】。而如果, 我们在A的内部定义一个B函数,那B函数同样会创建一个[[scope]]属性,B的[[scope]]属性包含了两个对象,一个是A的活动对象【Activation Object】【对于函数来说】一个是全局对象,A的活动对象上面,全局对象在下面。以此类摧,每个函数的都在定义的时候创建自己的[[scope]],里面保存着一个类似于栈的格式的数据。

下面是示例代码:

// 外部函数
function A(){
     var somevar;
        
     // 内部函数
    function B(){
         var somevar;
     }
}

看下示意图:

【执行期】

当函数被执行的时候,就是进入这个函数的执行环境,首先会创一个它自己的活动对象【Activation Object】(这个对象中包含了this、参数(arguments)、局部变量(包括命名的参数)的定义,当然全局对象是没有arguments的)和一个变量对象的作用域链[[scope chain]],然后,把这个执行环境的[scope]按顺序复制到[[scope chain]]里,最后把这个活动对象推入到[[scope chain]]的顶部。这样[[scope chain]]就是一个有序的栈,这样保了对执行环境有权访问的所有变量和对象的有序访问。

 

// 第一步页面载入创全局执行环境global executing context和全局活动象
// 定义全局[[scope]],只含有Window对象
// 扫描全局的定义变量及函数对象:color【undefined】、changecolor【FD创建changecolor的[[scope]],此时里面只含有全局活动对象】,加入到window中,所以全局变量和全局函数对象都是做为window的属性定义的。
// FD已经定义好所以在此执行环境内任何位置都可以执行changecolor(),color也已经被定义,但是它的值是undefined

// 第二步color赋值"blue"
var color = "blue";

// 它是不需要赋值的,它就是引用本身
function changecolor() {
    // 第四步进入changecolor的执行环境
    // 复制changecolor的[[scope]]到scope chain
    // 创建活动对象,扫描定义变量和定义函数,anothercolor【undefined】和swapcolors【FD创建swapcolors的[[scope]]加入changecolor的活动对象和全局活动对象】加入到活动对象,活动对象中同时还要加入arguments和this
    // 活动对象推入scope chain 顶端
    // FD已经定义好所以在此执行环境内任何位置都可以执行swapcolors(),anothercolor也已经被定义好,但它的值是undefined
    
    // 第五anothercolor赋值"red"
    var anothercolor = "red";
    
    // 它是不需要赋值的,它就是引用本身
    function swapcolors() {
        // 第七步进入swapcolors的执行环境,创建它的活动对象
        // 复制swapcolors的[[scope]]到scope chain
        // 扫描定义变量和定义函数对象,活动对象中加入变量tempcolor【undefined】以及arguments和this
        // 活动对象推入scope chain 顶端
        
        // 第八步tempcolor赋值anothercolor,anothercolor和color会沿着scope chain被查到,并继续往下执行
        var tempcolor = anothercolor;
            anothercolor = color;
            color = tempcolor;    
    }

    // 第六步执行swapcolors,进入其执行环境
    swapcolors();
}

// 第三步执行changecolor,进入其执行环境
changecolor();

 

访问标识符:当执行js代码的过程中,遇到一个标识符,就会根据标识符的名称,在执行上下文(Execution Context)的作用域链中进行搜索。从作用域链的第一个对象(该函数的Activation Object对象)开始,如果没有找到,就搜索作用域链中的下一个对象,如此往复,直到找到了标识符的定义。如果在搜索完作用域中的最后一个对象,也就是全局对象(Global Object)以后也没有找到,则会抛出一个错误,提示undefined。

 

既然讲到执行期,顺便讲一下javascript的【声明提升机制Hoisting

先看下面代码:

var myvar = 'my value';  
alert(myvar); // my value  

当然会弹出my value

再看下面这样

var myvar = 'my value';  
(function(){
    alert(myvar); // undefined
    var myvar = 'you value';  
})()

结果却是undefined

这是因为,javascript解析器,进入一个函数执行环境,先对var 和 function进行扫描。相当于会把var或者function【函数声明】声明提升到执行环境顶部。

上面的代码会被解析成下面这样:

var myvar = 'my value';  
(function(){
    var myvar;
    alert(myvar); // undefined
    myvar = 'you value';  
})()

根据,标识符找查机制当执行到alert的时候,查找myvar是局部变量的myvar,此时myvar并没有被赋值。所以结果是undefined。
再看看下面的例子:

count(1,2); // 3
function count(a,b)
{
    alert(a+b);    
}

 

count(1,2); // 会报错误count is not a function
var count = function (a,b)
{
    alert(a+b);    
}

我们知道,根据声明提升机制,var和function都会被提升到执行环境的顶部,已经扫描完毕。所以,上面那种写法,会把function声明提到执行环境顶部,所以即使调用在声明的前面依然能够执行。

而下面这种写法会被解析成这样:

var count;
count(1,2); // 会报错误count is not a function
count = function (a,b)
{
    alert(a+b);    
}

因为此时count只是被扫描,但还末被引用到函数对象,所以此时,它不是一个function,所以把它当函数来执行会报错。

另外一个需要提一下的是,函数的声明是优先于变量的声明的。

 

作用域 

由上,可以得知,当查找一个个标识符的时候,是在作用域链[[scope chain]]里查找的,这个作用域链里只包含上一级的活动对象,但并不包括下一级的活动对象。这就意味着,内部执行环境可以访问外部的执行环境定义的变量,但外部的执行环境是无法访问内部执行环境定义的变量的。并且,内部环境定义的变量会覆盖掉外部环境定义的变量。我们还可以知道,特定执行环境的查找的标识符是在【定义期】就已经完成的,而不是在【执行期】,因为作用域链[[scope chain]]是从定义期的[scope]复制过来的。但是它本身的Activation Object对象是在执行期才推入,作用域链顶端的,所以本身的私有变量是【执行期】完成的。

 还是上面的例子:

// color全局变量changecolor 和 swapcolors都可以沿着scope chain访问到
var color = "blue";

function changecolor() {
   
    // anothercolor局部变量,全局访问不到 但 changecolor和swapcolors都可以沿着scope chain访问到
    var anothercolor = "red";
    
    function swapcolors() {
       
        // tempcolo局部变量,全局和changecolor访问不到 但 swapcolors都可以沿着scope chain访问到
        var tempcolor = anothercolor;
            anothercolor = color;
            color = tempcolor;    
    }

    swapcolors();
}
changecolor();

 

没有块级作用域

 与C、C++以及JAVA不同,Javscript没有块级作用域。看下面代码:

if(true){
        var myvar = "木乙";    
    }
    alert(myvar);// 木乙

根据上面我们讨论的,如果有块级作用域,外部是访问不到myvar的。再看下面

for (var i=0;i<5;i++){
            console.log(i)    
        }
        
        alert(i); // 5

对于有块级作用域的语言来说,i做为for初始化的变量,在for之外是访问不到的,这允分证明了,javascript是没有块级作用域的。

延长作用域链

虽然执行环境只有两种——全局作用域和函数作用域,但是还是可以通过某种方式来延长作用域链。因为有些语句可以在作用域链的顶部增加一个临时的变量对象。
有两种情况会发生这种现象:
1、try-catch语句的catch块;
2、with语句;
 
看下面实例:
(function bildUrl(){
            var qs = "?debug=true";
                
            with(location){
                var url = href + qs;    
            }
            alert(url)
        })()

with会把location对象的所有属性和方法包含到变量对象中,并加入到作用域链的顶部。此时访问href实际上就是location.href。

但新声明的“url”变量会加入到最近的执行环境的变量对象里。试下面例子,说明url是可以访问到的,只是此时是undefined

(function bildUrl(){
            var qs = "?debug=true";
            
            if (!url)
            {
                alert("这里可以看到url"); //可以正常显示url已经被声明并且被扫描进函数的变量对象
            }
            
            try{
                if (!a)
                {
                    alert(1);// 报错因为a根本就不存在
                }
            }
            catch(e){
                console.log("作用域链被延长了吧");
            }
            
            with(location){
                var url = href + qs;    
            }
            
        })()

对catch语句来说,会创建一个新的变量对象加入到作用域链的顶部,其中包含的是被抛出的错误对象的声明。
需要说明的是,ie8之前的版本有个不符合标准的地方,就是被势出的错误对象会被加入到执行环境的变量对象。

 

 
posted @ 2012-07-19 23:32  穆乙  阅读(3275)  评论(7编辑  收藏  举报