闭包【JavaScript基础面试题】

闭包的定义

如果一个函数能访问外部的变量,那么就形成了一个闭包。

闭包形成的原理

当一个普通函数执行结束之后,函数内的变量会被全部销毁(垃圾回收)。但是,如果某些变量在函数外部会被用到,那么该变量就不会被销毁,因此形成了闭包。可以看下面的例子:

function fn1() {
  let a = 1
  // 产生闭包
  return () => {
    console.log(a);
  }
}
const fn2 = fn1()
fn2()

理论上,函数 fn1 执行完毕之后,变量 a 会被销毁。但是由于函数 fn2 的存在,在今后调用 fn2 的时候,会使用到变量 a。 因此, 变量 a 被保存下来了(没有被销毁)。这就形成了一个闭包。

假设 fn1 返回的箭头函数内,没有使用到变量 a 。那么在 fn1 执行结束后,a 变量就会被销毁,也就不存在闭包了

闭包的应用场景

私有变量

下面示例中,count 本应该在函数结束时销毁。但是,由于increment() 使用了 count 变量,导致后续每次调用increment() 时会被使用到该变量,因此 count 不会被销毁。

在本例中,count 是一个私有变量。 外部无法直接修改它的值, 只能通过 counterModule 暴露出来的函数 increment() 来修改它的值。

const counterModule = (function () {
  let count = 0;

  function increment() {
    count++;
    console.log(count);
  }

  return {
    increment: increment,
  };
})();

counterModule.increment(); // 输出 1
counterModule.increment(); // 输出 2

函数的柯里化

    function curry(fn) {
      return function curried(...args) {
        if (args.length >= fn.length) {
          console.log(args);
          return fn(...args);
        } else {
          return function (...args2) {
            return curried(...args.concat(args2));
          };
        }
      };
    }

curried 函数形成了一个闭包,因为它可以访问外部函数 curry 中的 fn 参数。当 curried 函数在 curry 函数作用域之外执行时,它仍然可以访问 fn这是闭包的典型特征

防抖和截流

myTimerdebounce 函数执行结束后,仍未被销毁,这是典型的闭包特征。

    function debounce(fn) {
      let myTimer = null;
      return function () {
        if (myTimer) {
          clearTimeout(myTimer);
          myTimer = setTimeout(() => {
            myTimer = null;
            console.log("3s已到,可以再次点击");
          }, 3000);
        } else {
          fn();
          myTimer = setTimeout(() => {
            myTimer = null;
          }, 3000);
        }
      };
    }

循环时保存i

for (var i = 0; i < 5; i++) {
  (function (i) {
    setTimeout(() => {
      console.log(i + 1);
    }, 100);
  })(i);
}

闭包的缺陷(问题)

事件监听

largeArray 是个超长数组,由于在回调函数(click) 中使用到该数组,因此该数组不会被销毁。除非取消监听。

function addEventListeners(elements) {
  const largeArray = new Array(1000000).fill('large data');

  elements.forEach((element) => {
    element.addEventListener('click', () => {
      console.log('Element clicked:', element);
      console.log(largeArray[0]);
    });
  });
}

const elements = document.querySelectorAll('.my-element');
addEventListeners(elements);

全局变量

在下面例子中,foo 是一个全局变量,在 fn() 执行结束之后,仍然不会被销毁。因此占用大量内存。

function createBigData() {
    const bigData = [];
    for(let j = 0; j < 100; j++) {
        bigData.push(new Array(10000).join("this_is_a_big_data"));
    }
    return bigData;
}

function fn() {
    foo = createBigData(); // 意外的全局变量导致内存泄漏
}
for (let j = 0; j < 100; j++) {
    fn();
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

codereasy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值