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

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

 

 

 

闭包

 

闭包是JavaScript中比较高级的概念和技巧,也是难理解的部分,必须熟练掌握函数表达式、作用域、变量的生存周期等概念后,才能掌握闭包的技巧。简单来说,闭包就是指有权访问另一个函数作用域中的变量的函数,创建闭包的形式,就是在一个函数内部创建另一个函数(函数嵌套)。我们来回顾之前的示例:

 

var name="张三";
function changename(othername){
    function swapname(){
        var tempname=othername;
        othername=name;
        name=tempname;
        //此处可以访问name、tempname、othername
    }
    swapname();
    //此处可以访问name、othername,但不能访问tnepname
}
changename("李四");             
alert(name);      //显示“李四”

 

 

在函数swapname()中可以访问变量othername,但othername并不是在swapname中创建的,这就是“有权访问另一个函数作用域中的变量”。但是,此类用法并没体现出闭包的特点来,闭包更常见的用法是向外部暴露(或者说是传递)函数内部的变量,并延长该变量的生存周期。来看以下代码:

 

function setname(){
    var name="李四";     
}
setname();
alert(name);    //显示为空

 

根据之前作用域的学习,我们知道在函数setname()的外部是无法访问其内部定义的局部变量name的,因此alert(name)并不会如预期般的显示“李四”,而是重新创建一个名为name的全局变量,并且由于未赋值而显示为空。那么,在某些时候我们需要获得函数内部的局部变量,那应该这么做呢?看以下代码:

 

function setname(){
    var name="李四";    
    return function(){
        return name;    
    }
}
var a=setname();
alert(a());     //显示"李四" 

 

分析以上代码会发现,这个就是“将函数作为返回值”的实现,函数setname()返回一个匿名函数,而当执行“var a=setname()”时,变量a获得了该匿名函数的引用,而该匿名函数返回了函数setname()中的一个内部变量name,因此当执行下一句“a()”时,实际上是执行了该匿名函数并获得其返回的name,这样,我们就在外部获得了本应该作用域只限定在函数setname()内部的变量name,这个就是闭包起到的作用。

 

当然,可能有人会觉得,何必如此麻烦,直接将name作为函数的返回值不也能实现相同目的吗?如以下代码:

 

function setname(){
    var name="李四";    
    return name;
}
var a=setname();
alert(a);     //显示"李四"

 

在学习执行环境中,我们说过,当某个执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量及函数也随之销毁,因此在变量的作用域外是无法访问它的。因此,以上代码执行完毕后,函数sentname()的执行环境将被销毁,name也随之被销毁。只是在销毁之前,将name的值复制给了a。

 

而“将函数作为返回值”的写法,却又是完全不同的机制,在整个代码过程中,函数setname()的执行环境并未被销毁,变量name也一直存在的,因此实际上该内部变量的生存周期被延长了。为什么会发生这种情况?我们从作用域链的角度再深入的理解之前的示例代码:

 

function setname(){
    var name="李四";    
    return function(){
        return name;    
    }
}
var a=setname();
alert(a());     //显示"李四" 

 

以上代码先定义了函数setname(),然后在全局环境中调用了它。当调用setname()时,会创建setname()的活动对象,其中包含arguments(此处为空)和变量name。当从setname()内部返回匿名函数时,同样会创建该匿名函数的活动对象,由于在一个函数内部定义的函数会将包含函数(即外部函数)的活动对象添加到它的作用域链中,因此返回的匿名函数的作用域链中将包含setname()的活动对象,而该匿名函数又被变量a引用,所以,当setname()函数代码执行完毕后,其活动对象仍然被变量a引用,因此setname()的活动对象仍然被保存,不会被销毁,自然就能继续访问setname()的内部变量name了。而只有解除对匿名函数的引用后,该活动对象才会被真正的销毁:

 

function setname(){
    var name="李四";    
    return function(){
        return name;    
    }
}
var a=setname();
alert(a());     //显示"李四"
a=null;           //解除引用,变量name被销毁

posted @ 2017-04-14 17:04  我的天空-老潘  阅读(189)  评论(0编辑  收藏  举报