关于一道面试题的寻寻觅觅(闭包问题)

  牛客网上做题目,刷到了这一题,是关于闭包的问题,感觉在这一方面还是蛮欠缺的,看完了答案感觉还是迷惑,就翻出闭包的概念看了下,又综合了大牛的解释才稍加清晰,这道题算是一个很不错的引子吧。

题目:

<ul>
 <li>click me</li>
 <li>click me</li>
 <li>click me</li>
 <li>click me</li>
</ul>

执行以下代码:

var elements=document.getElementsByTagName('li');
    var length=elements.length;
    for(var i=0;i<length;i++){
        elements[i].onclick=function(){
        alert(i);
    }
 }

请回答:

依次点击4个li标签,哪一个选项是正确的运行结果()?
A、依次弹出1,2,3,4
B、依次弹出0,1,2,3
C、依次弹出3,3,3,3
D、依次弹出4,4,4,4

答案详解:

  这道题的正解是D。嗯……选择了B的我即使在被判别错误之后还是很迷惑:为什么?难道不是依次点击的时候,每次弹出相应<li>对应的i值吗?那不就是稳稳地0,1,2,3了?

  于是我先在网上搜索了这道题的详细解释,果然已经有大牛博主曾经写过:(原址

  这题是一个陷阱,主要考的是闭包问题。只有for循环i运行到最大值时才执行点击事件,所以每次点击弹出都是4,函数中弹出的i其实是父级作用域中的i,要想达到弹出0,1,2,3,必须将参数i传进去。

  博主还给出了修正的答案,若是想要达到输出B的效果,那么就应该写成:

for(var i=0;i<length;i++){
    elements[i].onclick=function(num){
  return function(){
    alert(num);
  };
}(i);

  解释就是:

  闭包只能取得包含函数中任何变量的最后一个值。闭包所保存的是整个变量对象,而不是某个特殊的变量。
  在调用匿名函数时,我们传入变量i,由于参数是按值传递的,所以就会将变量i的当前值复制给参数num。而这个匿名函数内部,又创建并alert了一个访问num的闭包。这样,每一次点击都有num变量的一个副本,因此可以返回各自不同的数值。

  虽然博主写的解释十分不错,不过可能是我基础比较薄弱吧,还是难以搞清楚为什么。首先,就是为什么要当for循环运行到i最大值的时候才执行点击事件呢?不能循环一个执行一个么?

  于是我就查询了关于onclick事件的绑定问题:

  对于一个文档的执行,首先要渲染了DOM元素,然后就是执行事件的绑定。即立即执行了for循环,所以当用户触发事件时,实际上for循环已经执行结束了,此时的i也就已经变成了4。在我的理解里,就是在用户操作之前,浏览器就会把该绑定的事件统统绑定好,然后坐板凳等待在那里,这就很好理解了。所以到这里,我的一个迷惑点就解开了。

  不过,闭包还是有点麻烦啊,这题会了,下一次可能药丸。所以闭包到底是怎么回事呐?

闭包:

  关键时刻还是要找详解:(这是一篇详细到令人发指的文章:原址

  看完之后,大概也是有所了解了,我对里面的内容整理了下,又稍微加了些我的理解:

  首先就是要搞清楚作用域的问题。在JS里面,变量分为全局变量和局部变量。(函数内部声明变量的时候,一定要用var,否则其实你声明的是个全局变量。

  函数内部可以直接读取全局变量,但是函数外部却不能直接读取局部变量。虽说是为了安全,不过这样有的时候就会比较头疼,万一需要了呢?

function woman(){
var age=30;
}
alert(age);//wrong

  所以闭包就显出来了作用,它在很多JS模式里分量很足的一大原因就在此了。

  而闭包到底怎么用呢?即,在函数内部在声明一个函数。

function woman(){
var age=30;

    function happy(){
        alert(age);//30
    }
}

  为什么这样就可以了呢?

  由于happy函数是在woman函数的内部,所以woman函数的变量的作用域是包含了happy函数的,那么对于happy函数来说这些变量都是可以调用的。

  但是,如果happy里有局部变量,那么这个变量就只能在happy内部调用,对于woman函数来说却是不能用的,因为变量的作用域只在happy函数内部。

  即:

  父对象的所有变量,对子对象都是可见的,但是子对象内部的变量,对于父元素却是不可见的。(嗯……父爱的伟大与孩子的自私?!?)

  那么,现在我们可以在子对象里调用上级变量,要怎么在外部使用呢?

  这就很好解决了:既然子对象里可以调用,只要通过它来返回父对象的变量不就可以了嘛!(就像大人不好骗,诱导孩子偷他自家的钱)

function woman(){
var age=30;

    function happy(){
        alert(age);
    }
}

var cheat=woman();
cheat();//30

  所以说,到了这里,闭包到底是什么呢?

  通俗地说,就是让外部可以访问函数内部变量,将函数外部与函数内部连接起来的桥梁。

  而除此之外,闭包还有另一个特性:闭包会导致变量的值保存在内存中。

  我们知道,函数内部声明的局部变量,只是一个暂时的变量,在它的作用域结束了之后它就会被垃圾回收销毁了。所以我们在外部想要调用它时,它已经死掉了,当然就是error未定义了。而闭包达到了怎样的目的呢?上例中,外部的全局变量调用了woman,所以它一直占用在内存中,并没有在调用之后被销毁。

  这样就是使用闭包的一大注意点了:由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

  除了这个注意点,还有另外一点:

  由于闭包调用函数内部变量,不是拷贝,而是引用,即,它是直接用了这个变量本身。所以说:

  闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

 

  嗯……终于写完了……虽然码了好久,不过在这个过程中,也确实是加深了理解。

转载于:https://www.cnblogs.com/nicola-moon/p/8576591.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值