JS中的闭包

118 阅读3分钟

JS中的闭包

by Awayer 2023 / 6 / 9

一、什么是闭包

官方的解释:

  • 闭包是一种特殊的对象。它由两部分构成:函数,以及创建该函数的环境。环境由闭包创建时在作用域中的任何局部变量组成。
  • 闭包是由一个函数以及创建该函数的词法环境组合而成,这个环境包含了这个闭包创建时能访问的所有局部变量。

我的解释:

  • 闭包就像是一个背包,它在每个函数创建时都会拥有自己的背包,这个背包类似于上下文,会储存函数创建时作用域中的变量,并且保护这些私有变量。

  • 闭包是一个函数,并且存在于另一个函数当中

    闭包可以访问到父级函数的变量,且该变量不会销毁

二、闭包原理

一般在一个内层函数中调用外层函数变量会产生闭包,下面我们来看几个例子:

const obj = {
    name:"Awayer",
    sayName:function() {
        const name = this.name;
        return function () {
            console.log(name);
        }
    }
}
obj.sayName()();

上述例子中,sayName方法就是一个外层函数,它返回的内层函数中用到了sayName函数的变量(const name = this.name),所以内层函数就相当于一个闭包

再来看一个例子:

var fn;
function foo() {
  var a = 2;
  function baz() {
    console.log(a);
  }
  fn = baz;
}
function bar() {
  if (typeof fn == 'function') {
    fn()
  } else {
    return false;
  }
}
foo();   
bar();  // 2

现在,我们来分析代码的执行过程:

  1. 执行foo()函数时,声明并初始化了变量a,并定义了内部函数baz
  2. 然后,将baz函数赋值给全局变量fn,此时fn指向baz函数。
  3. 执行到bar()函数时,判断fn的类型为函数,因此进入if语句块。
  4. if语句块中,调用fn(),即调用了baz()函数。
  5. baz()函数在其作用域链中找到了变量a,并打印出其值2。

因为baz函数形成了闭包,它可以访问外部函数foo的作用域,包括其中的变量a。即使foo函数已经执行完毕,baz函数仍然可以访问和操作foo函数中的变量。这是因为baz函数的作用域链仍然保持对foo函数作用域的引用。

需要注意的是,在调用foo()之前调用bar()fn还没有被赋值为baz函数,因此bar()函数中的else语句块会被执行,返回false

这个例子展示了闭包的一个常见用途:延长变量的生命周期,使其在函数执行完毕后仍然可访问。

三、闭包的应用

  1. 封装私有变量和数据:通过闭包,可以创建私有变量和数据,只能通过内部函数进行访问。这种方式可以避免全局命名冲突,并提供了一种封装数据和实现信息隐藏的方式。
function createCounter() {
  let count = 0;

  return {
    increment: function() {
      count++;
    },
    decrement: function() {
      count--;
    },
    getCount: function() {
      return count;
    }
  };
}

const counter = createCounter();
counter.increment();
counter.increment();
console.log(counter.getCount()); // 2

  1. 保存函数状态:闭包可以在函数调用之间保持函数的状态。这对于实现计数器、缓存和记忆化函数等功能非常有用。
function createCache() {
  const cache = {};

  return {
    get: function(key) {
      return cache[key];
    },
    set: function(key, value) {
      cache[key] = value;
    },
    clear: function() {
      for (let key in cache) {
        delete cache[key];
      }
    }
  };
}

const cache = createCache();
cache.set('foo', 'bar');
console.log(cache.get('foo')); // 输出:'bar'

  1. 实现模块化和命名空间:闭包可以创建独立的模块和命名空间,避免全局命名冲突,并将相关的函数和数据组织在一起。
var myModule = (function() {
  var privateData = 'I am private';

  function privateFunction() {
    console.log(privateData);
  }

  return {
    publicFunction: function() {
      privateFunction();
    }
  };
})();

myModule.publicFunction(); // 输出:'I am private'

上述例子中的闭包都有一个共同点:它们能够捕获并保持对其创建时的作用域的引用。这使得函数可以在其定义的词法环境之外访问和操作变量。