前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >面试最爱问的闭包问题!!!!

面试最爱问的闭包问题!!!!

原创
作者头像
zayyo
发布2023-12-10 09:49:46
2461
发布2023-12-10 09:49:46
举报
文章被收录于专栏:zayyo前端

JS中闭包的定义

这里先来看一下闭包的定义,分成两个:在计算机科学中和在JavaScript中。在计算机科学中对闭包的定义(维基百科):

闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures)。是在支持 头等函数 的编程语言中,实现词法绑定的一种技术;闭包在实现上是一个结构体,它存储了一个函数和一个关联的环境(相当于一个符号查找表); 闭包跟函数最大的区别在于,当捕捉闭包的时候,它的 自由变量 会在捕捉时被确定,这样即使脱离了捕捉时的上下文,它也能照常运行;闭包的概念出现于60年代,最早实现闭包的程序是 Scheme,那么我们就可以理解为什么JavaScript中有闭包: 因为JavaScript中有大量的设计是来源于Scheme的;

我们再来看一下MDN对JavaScript闭包的解释:

一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure); 也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域;在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来;

那么我的理解和总结:

一个普通的函数function,==如果它可以访问外层作用域的自由变量,那么这个函数就是一个闭包;==

从广义的角度来说:JavaScript中的函数都是闭包;

从狭义的角度来说:JavaScript中一个函数,如果访问了外层作用域的变量,那么它是一个闭包;

例子:

代码语言:javascript
复制
function foo() {
  // AO: 销毁
  var name = "foo"
  function bar() {
  //函数bar访问了外层作用域的自由变量name,那么这个函数就是一个闭包;
    console.log("bar", name)
  }

  return bar
}

var fn = foo()
fn()

那这里又会有一个问题产生,因为我们的JavaScript的内存是通过系统自动回收的

。而闭包的使用会导致内存无法被回收,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃的后果。

那系统的回收机制是什么呢?为什么不能回收

现在主流浏览器通常用采用的垃圾回收有两种方法:标记清除、引用计数

这里重点介绍 "引用计数"(reference counting),JS 引擎有一张"引用表",保存了内存里面所有的资源(通常是各种值)的引用次数。如果一个值的引用次数是0,就表示这个值不再用到了,因此可以将这块内存释放

<p align=center><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9328472d1a1740269237a5d1c297a7c8~tplv-k3u1fbpfcp-zoom-1.image" alt="link.png" /></p>

上图中,左下角的两个值,没有任何引用,所以可以释放

如果一个值不再需要了,却引用数却不为0,垃圾回收机制无法释放这块内存,从而导致内存泄漏

判断一个对象是否会被垃圾回收的标准:  从全局对象 window 开始,顺着引用表能找到的都不是内存垃圾,不会被回收掉。只有那些找不到的对象才是内存垃圾,才会在适当的时机被 gc 回收

那什么是内存泄漏呢?

内存泄漏是指:用动态存储分配函数内存空间,在使用完毕后未释放,导致一直占据该内存单元。直到程序结束。指任何对象在你不再拥有或需要它之后仍然存在。

demo代码:

代码语言:html
复制
function foo() {
  // AO: 销毁
  var name = "foo"
  function bar() {
  //函数bar访问了外层作用域的自由变量name,那么这个函数就是一个闭包;
    console.log("bar", name)
  }
  return bar
}
var fn = foo()
fn()

fn函数在调用完毕之后,foo函数会自动销毁,但foo函数中的变量name不会被销毁,因为在bar函数内部进行了访问,因为在JavaScript的内存回收机制中规定,被另一个作用域引用的变量不会被回收。除非bar函数解除调用才能销毁。

如果该函数使用的次数很少,不进行销毁的话就会变为闭包产生的内存泄漏。

那我们怎么解决闭包导致的内存泄漏问题呢?

1、手动释放(需要避免的情况)

只需将该函数赋值为null即可。但是也是最容易出错的方式,因为当项目越来越庞大时,我们总是容易遗忘手动释放。

代码语言:javascript
复制
fn = null  // 阻止内存泄漏

2、自动释放(大多数的场景)

闭包引用的变量定义在函数中,这样随着外部引用的销毁,该闭包就会被 gc 自动回收 (推荐),无需人工干涉

代码语言:javascript
复制
export const debounce = (fn, time) => {
  let info = {
    arr: new Array(10 * 1024 * 1024).fill(1),
    timer: null
  };
  return function (...args) {
    info.timer && clearTimeout(info.timer);
    info.timer = setTimeout(() => {
      fn.apply(this, args);
    }, time);
  };
};

结论

在绝大多数情况下,只要引用闭包的函数被正常销毁,闭包所占的内存都是会被 gc 自动回收的。特别是现在 SPA 项目的盛行,用户在切换页面时,老页面的组件会被框架自动清理,所以我们可以放心大胆的使用闭包,无需多虑!

我正在参与2023腾讯技术创作特训营第四期有奖征文,快来和我瓜分大奖!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • JS中闭包的定义
  • 那系统的回收机制是什么呢?为什么不能回收
  • 那什么是内存泄漏呢?
  • 那我们怎么解决闭包导致的内存泄漏问题呢?
  • 结论
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档