你对闭包了解多少?

160 阅读4分钟

想象一下,你正在观看一场魔术表演,魔术师把一只兔子放进了帽子,然后他做了一些手势,当他再次打开帽子的时候,兔子消失了。你会觉得很神奇,对吧?

这就像在JavaScript中你定义了一个函数,然后在这个函数里面又定义了另一个函数。当你调用外部的函数的时候,内部的函数能够访问到外部函数的变量,就像兔子能够在帽子里面消失一样。这就是闭包。

然后你可能会问,兔子去哪儿了?它还在帽子里面吗?

这就像你在问,我调用完外部的函数后,那些变量还在吗?答案是,它们还在。因为内部的函数依然可以访问到这些变量,这些变量就像被困在帽子里的兔子,不能逃脱。

这就是闭包的魔力所在,它可以让你在函数执行完毕后,仍然能够访问到函数内部的变量。

假设我们有一个魔术师(外部函数):

function magician() {
  let rabbit = "🐰";

  function magicHat() {
    console.log(rabbit);
  }

  return magicHat;
}

let magicShow = magician();

在这个例子中,magician就像一个魔术师,rabbit就像他的兔子,而magicHat就像他的魔术帽。你可以看到,在magician函数执行完毕后,我们依然可以通过magicShow访问到magicHat函数。这个时候,兔子(rabbit)已经消失在魔术帽(magicHat)中了。

当我们调用magicShow函数的时候:

magicShow(); // 输出: 🐰

你会发现,即使magician函数已经执行完毕,magicHat函数依然可以访问到rabbit变量。这就是闭包的魔法,即使外部函数已经返回,内部函数依然可以访问到外部函数的变量。

闭包需要注意的地方

1、 数据封装和私有变量:JavaScript没有内置的私有变量支持,但是我们可以通过闭包来模拟私有变量。闭包可以帮助我们隐藏和保护内部的状态和实现,使得这些状态和实现不会被外部代码所直接访问。

function Counter() {
  let count = 0;
  return {
    increment: function() {
      count++;
    },
    currentValue: function() {
      return count;
    }
  };
}

let counter = Counter();
counter.increment();
console.log(counter.currentValue()); // 输出 1
console.log(counter.count); // 输出 undefined

在这个例子中,count是一个私有变量,只能通过incrementcurrentValue这两个闭包函数来访问和修改。

2、创建函数工厂:如果你需要创建一系列相似的函数,闭包可以帮你实现。

function makeMultiplier(factor) {
  return function(number) {
    return number * factor;
  };
}

let doubler = makeMultiplier(2);
let tripler = makeMultiplier(3);
console.log(doubler(5)); // 输出 10
console.log(tripler(5)); // 输出 15

使用闭包需要注意的地方

1、内存消耗

由于闭包会持有一个对外部变量的引用,因此如果这个闭包长时间不被释放的话,这些外部变量也不会被垃圾回收器回收,这可能会导致内存泄漏。因此在使用闭包的时候,一定要注意及时释放不再使用的闭包。

在JavaScript中,一个闭包会引用它所在的词法环境,这就是为什么它能够访问它外部的变量。然而,只要这个闭包还在被使用,这个词法环境(以及它包含的所有变量)就不会被垃圾回收。这就可能会导致内存泄漏。

当你不再需要一个闭包时,你可以通过把引用它的变量设置为null来释放它。例如:

let myClosure = function() {
    let largeObject = new Array(1000000).fill('data');
    return function() {
        console.log(largeObject.length);
    };
}();

// 当不再需要这个闭包时:
myClosure = null;

在上面的代码中,myClosure是一个闭包,它引用了一个包含大量数据的变量largeObject。当我们不再需要这个闭包时,我们可以通过把myClosure设置为null来释放这个闭包,以及它引用的所有资源。

注意,只有当没有任何其他的引用指向这个闭包时,垃圾回收器才会释放这个闭包。也就是说,如果还有其他地方在引用这个闭包,那么即使你把myClosure设置为null,这个闭包也不会被释放。

虽然这个方法可以帮助你释放不再需要的闭包,但是在编写代码时,你应该始终尽量减少闭包的使用,以减少内存消耗。并且,你应该尽量避免在闭包中引用大量的数据,因为这会增加内存消耗。

2、变量共享

如果多个闭包引用了同一个外部变量,他们实际上是共享这个变量的。

这意味着如果一个闭包修改了这个变量的值,其他的闭包也会看到这个修改。这在一些情况下可能会导致意想不到的结果。