再来看看闭包

再来看看闭包

什么是闭包

首先,先放入红宝书上的一个定义:闭包是指有权访问另一个函数作用域中的变量的函数
换句话说,A 函数可以访问 B 函数的变量,即 A 函数定义在函数 B 内部

function func1(){
    var str='closure'
    return function(){
        console.log(str)
    }
}
var demo=func1()
demo()// closure

为什么会产生闭包

如上面的代码,当我们执行 demo()的时候,代码解释器会在当前的作用域中查找,如果没有就会当父级作用域去查找,直到找到该变量或者不存在。其实核心就是作用域链的问题,从函数自身的作用域一直往外查询(最外面当然就是全局作用域 window 了)。这里还是做一个不完美的比喻,沿着作用域链查找变量的过程,这就像画一个洋葱,先把最里层洋葱核的那部分画上,在把外层一层层的画上,直到把最外层的表皮画上

一个 tricky 的地方

看着上面的代码,是不是一定要 return 才是闭包了?其实不是的,这只是表现形式而已,只要当前函数可以指向父级作用域,那么就是闭包

var innerFunc;
function outFunc(){
    var str='closure'
    innerFunc=function(){
        console.log(str)
    }
}
outFunc()
innerFunc()

在这个 demo 中 outFunc()的执行相当于就是创建了一个块作用域环境,外部定义的 innerFunc 相当于就是之前的 return 的功能——暴露给我们一个“接口”,可以使用它。当执行 innerFunc()的时候,由于有 outFunc()创建的块级作用域的存在,故也能一层层的向着上一级父级作用域查找变量。

一个长问的 demo,用闭包来解释

demo1:
for(var i = 1; i <= 5; i ++){
  setTimeout(function() {
    console.log(i)
  }, 0)
}

demo2:
for(let i = 1; i <= 5; i ++){
  setTimeout(function() {
    console.log(i)
  }, 0)
}

为什么 demo1 这里输出全部是 6
Answer:

  • setTimeout 为异步任务,主线程会先把 for()循环 i++这部分同步任务执行完后才去执行异步任务,因此循环结束后 setTimeout 中的回调才依次执行。

  • 对于 setTimeout 函数而言,它也是一种闭包,往上找它的父级作用域链就是 window,变量 i 挂载在了 window 上,开始执行 setTimeout 之前变量 i 已经就是 6 了,因此最后输出的值就都是 6了。


那为什么对于 demo2,用了 let 结果就变了呢?
其实这里最为主要的区别在于,因为 let 和 for 一起创建了一个块作用域,对于 setTimeout 函数而言,其最近的父级作用域链就是 for 包裹的这层块级作用域链,在这层之外才是 window,简而言之,就是因为 let 的特性,使得 for 包裹的这层也生层了一个作用域

在深入一点,从 JS 引擎的角度谈下闭包的使用

关于 JS V8 引擎的常识内容,可以看看我的上一篇文章,上一篇文章中对 JS 引擎进行了一个简单介绍包括几个易混淆的关系以及引擎的组层部分。在代码的编写中,闭包使用的场景会特别多而且复杂,有一个问题于闭包而言便是闭包会使一些变量一直保存在内存中不会自动释放,所以如果大量使用的话,就会消耗大量内存,从而影响网页性能。因此在这里想再从 JS 引擎的角度去谈谈,闭包与引擎的关系,换句话说,从引擎的角度理解闭包后,我们在使用闭包的时候才能更考虑编码性能。

先来看看红宝书上,对于垃圾收集原理的一句话讲解:“找出那些不再继续使用的变
量,然后释放其占用的内存。为此,垃圾收集器会按照固定的时间间隔(或代码执行中预定的收集时间),
周期性地执行这一操作。”

还是对第一个 demo 进行分析,对于变量 str 的生命周期进行思考,如果内部没有匿名函数(或者说闭包现象),当函数 func1()执行完后,存储局部变量 str 的内存就会被释放,but 由于闭包的原因,这部分内存依然被 str 占用,直到内部函数执行。假如说,如果执行完外部函数后很久才执行内部函数,那么相比非闭包情况,很长一段时间内,str 占用的内存便不能被释放,一直被 str 占用。

对于垃圾回收机制而言主要是标记清楚和引用计数的方式(具体过程,这里不深究)。由于垃圾收集器是周期运行的,因此如果为变量分配的内存数量很可观,回收工作量也是相当大的。因此这也是为什么闭包会消耗大量内存的原因进而影响性能的原因。

写在最后:其实在笔者在不停的逼着自己输出的时候,就发现很多之前的知识跟现在所写的知识点串联起来了。把几个重要的点串联起来了,基础知识的面也就顺其自然地形成了,有了这个扎实的知识根基,再去上面做一些拓展工作,也就更加容易了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
闭包JavaScript中一个重要且强大的概念。它指的是函数能够访问自己定义范围之外的变量,并且这些变量在函数执行完毕后仍然可以被访问到。闭包的应用场景很广泛,它可以用来创建私有变量、实现模块化、延迟执行等等。了解和掌握闭包对于理解JavaScript语言的本质,以及提高代码的可维护性和灵活性都非常有帮助。 从技术的角度来看,所有的JavaScript函数都可以被看作是闭包。因为在函数内部,它们可以访问在函数定义范围之外的变量。这也是为什么在JavaScript中,函数可以在定义之后仍然可以访问外部的变量。 总结起来,闭包JavaScript中一个非常重要的概念,它能够让函数访问自己定义范围之外的变量,并且这些变量在函数执行完毕后仍然可以被访问到。了解闭包可以帮助我们更好地理解JavaScript语言的本质,并且提高代码的可维护性和灵活性。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [深入理解JavaScript闭包](https://blog.csdn.net/qq_54384868/article/details/130182961)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [JavaScript深入之闭包](https://blog.csdn.net/TZOF_/article/details/117048674)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值