闭包 javascript

在 JavaScript 中,闭包(Closure)是一个非常强大且常用的概念,它涉及到 函数与函数外部环境的引用。简单来说,闭包是指一个函数可以访问其外部函数的变量,即使外部函数已经执行完毕。

闭包的原理

闭包的核心原理是:函数内部可以访问外部函数的变量,即使外部函数已经执行结束

JavaScript 的函数是作用域链的一部分,当函数被调用时,它会创建一个 执行上下文,并且会形成一个 作用域链,用于查找变量。当内部函数引用了外部函数的变量时,就形成了闭包。

闭包的特点

就是变量驻留,通过内部引用外部资源,使外部资源在内存中不被回收长时间保留

作用域链的形成

每当你定义一个函数,JavaScript 会为该函数创建一个 作用域链。这个作用域链会包含函数的局部变量以及父级作用域的变量。即使外部函数执行完毕,内部函数仍然保持对外部函数的作用域的引用,这样就形成了闭包。

闭包的形成与举例

假设我们有两个函数,一个外部函数和一个内部函数:

function outer() 
{ 
let outerVar = 'I am from outer function';
 function inner() 
 { 
console.log(outerVar);
 } 
return inner; 
} 
const closureFunction = outer();
closureFunction(); // 输出: I am from outer function

在上面的例子中:

  1. outer 是外部函数,inner 是内部函数。
  2. outer 函数返回了 inner 函数。
  3. 当 outer() 被调用时,outer 执行完毕后,outerVar 本应被销毁,但由于 inner 函数在返回后仍然引用了 outerVar,所以 outerVar 被保存在内存中,inner 依然可以访问它。

闭包的作用和优势

  1. 数据封装和私有变量
    闭包可以帮助你在函数外部隐藏内部状态,提供类似私有变量的效果。通过闭包,你可以控制哪些变量对外部是可见的,哪些是不可见的。

    示例:

    function createCounter() 
    { 
    let count = 0; // count 是私有变量
     return 
     { 
       increment: function() 
       { 
         count++;
         console.log(count);
       }, 
       decrement: function()
       { 
         count--; 
         console.log(count); 
       },
       getCount: function() 
       {
        return count; 
       } 
      }; 
    } 
    const counter = createCounter(); 
    counter.increment(); // 1 
    counter.increment(); // 2 
    counter.decrement(); // 1
    console.log(counter.getCount()); // 1

    这里,count 是一个私有变量,外部不能直接访问它,只能通过 incrementdecrementgetCount 方法来操作。

  2. 回调函数中的应用
    在异步编程中,闭包是处理回调函数的常见方式。例如,setTimeout 或事件处理程序常常通过闭包访问外部变量。

    示例:

    function sayHello(name)
     { 
      setTimeout(function() 
       {
        console.log('Hello, ' + name); 
       }, 1000); 
      }
       sayHello('Alice'); // 1秒后输出:Hello, Alice

    setTimeout 中的回调函数内,name 被保存为闭包的一部分。即使 sayHello 函数已经执行完毕,name 依然可以在回调中访问到。name是外部函数的形参。他在外部函数执行完之后应该就消失了,但是内部还有一个1秒定时器,定时器的回调函数引用了这个形参name。所以这个形参在外部函数执行完之后不会消失,而是等内部定时器执行完回调函数了之后这个形参name所占用的内存才会消失

  3. 避免全局污染
    闭包可以有效避免污染全局作用域。通过将变量和方法封装在函数内部,可以减少全局命名冲突和意外覆盖。

    示例:

    (function() 
    { 
     let secret = 'I am secret'; // 这个变量不被外部访问 
     console.log(secret); // 输出: I am secret 
    }
    )();

    通过使用 立即执行函数表达式 (IIFE),我们创建了一个临时的作用域,将变量 secret 封装在函数内部,外部无法访问。

  4. 函数工厂和模块化编程
    闭包常用于函数工厂中,例如生成一系列定制化的函数,或者在模块化编程中封装私有数据。

    示例:

    function multiplier(factor) 
    { 
     return function(number) 
     { 
      return number * factor;
     }; 
    } 
    const double = multiplier(2); 
    const triple = multiplier(3); 
    console.log(double(5)); // 10 
    console.log(triple(5)); // 15

    multiplier 是一个工厂函数,它返回一个新的函数,内部使用闭包保存了 factor 的值,从而实现了不同的乘法器。返回的内嵌函数(闭包)会记住 multiplier 中的 factor 参数,即使 multiplier 函数已经执行完毕,这个 factor 仍然可以在内嵌函数中被访问,const double = multiplier(2);这里,调用 multiplier(2),返回了一个新函数(它计算 number * 2)。这个新函数被赋值给 double,所以 double 现在是一个函数,接受一个数字并将其乘以 2。调用 double(5) 时,double 是一个函数,它实际上调用的是 multiplier(2) 返回的内嵌函数

闭包的注意事项

  1. 内存管理
    闭包会导致内存泄漏,特别是当闭包中引用了大量对象时。虽然 JavaScript 的垃圾回收机制会回收不再使用的对象,但由于闭包的存在,引用链可能导致对象不能及时被销毁,因此要注意避免无用的闭包。

  2. 性能问题
    使用闭包时要小心性能问题,尤其是在频繁创建函数或在循环中使用闭包时,可能会导致性能下降。

闭包引起内存泄漏

闭包引起内存泄漏的主要原因是 闭包保持对外部作用域中变量的引用,从而导致这些变量不会被垃圾回收机制清除。即使外部函数的执行已经结束,这些变量仍然存在于内存中,可能会导致不必要的内存占用。

function outer() 
{
  let largeData = new Array(1000000).join('x'); // 模拟一个大的数据结构
  return function inner() 
  {
    console.log(largeData); // inner 函数引用了 outer 中的 largeData
  };
}

const closure = outer(); // 调用 outer,返回 inner 并赋值给 closure
closure(); // 执行 closure(实际上是调用了 inner 函数)

在这个例子中,largeData 是一个在 outer 函数中定义的变量,而 inner 函数引用了这个变量。当 outer 函数执行完毕时,如果没有闭包存在,largeData 会被垃圾回收。但由于 inner 函数还持有对 largeData 的引用,largeData 仍然存在于内存中,导致无法被回收,从而产生了内存泄漏。

解决闭包引起内存泄漏的方法

  1. 避免不必要的闭包:尽量避免在不需要的地方创建闭包,尤其是那些持有大量内存的对象。要确保闭包的作用范围尽可能小。

  2. 手动解除引用:当不再需要闭包时,可以手动将闭包中的引用解除,确保垃圾回收机制能够回收不再使用的对象。

function outer() {
  let largeData = new Array(1000000).join('x');
  let inner = function() {
    console.log(largeData);
  };
  return inner;
}

const closure = outer();
// 使用 closure 后,如果不再需要 largeData,可以手动解除引用
closure = null; // 手动解除引用,允许垃圾回收

总结

闭包是 JavaScript 中非常重要的概念,它使得函数能够记住并访问定义时的作用域,即使函数的外部环境已经执行完毕。闭包在数据封装、私有变量、回调函数、函数工厂等方面非常有用,是实现灵活、模块化代码的利器。

你可能感兴趣的:(javascript,开发语言,ecmascript)