js闭包详细总结分析(面试必问)

之前做笔试题,好多关于JS闭包问题,就整理出来,分享一下。

首先,引用下所谓的“官方解释”:所谓“闭包”,指的是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分

请注意加粗部分,会有益于你的理解。

来看例子1:

function a(){
   var i = 1;
   function b(){
      alert(i);
   }
   return b();
}
var c = a(); c;  // a();  

例子2:

function a(){
    var i = 1;
    function b(){
        alert(i);
    }
    return b;
}
var c = a(); c();  // a()();  

上述两个例子的运行结果都是一样的,只不过应该会有很少人用例子2那样结尾a()()那样的写法吧。嘻嘻,我也是今天才发现的。这样写的话,你是不是对函数后面的括号()有了更深的理解呢?

没错,从上面我们可以看出来,函数声明定义时肯定少不了身份证(),而当把一个函数名连带括号()也赋给一个变量,这时的变量其实是指向了这个函数,也就是说把函数的指针赋给了此变量。像上面例子2结尾那样,a()()中的前面部分a()返回值(结果)是一个函数名(指向函数的指针),也就是说不会执行。所以要在a()后面再加一个(),才会执行,相当于b()。因为a()的返回值是ba()()也就相当于b()

插播结束,返回来说“闭包”问题。

继续看例子:

function a(){
    var i = 1;
    alert(i);
}
function b(){
     alert(i);
}
b();

上面的例子是会报错的:

i 没æå®ä¹ï¼ï¼æ æ访é®ï¼

现在返回去继续想“闭包”,你好惊奇的发现,例1和例2里,变量c竟然可以弹出了i!它可是没有在变量c的同级作用域定义过啊!但是——因为“闭包”的存在:函数b()以及函数a里的变量i

注意

上面提到的,“闭包”是包含着函数b()以及变量i,好多网上的文章只简单的提到了函数b(),我认为,结合开篇中的“官方说法”,那种说法是不精确的。(变量也是表达式(闭包)的一部分)

JS闭包的有关概念。

首先应该知道的是JS中的作用域链(scope chain)概念:

(1)定义
每个人都有自己的生活环境,代码也一样,JS中的代码都有自己的“执行环境”。当代码在一个环境中执行时,就会生成变量对象的作用域链。这个链条,从当前环境中的变量对象开始,上溯至父环境(包含环境)中的变量对象,再到父环境的父环境中的变量对象···一直上溯到全局执行环境的变量对象。想象着一条锁链,从最下面开始爬,一直爬到最上顶端。每一个节点,代表着这一层级环境中的变量对象。

(2)作用
作用域链用来保证那些有权访问不同执行环境的代码,在对不同环境里的变量和函数访问时的有序性以及确定性

(3)应用
当解析一个标识符时,是沿着作用域链一级一级地上溯(回溯)搜索标识符的过程。如果找不到标识符,就会产生错误。

还是直接看代码吧:

var color = "blue";  //全局作用域
function changeColor(){
    if(color === "blue"){ //寻找`color`,此作用域(函数内)没有,则上溯到父级作用域,(这里是全局)找到了
          color = "red";
    }else{
          color = "blue";
    }
}
changeColor();
alert("Color is now " + color);

在这个例子中,函数changeColor的作用域链包含两个对象:它自己的变量对象(每个函数都有的arguments对象),以及全局环境中的变量对象。

再来看一个多层作用域链示例:

var weibing = "在";  //最外层守卫
var dajiangjun = "不在";  //2号大将军
function chengqiang(){  //城墙内
    var dajiangjun= "在";  //1号大将军
    function gongdian(){  //宫殿内
        var zaixiang = "不在";  //宰相
        if(zaixiang === "在")
             console.log("把宰相找来!");
        else if(dajiangjun === "在")
             console.log("把大将军找来!");
        else
             console.log("把卫兵找来!");
    }
     return gongdian();
}
 chengqiang();    

结果可想而知:

çå¸å¬è§å±çº§

想象这样一个场景:皇帝身居寝宫,寂寞了,想要招纳美女。那么首先想到的是宰相,他可以直接召见宰相,让宰相给自己办事。假如宰相不在,那么他可以召见大将军(职级低于宰相),让大将军给自己干;碰巧,大将军也去喝酒了,那就直接召见卫兵。在这个例子中,皇帝找到大将军了。但是有人会问,这段代码里,有两个dajiangjun啊!对啊,可是,皇帝他老人家是从自己身边找人的。最外边的那个2号大将军,虽然也在,但是他没上朝,去考察去了。(没在城墙内),所以就召见1号大将军喽。而且自然而然,在城墙外边的人(例如普通老百姓),当然是见不到宰相和皇帝的啦。
例如在上面代码下面加如下代码:

