JavaScript 中闭包的优点和缺点

83 阅读4分钟

优点

  1. 保护变量:闭包可以保护函数内的变量,使得这些变量不会被外部代码所修改。这对于需要维护状态的函数非常有用,可以确保变量不会被误修改。
function counter() {
  let count = 0;
  return function() {
    return ++count;
  };
}

const increment = counter();
console.log(increment()); // 输出 1
console.log(increment()); // 输出 2
console.log(increment()); // 输出 3

在上面的例子中,我们创建了一个 `counter` 函数,它返回了一个闭包,该闭包可以访问 `count` 变量。
由于该变量是在闭包内部声明的,因此外部无法直接修改它,从而保护了其值。
  1. 实现封装:闭包可以实现一些类似于面向对象编程中的封装特性。通过闭包,可以将一些变量和函数私有化,不对外暴露,从而避免其他代码的访问和修改,提高代码的安全性和可维护性。
function createPerson(name) {
  let age = 0;

  function increaseAge() {
    age++;
  }

  return {
    getName() {
      return name;
    },
    getAge() {
      return age;
    },
    celebrateBirthday() {
      increaseAge();
      console.log(`Happy birthday, ${name}!`);
    }
  };
}

const person = createPerson('Alice');
console.log(person.getName()); // 输出 'Alice'
console.log(person.getAge()); // 输出 0
person.celebrateBirthday(); // 输出 'Happy birthday, Alice!'
console.log(person.getAge()); // 输出 1

在上面的例子中,我们创建了一个 `createPerson` 函数,它返回了一个对象,
该对象有三个方法:`getName``getAge``celebrateBirthday`。
其中 `getName``getAge` 方法是公开的,而 `increaseAge` 方法是私有的。
由于 `age` 变量只在闭包内部可见,因此外部无法直接访问或修改它,从而实现了封装。
  1. 延长变量寿命:当函数执行完毕后,其内部的变量会被销毁。但是如果存在闭包,则这些变量的生命周期会被延长,直到闭包被销毁。这在一些需要长期保持状态的场景中非常有用。
function createLogger() {
  const logs = [];

  return {
    log(message) {
      logs.push(message);
    },
    printLogs() {
      console.log(logs.join('\n'));
    }
  };
}

const logger = createLogger();
logger.log('First log message');
logger.log('Second log message');
logger.printLogs(); // 输出 'First log message\nSecond log message'

在上面的例子中,我们创建了一个 `createLogger` 函数,它返回了一个对象,
该对象有两个方法:`log``printLogs`。在 `log` 方法中,
我们将传入的日志消息保存到 `logs` 数组中。由于 `logs` 数组只在闭包内部可见
,因此外部无法直接访问或修改它,从而确保了日志的安全性。
而由于 `logger` 对象一直存在,因此 `logs` 数组的生命周期也会一直延长,直到 `logger` 被销毁。



  1. 实现高阶函数:闭包可以用来实现高阶函数,即函数可以作为参数或返回值传递。这可以让代码更加简洁、灵活,提高代码的可读性和可维护性。
function createMultiplier(multiplier) {
  return function(number) {
    return number * multiplier;
  };
}

const double = createMultiplier(2);
console.log(double(3)); // 输出 6

const triple = createMultiplier(3);
console.log(triple(3)); // 输出 9
定义了一个 `createMultiplier` 函数,它返回一个闭包,该闭包接受一个参数 `number`,
并将其与传入的 `multiplier` 相乘。由于闭包可以访问 `multiplier` 变量,
因此我们可以轻松地创建一个 `double` 函数和一个 `triple` 函数,
它们分别将输入参数乘以 23

缺点

  1. 内存泄漏:如果闭包中引用了一些大量的变量或者对象,那么这些变量和对象的内存会一直被占用,直到闭包被销毁。这会导致内存泄漏的问题,影响程序的性能。
function createCounter() {
  let count = 0;
  setInterval(function() {
    count++;
  }, 1000);
  return function() {
    return count;
  };
}

const counter = createCounter();
console.log(counter()); // 输出 0


在上面的例子中,我们创建了一个 `createCounter` 函数,它返回了一个闭包,该闭包可以访问 `count` 变量。
同时,我们在 `createCounter` 函数中设置了一个间隔为 1 秒的定时器,用于每秒将 `count` 变量自增 1。
但是由于该定时器是闭包中的一个引用,因此 `createCounter` 返回的闭包永远不会被释放,从而导致内存泄漏
  1. 变量共享:闭包可以共享外部函数中的变量,如果这些变量被多个闭包共享,则可能会出现不可预料的结果,导致程序出现错误。
function createCounter() {
  let count = 0;
  return function() {
    return ++count;
  };
}

const counter1 = createCounter();
console.log(counter1()); // 输出 1

const counter2 = createCounter();
console.log(counter2()); // 输出 1
console.log(counter2()); // 输出 2
console.log(counter1()); // 输出 2

在上面的例子中,我们创建了一个 `createCounter` 函数,它返回了一个闭包,
该闭包可以访问 `count` 变量。由于 `count` 变量是在闭包内部声明的,
因此每次调用 `createCounter` 函数时都会创建一个新的 `count` 变量。
这意味着如果我们同时使用多个计数器,它们会相互影响,从而导致变量污染。



  1. 性能问题:使用闭包会带来一定的性能开销,因为闭包需要维护一个额外的引用环境。在大量使用闭包的情况下,会对程序的性能产生一定的影响。

function calculateAverage() {
  const values = [1, 2, 3, 4, 5];
  let sum = 0;
  for (let i = 0; i < values.length; i++) {
    sum += values[i];
  }
  return function() {
    return sum / values.length;
  };
}

const average = calculateAverage();
console.log(average()); // 输出 3

在上面的例子中,我们创建了一个 `calculateAverage` 函数,它返回了一个闭包,
该闭包可以访问 `sum` 和 `values` 变量。但是由于每次调用闭包时都需要重新计算平均值,
因此该代码可能会带来一些性能问题。