资讯专栏INFORMATION COLUMN

深入理解JavaScript(二):由一道题来思考闭包

曹金海 / 1256人阅读

摘要:中所有的事件绑定都是异步编程当前这件事件没有彻底完成,不再等待,继续执行下面的任务当绑定事件后,不需要等待执行,继续执行下一个循环任务,所以当我们点击执行方法的时候,循环早已结束即是最后。

概念
闭包就是指有权访问另一个函数作用域中的变量的函数
    
    
    
    
    点击li标签弹出对应数字
    
    
    
  • 0
  • 1
  • 2
  • 3

如上题,最为常见的一个例子,这里解释由这道题引出的js知识点,如上我们知道在浏览器运行无论点击哪个li标签都是弹出3,首先来理解为什么会弹出3。

程序通过for循环给每个li标签绑定了事件,然后通过点击li标签触发方法,即执行alert(i)。js中有个作用域链查找机制,首先会在onclick返回的函数作用域查找i变量的值,找不到则往上一层找i,上一层即是window全局作用域,即找到全局变量i,即for循环定义的i。

注意for循环的i并不是私有变量,而是全局变量。

js中所有的事件绑定都是异步编程(当前这件事件没有彻底完成,不再等待,继续执行下面的任务)

当绑定onclick事件后,不需要等待执行,继续执行下一个循环任务,所以当我们点击执行方法的时候,循环早已结束,即是最后i=3。故程序执行后全局变量i被循环执行后赋值为最终的3,所以当点击的时候,外层循环已经结束,页面加载完成预示着js代码都已经执行完成,即执行alert(i)时,由于i不是私有变量,便会找到上一级window作用域全局的i,所以无论点击哪个li标签都是弹出3

为什么要用闭包

那么,解决这个问题的缘由,在于i每次在页面加载完就赋值为3,alert(i)的时候总是找到全局变量i。在ES5传统语法中,能形成作用域的只有全局和函数,现在每次i找的都是全局,那么要保住i的值只能在全局和onclick返回函数的作用域中间再加一个小的私有作用域,即是大的作用域外再加一个小的作用域,这样i往上一层作用域查找时,就会获取小作用域的i的值,而不会去获取全局变量的i值。

这个思路解决问题就需要引入闭包,在这个理解上闭包是指函数变量可以保存在函数作用域内,因此看起来是函数将变量“包裹”了起来。于是,代码改成:

for(var i = 0;i < list.length;i++){
    list[i].onclick = (function(n){//形参n
        //=>让自执行函数执行,把执行的返回值(return)赋值给onclick
        //(此处onclick绑定的是返回的小函数,点击的时候执行的是小函数),
        // 自执行函数在给事件赋值的时候就已经执行了
        // 自执行函数形成一个私有作用域
        var i = n;
        return function(){
            alert(i);
        }
    })(i);//传入实参i
}

循环三次,形成三个不销毁的私有作用域(自执行函数执行),而每一个不销毁的栈内存中都存储了一个私有变量i,而这个值分别是每一次执行传递进来的全局i的值(也就是:第一个不销毁的作用域存储的是0,第二个是1,第三个是2,第四个是3);当点击的时候,执行返回的小函数,遇到变量i,向它自己的上级作用域查找。这样就达到了我们需要的效果,这种闭包实现,也可以有另一种写法。

/*原理同法二都是形成三个不销毁的私有作用域,分别存储需要的索引值*/
for(var i = 0;i < list.length;i++){
    (function(n){
        list[n].onclick = function(){
            alert(n);
        } 
    })(i)
}

对于初始的代码,如果说为什么不能实现,那原因就可归纳为:

1.执行方法,形成一个私有的栈内存,遇到变量i,i不是私有变量,向上一级作用域查找(上级作用域window)
2.所有的事件绑定都是异步编程,绑定事件后,不需要等待执行,继续执行下一个循环任务,所以当我们点击执行方法的时候,循环早已结束(让全局的i等于循环最后的结果3)
ES6语法的解决方式

在ES6中,解决这种问题只需要一个let变量,ES6中才有块级作用域(类似于私有作用域)的概念

for(let i = 0;i < list.length;i++){
    list[i].onclick = function(){
        alert(i);
    }
}
闭包对内存的影响

从上面可知,每次for都会形成一个私有作用域,每个都里面保存的变量i的值,程序运行后这些作用域并不会被销毁,所以由于闭包会携带包含它的函数的作用域,所以会比其他函数占用更多内容,过度使用闭包会导致内存占用过多。

