字节一面:闭包是什么?闭包的用途是什么? 野性酷女 2024-04-17 10:26 62阅读 0赞 ![a90d604fac5e42b394b6452c426b9890.png][] **前言** > 最近博主在**字节面试**中遇到这样一个面试题,这个问题也是前端面试的高频问题,因为在前端开发的日常开发中我们经常会用到闭包,我们会借助闭包来封装一些工具函数,所以更深的了解闭包是很有必要的,博主在这给大家细细道来。 -------------------- > ? 作者简介:[程序员小豪][Link 1],全栈工程师,热爱编程,曾就职于**蔚来、腾讯**,现就职于某互联网大厂,技术栈:Vue、React、Python、Java > ? 本文收录于小豪的[前端][Link 2]系列专栏,后续还会更新**前端入门以及前端面试**的一些相关文章,手把手带你从零学习前端到面试找工作,并如果有想进入前端领域工作的同学,这个[前端][Link 2]专栏会对你有所帮助,欢迎关注起来呀 > ? 本人也会持续的去关注`AIGC以及人工智能领域`的一些动向并总结到博客中,大家感兴趣的可以关注一下我的[人工智能][Link 3]专栏 > ? [云原生][Link 4]的入门学习系列,大家有兴趣的可以看一看 #### 本文目录 #### * 什么是闭包 * 应用场景 * * 实现块级作用域 * 保存内部状态 * 函数柯里化 * 单例模式 * 模拟私有属性 * 闭包的缺点 * * 如何解决闭包导致的内存泄漏? * 总结 * * 个人结语 ## 什么是闭包 ## 闭包就是每次调用外层函数时,临时创建的函数作用域对象。 因为内层函数作用域链中包含外层函数的作用域对象,且内层函数被引用,导致内层函数不会被释放,同时它又保持着对父级作用域的引用,这个时候就形成了闭包。 所以闭包通常是在函数嵌套中形成的。 例如下面的代码,就形成了闭包: javascript 复制代码function foo (){ var name = 'snail' return function(){ console.log('my name is '+name) } } var bar = foo(); bar(); 闭包并不是一个需要学习新的语法或模式才能使用的工具或技巧,它是基于词法作用域编写代码时自然产生的结果。你甚至不需要为了闭包而创建闭包,了解了闭包,你会发现,代码中闭包随处可见。 了解闭包是为了可以根据自己的意愿来识别和影响闭包的使用。接下来我们看一下闭包常见的应用场景。 ## 应用场景 ## ### 实现块级作用域 ### 首先我们来看这样一段代码: function foo(){ var result = []; for(var i = 0;i<10;i++){ result[i] = function(){ console.log(i) } } return result; } var result = foo(); result[0](); // 10 result[1](); // 10 可以看到,每个函数并不像我们期待的那样 `result[0]()` 打印 `0`,`result[1]()` 打印 `1`,以此类推。 因为 `var` 声明的 `i` 不只是属于当前的每一次循环,甚至不只是属于当前的 `for` 循环,因为没有块级作用域,变量 `i` 被提升到了函数 `foo` 的作用域中。所以每个函数的作用域链中都保存着同一个变量 `i`,而当我们执行数组中的子函数时,此时 `foo` 内部的循环已经结束,此时 `i = 10`,所以每个函数调用都会打印 `10`。 接下来我们对 `for` 循环内部添加一层即时函数(又叫立即执行函数 `IIFE`),形成一个新的闭包环境,这样即时函数内部就保存了本次循环的 `i`,所以再次执行数组中子函数时,结果就像我们期望的那样 `result[0]()` 打印 `0`,`result[1]()` 打印 `1` … function foo(){ var result = []; for(var i = 0;i<10;i++){ (function(i){ result[i] = function(){ console.log(i) } })(i) } return result; } var result = foo(); result[0](); // 0 result[1](); // 1 ### 保存内部状态 ### 首先我们来看这样一段代码: function cacheCalc(){ var cache = new Map() return function (i){ if(!cache.has(i)) cache.set(i,i*10) return cache.get(i) } } var calc = cacheCalc() console.log(calc(2)) // 20 可以看到,函数内部会使用 `Map` 保存已经计算过的结果(当然也可以是其他的数据结构),只有当输入数字没有被计算过时,才会计算,否则会返回之前的计算结果,这样就会避免重复计算。 而这样的技巧在 `Vue3源码` 中同样有使用到。[代码地址][Link 5] 这里我在阅读源码的过程中加了一些注释,导致截图中代码行号和源文件中的不一致,但是代码并未进行任何修改。 ![在这里插入图片描述][f683313d56b14196825dbb531327f523.png] 这里的 `compileToFunction` 函数会将我们编写的模板进行编译生成 `render` 函数,而为了避免重复编译,这里在内部创建了一个 `compileCache` 对象保存编译过的数据。 ### 函数柯里化 ### 首先说一下什么是函数柯里化? 柯里化是把接收多个参数的函数变成接收单一参数(最初函数的第一个参数)的函数,并且返回接收余下的参数且返回结果的新函数。 翻译成人话就是可以将一个接受多个参数的函数分解成多个接收单个参数的函数的技术,直到接收的参数满足了原来所需的数量后,才执行原函数的逻辑。 例如一个非常经典的面试题 => 实现 `add(x)(y)(z) = x+y+z` 中就用到了函数柯里化。代码如下: function add(x){ return function(y){ return function(z){ return x+y+z } } } console.log(add(1)(2)(3)) // 6 再比如我们有一个函数 `foo`,可以将输入的数字保留两位小数。此时我们需要一个函数,可以把输入数字保留两位小数并每隔三位添加一个逗号,这个时候就可以把函数 `foo` 引入进来,并在之前结果的基础上添加每隔三位添加逗号的功能。 ### 单例模式 ### 单例模式是一种常见的涉及模式,它保证了一个类只有一个实例。实现方法一般是先判断实例是否存在,如果存在就直接返回,否则就创建了再返回。单例模式的好处就是避免了重复实例化带来的内存开销: // 单例模式 function Singleton(){ this.data = 'singleton'; } Singleton.getInstance = (function () { var instance; return function(){ if (instance) { return instance; } else { instance = new Singleton(); return instance; } } })(); var sa = Singleton.getInstance(); var sb = Singleton.getInstance(); console.log(sa === sb); // true console.log(sa.data); // 'singleton' ### 模拟私有属性 ### `javascript` 没有 `java` 中那种 `public` `private` 的访问权限控制,对象中的所用方法和属性均可以访问,这就造成了安全隐患,内部的属性任何开发者都可以随意修改。虽然语言层面不支持私有属性的创建,但是我们可以用闭包的手段来模拟出私有属性: // 模拟私有属性 function getGeneratorFunc () { var _name = 'John'; var _age = 22; return function () { return { getName: function () { return _name;}, getAge: function() { return _age;} }; }; } var obj = getGeneratorFunc()(); obj.getName(); // John obj.getAge(); // 22 obj._age; // undefined ## 闭包的缺点 ## 从上面的介绍中我们可以得知,闭包的使用场景非常广泛,那我们是不是可以大量使用闭包呢?不可以,因为闭包过度使用会导致性能问题,还是看之前演示的一段代码: function foo() { var a = 2; function bar() { console.log( a ); } return bar; } var baz = foo(); baz(); // 这就形成了一个闭包 乍一看,好像没什么问题,然而,它却有可能导致 **内存泄露**。 我们知道,`javascript` 内部的垃圾回收机制用的是引用计数收集:即当内存中的一个变量被引用一次,计数就加一。垃圾回收机制会以固定的时间轮询这些变量,将计数为 `0` 的变量标记为失效变量并将之清除从而释放内存。 上述代码中,理论上来说, `foo` 函数作用域隔绝了外部环境,所有变量引用都在函数内部完成,`foo` 运行完成以后,内部的变量就应该被销毁,内存被回收。然而闭包导致了全局作用域始终存在一个 `baz` 的变量在引用着 `foo` 内部的 `bar` 函数,这就意味着 `foo` 内部定义的 `bar` 函数引用数始终为 `1`,垃圾运行机制就无法把它销毁。更糟糕的是,`bar` 有可能还要使用到父作用域 `foo` 中的变量信息,那它们自然也不能被销毁… JS 引擎无法判断你什么时候还会调用闭包函数,只能一直让这些数据占用着内存。 > 这种由于闭包使用过度而导致的内存占用无法释放的情况,我们称之为:内存泄露。 ### 如何解决闭包导致的内存泄漏? ### 返回的函数调用后,把外部的引用关系置空 function fn2(){ let test = new Array(1000).fill('isboyjc') return function(){ console.log(test) return test } } let fn2Child = fn2() fn2Child() fn2Child = null ## 总结 ## 本期博客详细介绍了**闭包是什么,闭包的应用场景,闭包的缺点以及如何解决闭包导致的内存泄漏问题**,跟着这篇博文认真的学习下来相信下次面试官再问你闭包的问题你不会在惧怕,甚至能够回答的面面俱到让面试官眼前一亮。 后续我们这个前端专栏还会讲述`ES6`、`垃圾回收`、`js算法技巧`、`Vue入门实战`、`React入门实战`、`前端面试题`等等文章,如果您感兴趣的话,欢迎点赞三连并关注我以及我的前端专栏,我们下期文章再见。 ### 个人结语 ### 各位看官老爷们好,小豪已经建立了技术交流群,如果你很感兴趣,可以私信我加入我的社群。 ?社群中不定时会有很多活动,例如学习资料分享、大厂面经分享、技术讨论、行业大佬创业杂谈等等。 ?本人目前是在互联网大厂正式工作,也有过多个大厂的工作经历,加入社群也会有简历修改辅导,模拟面试,手把手项目实战教学,大厂工作内推机会以及大厂面试题解析分享等福利。 ?社群方向很多,相关领域有Web全栈(前后端)、人工智能、AIGC、自媒体变现、前沿科技文章分享、论文精读等等。 ?不管你是多新手的小白,都欢迎你加入社群中讨论、聊天、分享,加速助力你成为下一个技术大佬!也随时欢迎您跟我沟通,一起交流,一起成长。变现、进步、技术、资料、项目、你想要的这里都会有 ?网络的风口只会越来越大,风浪越大,鱼越贵!欢迎您加入社群~一个人可以或许可以走的很快,但一群人将走的更远! ?想都是问题,做都是答案!行动起来吧!欢迎评论区or后台与我沟通交流,也欢迎您扫描下方二维码直接加入到我的交流社群! ![3806aeda9473446a931f116ad562044c.png][] [a90d604fac5e42b394b6452c426b9890.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/14/b2dd112d438a40f888cc86303a6620b3.png [Link 1]: https://blog.csdn.net/A_D_H_E_R_E [Link 2]: https://blog.csdn.net/a_d_h_e_r_e/category_12357719.html [Link 3]: https://blog.csdn.net/a_d_h_e_r_e/category_12402347.html [Link 4]: https://blog.csdn.net/a_d_h_e_r_e/category_12369482.html [Link 5]: https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Fvuejs%2Fvue-next%2Fblob%2Fmaster%2Fpackages%2Fvue%2Fsrc%2Findex.ts [f683313d56b14196825dbb531327f523.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/14/1f505be5469e419ea51c671d4c5c8218.png [3806aeda9473446a931f116ad562044c.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/14/b9b840d47323448bb172c88f2bd3c914.png
相关 字节一面:闭包是什么?闭包的用途是什么? 本期博客详细介绍了**闭包是什么,闭包的应用场景,闭包的缺点以及如何解决闭包导致的内存泄漏问题**,跟着这篇博文认真的学习下来相信下次面试官再问你闭包的问题你不会在惧怕,... 野性酷女/ 2024年04月17日 10:26/ 0 赞/ 63 阅读
相关 什么是闭包,Python闭包 ![2486780da7c98c38639e15a45aed34fb.png][] 闭包,又称闭包函数或者闭合函数,其实和前面讲的嵌套函数类似,不同之处在于,闭包中外部函数返 不念不忘少年蓝@/ 2023年10月07日 22:57/ 0 赞/ 62 阅读
相关 什么是闭包? 我们都知道一个概念。 在JS当中,一个函数可以访问其外部的变量资源。 ![format_png][] 一个典型的代码 但以下这种情况会出错。 fun 冷不防/ 2022年11月26日 14:56/ 0 赞/ 200 阅读
相关 什么是闭包? 闭包 我们都知道,js的作用域分两种,全局和局部,基于我们所熟悉的作用域链相关知识,我们知道在js作用域环境中访问变量的权利是由内向外的,内部作用域可以获得当前作用域下的 £神魔★判官ぃ/ 2022年11月22日 05:27/ 0 赞/ 201 阅读
相关 什么是闭包 \----本文摘自programming in lua 如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包 超、凢脫俗/ 2022年08月23日 12:47/ 0 赞/ 212 阅读
相关 什么是闭包 什么是闭包? 简单来说,闭包是指外部可以访问另一个函数作用域内的变量的函数,一般是定义在外层函数中的内层函数。 为什么需要闭包? 局部变量无法共享和长久的保存,而全局变量 r囧r小猫/ 2022年05月28日 08:35/ 0 赞/ 266 阅读
相关 什么是闭包? 闭包是指有权访问另一个函数作用域中变量的函数 作用:访问另一个函数作用域中变量 原理:通过匿名式函数把局部变量驻留在内存里,可以减少全局变量的使用 优点是封装性, 水深无声/ 2021年11月17日 02:50/ 0 赞/ 716 阅读
相关 什么是闭包? 什么是闭包? 说明 闭包 有些读者可能很好奇,为什么闭包会成为一个独立的概念,以及为什么它需要编程语言提供额外的支持。这些疑惑可以本文中找到答案。 - 日理万妓/ 2021年09月25日 03:46/ 0 赞/ 432 阅读
相关 闭包是什么? 1.什么是闭包? 古老定义 闭包(closure),是指函数变量可以保存在函数作用域内,因此看起来是函数将变量“包裹”了起来 那这样说来,包含变量的函数就是闭 骑猪看日落/ 2021年09月14日 11:32/ 0 赞/ 434 阅读
相关 什么是闭包? 闭包 定义:闭包是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量,利用闭包可以突破作用链 你的名字/ 2021年09月14日 08:38/ 0 赞/ 462 阅读
还没有评论,来说两句吧...