闭包相关以及内存回收

闭包

function f1() {
  var a = 1
  function f2() {
    return a
  }
  return f2 // return为了让f2函数能够被使用
}
var b = f1()
b() // 1    b作为全局变量获取到了局部变量a的值

在上方代码中,函数 f2 被包括在函数 f1 内部,这时 f1 内部的所有局部变量,对 f2 都是可见的。
既然 f2 可以读取 f1 中的局部变量,那么只要把 f2 作为返回值,就可以在 f1 外部读取它的内部变量了。

上面代码中的 f2 函数,就是闭包。(通俗理解:闭包就是能够读取其他函数内部变量的函数。)

闭包可以简单理解成 “定义在一个函数内部的函数“ 。所以,在本质上,闭包是连接函数内外部的桥梁。

经典例1

function f() {
  var a = 1 // 没有被闭包所引用,函数执行完毕被回收
  console.log(++a)
}
f() // 2
f() // 2  
// 变量a会在每次被调用时新创建
function f() {
  var a = 1 // 被闭包所引用,不会被回收
  return function () {
    var b = 0
    console.log(++a);
    console.log(++b)
  }
}
var f1 = f()
f1() // 2 1
f1() // 3 1

一般情况下,在函数 f 执行完后,就应该连同它里面的变量一同被销毁。
但是在这个例子中,匿名函数内部引用着 f 里的变量 a,所以变量 a 无法被销毁,而变量 b 是每次被调用时新创建的,所以每次 f1 执行完后它就把属于自己的变量(b)连同自己一起销毁,于是乎最后就剩下孤零零的 a,于是这里就产生了内存消耗的问题。

经典例2 定时器与闭包

for (var i = 0; i < 3; i++) {
  setTimeout(() => {
    console.log(i);
  }, 1000)
} // 输出三次3

由于js是单线程的,所以在执行for循环的时候setTimeout被安排到任务队列中排队等待执行,等到setTimeout可以执行的时候,for循环已经结束,i的值也已经变为3,所以打印出来三个3。

解决办法:引入闭包来保存变量i,将setTimeout放入立即执行函数中,将for循环中的循环值i作为参数传递:

for (var i = 0; i < 3; i++) {
  (function (i) {
    setTimeout(() => {
      console.log(i);
    }, 1000)
  }(i))
} // (1s later)  0 1 2 

实现每隔1000毫秒分别依次输出数字:把时间1000改为1000*i

闭包的作用

  1. 可以在函数的外部访问到函数内部的变量,也就是「间接访问一个变量」。
  2. 让这些变量始终保存在内存中,不会随着函数的结束而自动销毁。
  3. 防止命名冲突。

使用闭包可能会导致的问题「内存泄漏」:
内存泄露就是指用不到(访问不到)的变量,依然占据内存空间,不能被再次利用起来,增大了内存消耗,造成内存泄漏。
解决方法是可以在使用完变量后手动为它赋值为null。

有时会使用全局变量去存储临时信息,那么要记得使用完毕后手动赋值为 null,以回收内存。

常见的四种js内存泄漏

1.全局变量
有时候我们无法避免使用全局变量,那么记得在使用完毕后手动释放它们,例如让变量指向null。

2 被遗忘的定时器或回调函数
不需要时记得清除定时器、移除监听事件

3 闭包 (解决方法:使用完变量后手动为它赋值为null

内存回收

在js中,垃圾回收器每隔一段时间就会找出那些不再使用的数据,并释放其所占用的内存空间。

以全局变量和局部变量来说,函数中的局部变量在函数执行结束后不再被需要,所以垃圾回收器会识别并释放它们(闭包可以让这些变量保存在内存中,不会随着函数的结束而销毁)。
而对于全局变量,垃圾回收器很难判断这些变量是否需要,所以尽量少使用全局变量。

垃圾回收模式:标记清除
从根部(在js中,我们一般认定全局对象window作为root)出发看是否能达到某个对象,如果能达到则认定这个对象还被需要,如果无法达到,则释放它。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
闭包可以造成内存泄露是因为闭包会持有对外部函数作用域的引用,导致外部函数中的变量无法被垃圾回收机制回收,从而占用了额外的内存空间。 当一个函数内部定义了另一个函数,并且内部函数引用了外部函数的变量时,就形成了闭包。当外部函数执行完毕后,其作用域中的变量理论上应该被销毁,但由于内部函数仍然引用着这些变量,导致这些变量无法被垃圾回收机制回收。 以下是一个示例代码,展示了闭包如何造成内存泄露: ```javascript function createClosure() { var data = "Hello"; // 外部函数的变量 return function() { console.log(data); // 内部函数引用了外部函数的变量 }; } var closure = createClosure(); // 创建闭包 closure(); // 输出 "Hello" // 由于闭包中引用了外部函数的变量,导致外部函数的作用域无法被销毁 ``` 在上述代码中,`createClosure`函数返回了一个内部函数,该内部函数引用了`data`变量。即使`createClosure`函数执行完毕后,`data`变量仍然被内部函数引用着,无法被垃圾回收机制回收,从而造成了内存泄露。 为了避免闭包造成内存泄露,可以采取以下几种方式: 1. 尽量避免使用闭包,特别是在处理大量数据时。 2. 在不需要使用闭包时,及时释放对外部函数作用域的引用。 3. 使用`let`关键字代替`var`关键字来声明变量,因为`let`具有块级作用域,可以更容易地释放内存

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

敲起来blingbling

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值