What is Closure? | 豆包MarsCode AI刷题

80 阅读4分钟

闭包是什么?

在了解闭包之前,我们要先理解JavaScript的作用域——全局作用域和局部作用域(先不考虑块级作用域)。

  • 在全局作用域下声明的变量就是全局变量.

  • 在局部作用域下声明的变量就是局部变量。

因为 作用域 链的存在,函数内部可以直接读取全局变量。而函数内部无法读取函数内部的局部变量。

作用域链是什么?

当在Javascript中使用一个变量的时候,首先Javascript引擎会尝试在当前作用域下去寻找该变量,如果没找到,再到它的上层作用域寻找,以此类推直到找到该变量或是已经到了全局作用域。

闭包的概念

闭包就是在一个函数内部创建另一个函数,让你可以在一个内层函数中访问到其外层函数的作用域。又或者说,闭包就是能够读取其他函数内部变量的函数。

function wrapValue(n) {
let local = n;
return () => local; //内层匿名函数访问外层函数变量n
}

let wrap1 = wrapValue(1);
let wrap2 = wrapValue(2);
console.log(wrap1());
// → 1
console.log(wrap2());
// → 2

这个例子来自于《JavaScript编程精解》,其中给wrap1赋值为一个包含 1 的一个函数 此时我们使用求值结果运算符来访问这个函数,函数返回了1,所以我们看到了1

wrap2 同理

function multiplier(factor) {
return number => number * factor;
}

let twice = multiplier(2);
console.log(twice(5));
// → 10

闭包的作用是什么?

闭包常常用来「间接访问一个变量」。换句话说,「隐藏一个变量」。

假设我们在做一个游戏,在写其中关于「还剩几条命」的代码。

如果不用闭包,你可以直接用一个全局变量:

window.lives = 30 // 还有三十条命

这样看起来很不妥。万一不小心把这个值改成 -1 了怎么办。所以我们不能让别人「直接访问」这个变量。怎么办呢?

用局部变量。

但是用局部变量别人又访问不到,怎么办呢?

暴露一个访问器(函数),让别人可以「间接访问」。

代码如下:

!function(){

var lives = 50

window.奖励一条命 = function(){
    lives += 1
}

window.死一条命 = function(){
    lives -= 1
}

}()

这就是闭包的第一个优点:保证函数不受外界干扰,实现封装,避免命名冲突

function f1(){
    var n=2;
    function f2(){
        var q=0;
        console.log('n=',++n);
        console.log('q=',++q);
    }
    return  f2;
}

var f=f1();
f() 
f() //这两个f(n)的结果分别是什么??

在上面这个例子中, f1 是 f2 的父函数,而 f2 被赋给了一个全局变量,所以f2 始终在内存中,且 f2 的存在依赖于 f1中的n,这就导致变量n无法被销毁,而变量q是每次被调用时新创建的,所以每次f2执行完后它就把属于自己的变量连同自己一起销毁,于是乎最后就剩下孤零零的n。

这就是闭包的第二个优点: 可以在内存中保存函数变量,不会被垃圾回收机制回收,充当缓存

但闭包也有缺点: 内存消耗很大,容易造成内存泄漏, 要谨慎使用

内存泄漏:内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

闭包的作用就是可以在一个函数的内部访问到函数外部的变量。这是因为内部函数对外部函数属于同一作用域内,通过闭包内的函数访问到变量是因为内部函数保持着对变量的 引用,当注册一个点击事件的时候,就是一个闭包,当点击事件完成的时候,还会对改变量保持着引用。

让我们再看看上面的例子,我们已经无法再通过js代码来引用到变量n,但垃圾回收器却认为这个对象还在被引用,因此在回收的时候不会释放它。导致了分配的这块内存永远也无法被释放出来。如果这样的情况越来越多,会导致内存不够用而系统崩溃。

其实内存泄漏并不是闭包造成的,而是通过闭包内的函数对变量的引用,闭包不是真正产生内存泄漏的原因! 大量博客记录的实际遇到的闭包产生内存泄漏问题的根本原因就是没有及时的断开对变量的引用,而不是注册监听事件的闭包产生的内存泄漏。如果我们对该引用可以进行控制,就可以解决内存泄漏的问题,而不应该把这个锅甩给无辜的闭包。

解决方法:可以在后面加一句

f=null

这样就销毁了其返回函数,也就解除了对不能释放的活动对象n的引用。