理解JavaScript闭包

96 阅读3分钟

闭包是JavaScript语言的重要部分,理解闭包对学习js来说是必要的,本文可以帮助小伙伴们快速理解闭包

定义:一个函数和对其周围状态的引用捆绑在一起,这样的组合就是闭包(closure)。不同的数据对闭包的定义有所不同,此处引用《你不知道的js》对闭包的定义

例子1:
function closure() {
    const a = 1;
    return function() {
        console.log(a);
    };
};
const fun = closure();
fun();  // 1

正常来说当函数执行完毕后,函数作用域或者说函数作用域对象会被销毁,其内部的变量无法再被访问,但此处closure函数执行完毕后,把返回的匿名函数赋值给了变量funfun执行时仍然可以访问到closure函数作用域中的变量a,这个就是闭包导致的。

闭包的另一种描述:

当外侧层函数执行完毕后,其内层函数依然持有该外层函数作用域(函数作用域对象)的引用,令其无法释放,这个引用就叫闭包。(例子1中外侧函数作用域中的a变量被引用)

例子2:
function closure() {
    let a = 1;
    return function() {
        a++;
        console.log(a);
    };
};
const fun1 = closure();
fun1();  //2
fun1();  //3
const fun2 = closure();
fun2();  //2
fun1();  //4

从此例中我们可以看出每次外侧函数执行完,都会形成一个新的闭包,多次执行产生的闭包都是独立的,互不影响。

隐匿的闭包
let t = null;
function closure(){
  const arr1 = new Array(10000000).fill('1*');
  const arr2 = new Array(10000000).fill('1*'); 
  const fun1 = function(){
    console.log(arr2);
  };
  t = function fun2(){
    console.log('hi');
  };
};
closure();

我们看这个例子,虽然closure函数执行过程中把内部一个名为fun2的函数赋值给了t,但该函数中并没有引用任何closure内部的变量,那就应该不会形成闭包。 让我们来打开谷歌浏览器的性能监控器,再执行例子中这段代码看一看。

闭包.JPG

咦?这跟我们的猜想是不一致的,函数执行后明显看到js堆内存占用增加了很多,即使随后触发了GC(垃圾回收),也有相当大块的内存仍然没有被释放。

这是什么原因呢?其实这里其实形成的一个隐匿的闭包,函数fun2是在closure中生成并赋值给外面的t变量,虽然fun2看起来并没有引用closure中定义的变量,但fun1fun2共享引用变量的,所以变量arr2所指向的数组最后仍被t引用着,无法被回收,形成了闭包 (arr1 会正常释放)

注意:该函数作用域中定义的变量会被在该函数中定义的所有函数之间共享引用(所引用的是指针,一个引用其它函数也会引用),如果形成了闭包那么该指针指向的内容常驻内存,不会被释放!

千万记住闭包在使用后一定及时清理,避免浪费内存!

下面列举一些闭包的常用场景

  1. 做缓存
  2. 实现私有化
  3. 模块化开发
  4. 单例模式
  5. 防抖、节流

理解闭包后,可以尝试手写实现一下哦