function chixinwangxiang(){ //痴心妄想
      alert("把"+zaixiang+"给爷叫出来!");
}

结果是: 

ç´å¿å¦æ³

在城墙外边,你肯定是见不到一人之下,万人之上的宰相大人啦!    - - 到现在,闭包的面纱即将被揭开,

总结:

变量的寿命兼闭包的第一大作用:延长寿命

除了变量的作用域,另外一个和闭包有着亲密关系的就是变量的生存周期了。一般来说,全局变量的生存周期是永久的,直到我们主动销毁。而在函数内不用var关键字声明的局部变量来说,当退出函数时,这些函数变量立即失去它们的价值,也就被垃圾回收机制销毁了,也算寿终正寝。可是在闭包中,却不是这样。
继续还是以代码说话:

var func = function(){
      var a = 1;  //退出后函数局部变量a直接被销毁
      a++;
      console.log(a);
}; 
func();  //2
func();  //2
func();  //依然是2

æ®éæåµç´æ¥éæ¯

现在看看这段代码:

var func = function(){
      var a = 1;
      return function(){  //匿名函数
            a++;
            console.log(a);
      }
};
var f = func(); //f是对func()的引用
f();   //输出2
f();   //输出3
f();   //输出4
f();   //输出5

é­åå°é­åé

由此可见,当退出函数后,局部变量a并没有立即消失,一直存在,这样在第二次调用时a才会是在 2的基础上加1,是3,以后每次调用也才会不断加1;这说明局部变量a一直存活着,寿命延长了!为什么呢?如果你看过我的上一篇文章,你就应该知道,在函数外边是不能访问函数(围墙)里面的变量的,而在这里,f返回了一个匿名函数的引用,那f就可以访问到func()被调用时产生的环境,也就是func()的生存空间,f可以进去闲逛啦!那么想象一下,假如你是这个名叫"func"的大观园的主人,你能不让随时都可能来的“刘姥姥”进去看看吗?既然让进,那么里面的亭台楼榭,一花一石恐怕都不能在园子一盖好,就把它们销毁了吧?

闭包的第二大作用:封闭变量

那么既然闭包可以有这样一个机制,我们可以用它来干什么呢?下面就来介绍介绍闭包这个特殊角色的奇技淫巧。其实上面已经有了第一大作用:延长寿命!现在来看一下他的第二个作用:封闭变量。其实这个也很好理解,闭包闭包,从字面上都可以理解有封闭作用啦。
继续例子:

<html>
    <body>
        <div>1</div>
        <div>2</div>
        <div>3</div>
        <div>4</div>
        <div>5</div>
    <script>
        var nodes = document.getElementsByTagName('div');
        for(var i = 0, len = nodes.length; i < len; i++){
            nodes[i].onclick = function(){
                alert(i);
            }
        };
    </script>
  </body>
</html>

测试这段代码,就会发现无论点击那个div,最后弹出的结果都是5.这是因为div节点的onclick事件是被异步触发的,当事件被触发的时候,for循环早已经结束,所以i变量的值已经是5,在后续onclick事件查找时肯定就是5啦。

那么怎么解决才能让div返回如我们所愿?这时就是闭包大展身手的时候啦!思路是将每次循环的i值封闭起来, 当沿着作用域链从内到外查找变量i时,会先找到被封闭在闭包环境中的i,这样的话,如果有5个div,这里的i就分别是0,1,2,3,4啦:

for(var i = 0, len = nodes.length; i < len; i++){
    (function(i){       
        nodes[i].onclick = function(){
                console.log(i);
            }
        })(i)
};

第三大作用:模拟面向对象

来看下面用面向对象实现的代码:

var extent = {
    value:0,
    call:function(){
        this.value++;
        console.log(this.value);
    }
};
extent.call();  //输出:1
extent.call();  //输出:2
extent.call();  //输出:3

或者:

var Extent = function(){
    this.value = 0;
};
Extent.prototype.call = function(){
    this.value++;
    console.log(this.value);
};
var extent = new Extent();
extent.call(); //输出:1
extent.call(); //输出:2
extent.call(); //输出:3

如果用闭包的方法,该怎么写呢?

var extent = function(){
    var value = 0;
    return {
        call:function(){
            value++;
            console.log(value);
        }
    }
};
var extent = extent();
extent.call();  //输出:1
extent.call();  //输出:2
extent.call();  //输出:3

领略了这么多的奇技淫巧,是不是对JavaScript的闭包有了更深的理解?其实闭包的用途远不止这么多,更精彩的还需要大家在实践中多多发现。

文章转载 https://www.cnblogs.com/xingzoudeyishu/p/5339472.html

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值