【JS高频八股】什么是闭包?

**八股问法:**什么是闭包?有什么作用和缺点?在哪里会用到闭包

什么是闭包?

闭包的本质是函数加上其所处词法环境的组合。

闭包内层函数 + 引用的外层函数变量。也就是一个内部函数可以访问其外部函数作用域的变量,其实外部函数已经执行完毕并销毁,内部函数依然能持有对外部作用域变量的引用。

闭包 是由捆绑起来(封闭的)的函数和函数周围状态(词法环境 )的引用组合而成。换言之,闭包让函数能访问它的外部作用域。在 JavaScript 中,闭包会随着函数的创建而同时创建。

闭包的变量存在堆中,即解释了函数调用之后为什么闭包还能引用到函数内的变量。

通俗理解闭包:一个房间(外层函数)中有一个小孩(内层函数)还有零花钱(外层函数变量),小孩可以花零花钱(内层函数可以访问外层函数变量),也就是内层函数+可访问的外层变量(小孩+可以花的零花钱)就是闭包。

javascript 复制代码
// 外层函数:独立房间
function outer() {
  let money = 100; // 房间私有变量:零花钱

  // 内层函数:小孩
  function inner() {
    console.log(money); // 小孩能用房间的钱
  }

  return inner; // 把小孩送出房间
}

// child 就是被送出去的内层函数
const child = outer();

// 跑到外面执行,依然能拿到 money
child(); // 输出:100

闭包的核心作用

1)封装私有变量、方法

解决全局变量污染问题,实现变量私有化,只有暴露出来的方法才能访问内部变量

复制代码
// 实战:封装计数器,避免count暴露在全局
function createCounter() {
  let count = 0; // 私有变量,外部无法直接访问
  return {
    increment: () => count++, // 暴露方法操作私有变量
    getCount: () => count
  }
}

const counter = createCounter();
counter.increment();
console.log(counter.getCount()); // 1
console.log(window.count); // undefined(变量私有化)

2)延长 局部变量的生命周期

函数执行完之后,局部变量不被销毁,可以后续复用

场景:防抖、节流(保存定时器),ajax请求的回调中请求访问参数

3)实现柯里化:可以把多参数函数拆解为单参数函数,提高函数的复用性

三个特性

  • ①函数嵌套函数
  • ②函数内部可以引用函数外部的参数和变量
  • ③参数和变量不会被垃圾回收机制回收

闭包的缺点及解决方案

闭包会长期持有对外部变量的引用,导致这些变量无法被垃圾回收,如果大量使用闭包或者闭包未及时释放,会占用过多的内存,甚至导致页面卡顿

**解决方法:**手动解除引用、避免闭包持有不必要的大对象、合理控制闭包的适用范围,避免全局范围长期持有闭包

闭包的形成需要的条件:

  • 存在函数嵌套:外部函数+内部函数
  • 内部函数引用了外部函数作用域的变量、方法
  • 外部函数执行后,其返回值(内部函数)被外部变量所持有(或者是内部函数以其他形式被外部访问)

内存泄漏

闭包不一定都有return和内存泄漏

有return:如果外部函数想要使用闭包中的函数

每一个函数都会拷贝上级作用域,形成一个作用域链条

哪里会用到闭包?

第一个最常用的就是防抖和节流。比如搜索框输入、窗口 resize、滚动监听,我们会把定时器 ID 存在外层函数里,返回的真正执行函数通过闭包一直持有这个 timer 变量,这样每次触发才能清楚上一次的定时器,实现防抖效果。如果没有闭包,timer 每次都会被回收,就做不到连续控制。

javascript 复制代码
function debounce(fn, delay) {
  let timer = null; // 闭包变量
  return function (...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}

function throttle(fn, delay) {
  let timer = null;
  return function(...args) {
    if (timer) return;
    timer = setTimeout(() => {
      fn.apply(this, args);
      timer = null;
    }, delay);
  };
}

第二个是实现私有变量或模块化。比如封装一个计数器,把 count 放在外部函数作用域里,只暴露 increment、getCount 这些方法,外部没办法直接修改 count,只能通过提供的方法操作,这就是用闭包做数据私有化,避免全局变量污染,也是早期模块化常用的方式。

javascript 复制代码
function createCounter() {
  let count = 0; // 私有变量,外部无法访问
  return {
    increment: () => count++,
    getCount: () => count
  };
}

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

第三个是异步场景保存现场,比如循环里绑定事件、定时器。像 for 循环注册多个点击事件,如果不用 let 或闭包,所有回调都会共享同一个变量,导致结果不符合预期。通过闭包把每次循环的 i 保存下来,每个回调就能拿到正确的值,这也是非常高频的用法。

javascript 复制代码
for(var i = 0; i < 5; i++) {
  (function (i) {
    setTimeout(() => console.log(i), 1000);
  })(i);
}

第四个是函数柯里化和参数复用。比如封装一个通用请求函数,先传入 baseURL 固定下来,返回的新函数再接收 url 和 params,后面调用时就不用重复传域名,这里也是靠闭包记住外层的 baseURL,实现参数预置。

javascript 复制代码
function requestWithBaseUrl(baseURL) {
  // 闭包记住 baseURL
  return function (url, params) {
    return fetch(baseURL + url, { params });
  };
}

const api = requestWithBaseUrl('https://api.xxx.com');
api('/user'); // 自动带上 baseURL

// 基础柯里化:拆分参数、分步接收
function add(a) {
  return function (b) {
    return function (c) {
      return a + b + c;
    }
  }
}

// 调用
add(1)(2)(3); // 6

还有像自定义 hooks 里保存状态事件监听里缓存 DOM 或配置 ,本质上也都是闭包在起作用。简单说,只要是需要长期保存一些变量,又不想暴露到全局的场景,基本都在用闭包。

相关推荐
微学AI1 小时前
Claude-Code-python 前端改造项目工作流程详解
开发语言·前端·python
宵时待雨1 小时前
linux笔记归纳3:linux开发工具
linux·运维·笔记
乐世东方客1 小时前
Nacos-2.1.0问题-自己记录
开发语言·python
D_C_tyu1 小时前
JavaScript | 数独游戏核心算法实现
javascript·算法·游戏
海天鹰1 小时前
EXIF-JS
javascript
清汤饺子2 小时前
【译】我的 AI 进阶之路:从怀疑到深度整合
前端·javascript·后端
@菜菜_达2 小时前
Vue生命周期
前端·javascript·vue.js
每天吃饭的羊2 小时前
UMD和IIfe
开发语言·前端·javascript
gCode Teacher 格码致知2 小时前
Javascript提高:自定义的占位符替换-由Deepseek产生
开发语言·javascript·ecmascript