JavaScript闭包

闭包

闭包(closure)是一个函数以及其捆绑的周边环境状态(lexical environment,词法环境的引用的组合。换而言之,闭包让开发者可以从内部函数访问外部函数的作用域。在 JavaScript 中,闭包会随着函数的创建而被同时创建。[MDN]

示例:

function outerFunction() {
  let outerVariable = 'I am from outer function';
  
  // 闭包: innerFunction
  function innerFunction() {
    console.log(outerVariable);
  }

  return innerFunction;
}

let closureFunction = outerFunction();
closureFunction(); 

在上述示例中,innerFunction 就是一个闭包。当 outerFunction 执行完毕后,innerFunction 仍然能够访问 outerFunction 中的 outerVariable 变量,这是因为闭包保留了创建它时的词法环境。

闭包由函数以及创建该函数的词法环境组合而成。即使创建闭包的外部函数已经执行完毕,闭包仍能访问外部函数中的变量(依然可以保留对这些外部变量的引用)。

我们来看一个例子:
下面的代码创建了一个 shooters 数组,需求是每个shooter输出自己的编号。

function makeArmy() {
  let shooters = [];

  let i = 0;
  while (i < 10) {
    let shooter = function() { // 创建一个 shooter 函数,
      console.log( "输出编号:", i ); // 应该显示其编号
    };
    shooters.push(shooter); // 将此 shooter 函数添加到数组中
    i++;
  }

  // ……返回 shooters 数组
  return shooters;
}

let army = makeArmy();

// ……所有的 shooter 显示的都是 10,而不是它们的编号 0, 1, 2, 3...
army[0](); // 编号为 0 的 shooter 显示的是 10
army[1](); // 编号为 1 的 shooter 显示的是 10
army[2](); // 10,其他的也是这样。

每个函数输出的编号都是10,为什么?
在上述代码中,所有的 shooter 函数显示的都是 10,原因是 JavaScript 的闭包特性和变量提升。
while 循环中创建的 shooter 函数都引用了同一个外部变量 i。当while循环结束时,i 的值已经变成了 10,此时 shooter 函数还保留着对外部变量i的引用。当调用 shooter 函数时,它们获取的都是此时 i 的最终值 10

正确实现:

function makeArmy() {
  let shooters = [];

  let i = 0;
  while (i < 10) {
    let shooter = (function(j) { // 使用立即执行函数表达式创建一个新的作用域
      return function() {
        console.log( "输出编号:", i ); // 应该显示其编号
      };
    })(i);
    shooters.push(shooter); // 将此 shooter 函数添加到数组中
    i++;
  }

  // ……返回 shooters 数组
  return shooters;
}

let army = makeArmy();

army[0](); // 0
army[1](); // 1
army[2](); // 2

通过立即执行函数表达式 (function(j) {...})(i) ,将当前的 i 值作为参数传递给这个函数,并在其内部创建了一个新的变量 j 来保存这个值。这样,每个 shooter 函数都有了自己独立的 j 值,从而能够正确显示各自的编号。
例如,如果 i 的值是 1,那么传递给立即执行函数表达式的就是 1,内部的 j 就被赋值为 1,对应的 shooter 函数就会显示 1

闭包用途和特点:

  1. 实现私有变量:通过闭包,可以创建只能在特定函数内部访问和修改的变量,模拟私有变量的效果。
    例如:
function createPerson() {
  let name = '张三';

  return {
    getName: function() {
      return name;
    },
    setName: function(newName) {
      name = newName;
    }
  };
}

let person = createPerson();
console.log(person.getName());  // 输出 张三
person.setName('李四');
console.log(person.getName());  // 输出 李四
  1. 数据封装和隐藏:可以将相关的操作和数据封装在一个闭包中,避免外部直接访问和修改关键数据。
  2. 函数柯里化:通过闭包逐步构建函数的参数,实现更灵活的函数调用方式。
function curry(func) {
  return function curried(...args) {
    if (args.length >= func.length) {
      return func(...args);
    } else {
      return function(...moreArgs) {
        return curried(...args,...moreArgs);
      };
    }
  };
}

function add(a, b, c) {
  return a + b + c;
}

let curriedAdd = curry(add);
let add5 = curriedAdd(5);
let add5And6 = add5(6);
console.log(add5And6(7));  // 输出 18

由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,滥用可能会导致内存泄漏、网页性能差 等问题。
在退出函数之前,将不使用的局部变量全部删除。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值