在真实项目中为了保证JS的性能(堆栈内存的性能优化),应该尽可能的减少闭包的使用(不销毁的堆栈内存是耗性能的)

堆内存和栈内存的释放

这里又要提到一个知识点,js中存储方式的分类:

JS中的内存分为堆内存和栈内存
堆内存:存储引用数据类型值(对象:键值对 函数:代码字符串)
栈内存:提供JS代码执行的环境和存储基本类型值

粗暴理解var定义的变量存在栈内存中,如for循环中的i是存在栈内存中的;而函数,它是存在堆内存中

一般情况下,当函数执行完成,所形成的私有作用域(栈内存)都会自动释放掉(在栈内存中存储的值也都会释放掉),那为什么闭包的栈内存不会被自动释放掉,在js中也有特殊不被销毁的情况:

1.函数执行完成,当前形成的栈内存中,某些内容被栈内存以外的变量占用了,此时栈内存不能释放(一旦释放外面找不到原有的内容了)
2.全局栈内存只有在页面关闭的时候才会被释放掉

闭包则是属于第一种情况,onclick函数形成的栈内存,被小函数【alert(i),i找到onclick作用域获取i值】占用了onclick函数的栈内存(变量i是存在栈内存中),故栈内存不能被释放,所以才会说闭包过度使用容易导致内存被占用过多,因为不会自动释放内存。

堆内存的释放

堆内存让所有引用堆内存空间地址的变量赋值为null即可(没有变量占用这个堆内存了,浏览器会在空闲的时候把它释放掉)

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/106351.html

相关文章

  • 【进阶1-4期】JavaScript深入之带你走进内存机制

    摘要:引擎对堆内存中的对象进行分代管理新生代存活周期较短的对象,如临时变量字符串等。内存泄漏对于持续运行的服务进程,必须及时释放不再用到的内存。 (关注福利,关注本公众号回复[资料]领取优质前端视频,包括Vue、React、Node源码和实战、面试指导) 本周正式开始前端进阶的第一期,本周的主题是调用堆栈,今天是第4天。 本计划一共28期,每期重点攻克一个面试重难点,如果你还不了解本进阶计划...

    不知名网友 评论0 收藏0
  • JavaScript深入之执行上下文

    摘要:深入系列第七篇,结合之前所讲的四篇文章,以权威指南的为例,具体讲解当函数执行的时候,执行上下文栈变量对象作用域链是如何变化的。前言在深入之执行上下文栈中讲到,当代码执行一段可执行代码时,会创建对应的执行上下文。 JavaScript深入系列第七篇,结合之前所讲的四篇文章,以权威指南的demo为例,具体讲解当函数执行的时候,执行上下文栈、变量对象、作用域链是如何变化的。 前言 在《Jav...

    gougoujiang 评论0 收藏0
  • web前端面试题一

    摘要:需求一个输入框,用户输入时有联想搜索,每次用户输入都会触发请求,过多的请求会造成服务器的压力,如何去解决这个问题请求函数面试者延迟发送可以去解决这样的问题。 写在前面的话 一般来说,面试质量的高低很大程度影响公司是否想接受改人才,也影响了人才是否愿意去公司。质量高的面试,公司能表明对人才的要求,个人也能表明所期待的公司是一个什么模式的公司。最终会有利于双向选择的过程。能尽早的把问题暴露...

    bergwhite 评论0 收藏0
  • 程序语言

    摘要:一面应该还问了其他内容,但是两次面试多线程面试问题和答案采访中,我们通常会遇到两个主题采集问题和多线程面试问题。多线程是关于并发和线程的。我们正在共享重要的多线程面试问题和答案。。 2016 年末,腾讯,百度,华为,搜狗和滴滴面试题汇总 2016 年未,腾讯,百度,华为,搜狗和滴滴面试题汇总 【码农每日一题】Java 内部类(Part 2)相关面试题 关注一下嘛,又不让你背锅!问:Ja...

    mtunique 评论0 收藏0
  • 程序语言

    摘要:一面应该还问了其他内容,但是两次面试多线程面试问题和答案采访中,我们通常会遇到两个主题采集问题和多线程面试问题。多线程是关于并发和线程的。我们正在共享重要的多线程面试问题和答案。。 2016 年末,腾讯,百度,华为,搜狗和滴滴面试题汇总 2016 年未,腾讯,百度,华为,搜狗和滴滴面试题汇总 【码农每日一题】Java 内部类(Part 2)相关面试题 关注一下嘛,又不让你背锅!问:Ja...

    stefan 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<