作用域、执行环境、闭包(二)

本文也同步发表在我的公众号“我的天空

 

 

 

执行环境

 

通过之前的示例,我们已经了解到了变量的作用域范围类似于一个层级关系,其最外部为全局变量,函数内部的为局部变量,随着函数一层层的深入,其函数内部能访问函数外部声明的变量,而反之则不行。

 

其实这诠释了JavaScript(其实也是其它编程语言)中非常重要的一个概念:执行环境(execution context),有时也称之为“上下文”。

 

执行环境定义了变量可被访问的范围、函数有权访问的其他数据。当某个执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量及函数也随之销毁,因此在变量的作用域外是无法访问它的,因为它不是还没有创建,就是已经被销毁了。

 

回到上一个示例,当函数swapname()执行完毕后,其会被销毁,因此内部声明的变量tempname也一并被销毁了,而当函数changename()执行完毕后,其传入参数othername自然也被销毁了:

 

var name="张三";
function changename(othername){
    function swapname(){
        var tempname=othername;
        othername=name;
        name=tempname;
        //此处可以访问name、tempname、othername
    }
    swapname();
    //此处swapname()已执行完毕,被销毁,因此不能访问tempname
}
changename("李四");  
//此处changename()已执行完毕,被销毁,因此不能访问othername           
alert(name);      //显示“李四”

 

每个执行环境都有一个与之关联的变量对象(variable object),该环境中定义的所有变量和函数都保存在这个对象中。例如上述示例中的函数changename(),其关联的变量对象中,就保存着参数othername,以及函数swapname();而函数swapname()与之关联的变量对象中保存着变量tempname。虽然我们编写的代码无法操作变量对象,但JavaScript在处理数据时会在后台使用它。

 

全局执行环境是最外围的一个执行环境,在Web浏览器中,全局执行环境是window对象,因此所有全局的变量和函数都是作为window对象的属性和方法创建的。全局执行环境直到应用程序退出,例如关闭网页或浏览器,才会被销毁。

 

要注意的是,如果声明变量是不加修饰符var,那么无论其在何处声明,均为全局变量,看以下代码:

 

function setname(){
    name="李四";     //声明变量未加var,因此是全局变量
    alert(name);
}

function showname(){
    alert(name);   //由于setname()中的name为全局变量,因此可以被访问到
}
showname();      //显示空
setname();       //显示“李四”
showname();      //显示“李四”

 

该示例与之前的一个示例非常相似,唯一不同的就是在函数setname()中声明变量name前未加修饰符var,因此name成为了一个全局变量,所以可以被函数showname()访问到。但是这种使用方法会使代码结构混乱,很容易产生误解,造成bug,因此强烈不建议这样的写法。

 

作用域链

 

而每个函数都有自己的执行环境,并且有与之关联的变量对象。当代码执行流进入该函数时,会创建该变量对象的作用域链,以保证对执行环境有权访问的变量和函数的有序访问。作用域链的最前端,总是当前执行环境的变量对象,接下来就是包含环境(即函数所出的外部函数),再接下来是再上一级包含环境,就像链条一样,环环相扣,而作用域链的最顶端,则是全局执行环境的变量对象。

 

当程序要遇到一个变量时,其会在作用域链最顶端(当前执行环境)的变量对象中查找是否有该变量,有则返回并结束查找,如果无则继续在作用域链的下一级寻找,层层搜索,直到最末端的全局环境的变量对象,直到找到为止。如果找不到,则通常会导致错误发生。

 

我们再来看之前的代码,分析一下函数swapname()所对应的作用域链:

 

var name="张三";
function changename(othername){
    function swapname(){
        var tempname=othername;
        othername=name;
        name=tempname;
        //此处可以访问name、tempname、othername
    }
    swapname();
    //此处swapname()已执行完毕,被销毁,因此不能访问tempname
}
changename("李四");  
//此处changename()已执行完毕,被销毁,因此不能访问othername           
alert(name);      //显示“李四”

 

在该作用域链的顶端,是函数swapname()的执行环境的变量对象,其包含变量tempname,接下来一层是函数changename()的变量对象,其包含参数othername,最后一层是全局环境,包含变量name。因此,沿着作用域链,函数swapname()可以访问这三个变量。而函数changfename()的作用域链中,并不包含函数swapname()的执行环境的变量对象,因此就无法访问swapname()内部定义的变量tempname。

 

经过两节的知识预备,下一讲我们终于可以开讲闭包了!

 

posted @ 2017-04-13 16:45  我的天空-老潘  阅读(208)  评论(0编辑  收藏  举报