学习笔记(7-01)函数闭包

目录

1、定义

  • 闭包: 带着外部变量的内部函数。 即使外部函数执行完退出了,这个内部函数依然能访问和操作,它创建时所在的外部函数里的变量,不会随着外部函数的结束而丢失这些变量的访问权。
  • 严谨的表述:当函数被创建并返回(或被保存)时,它会携带自己的词法环境(也就是创建它时所处的外部作用域),这个函数 + 它携带的词法环境,就构成了闭包。

2、示例1

1、外部函数就像一间教室,里面的变量是教室里的桌椅;内部函数就像一个学生,闭包就是这个学生离开教室时,把桌椅的 "使用权凭证" 带在了身上,哪怕教室已经锁门(外部函数执行完毕),学生依然能使用这些桌椅(访问外部变量)。

2、内部函数(学生) + 外部变量的访问引用(桌椅的使用权凭证) = 闭包

3、闭包并没有复制外部变量,只是持有了外部变量的访问引用。

js 复制代码
// 外部函数
function outerFunc() {
  // 外部函数作用域中的变量(仅在 outerFunc 内部可访问,正常情况下外部无法触及)
  let outerVar = "我是外部函数的变量";

  // 内部函数(闭包的核心:这个内部函数就是闭包载体)
  function innerFunc() {
    // 内部函数访问了外部函数的变量 outerVar
    console.log(outerVar);
  }

  // 外部函数返回内部函数(让内部函数可以在外部被调用)
  return innerFunc;
}

// 1. 调用外部函数,得到内部函数(此时 outerFunc 已经执行完毕,按常理其内部变量应被销毁)
const myClosure = outerFunc();

// 2. 调用内部函数(依然能访问到 outerFunc 中的 outerVar,这就是闭包的效果)
myClosure(); // 输出:我是外部函数的变量

3、分析

  1. outerFunc 执行完毕后,按照 JS 的垃圾回收机制,它的内部变量 outerVar 应该被回收销毁。
  2. 但因为 innerFunc 访问了 outerVar,且 innerFunc 被返回并赋值给了 myClosure(保留了引用),所以 outerVar 不会被销毁,会被 innerFunc 携带保存。
  3. 当调用 myClosure() 时,innerFunc 依然能从自己携带的「词法环境」中找到 outerVar,这就是闭包在起作用。

4、特性

  1. 访问外部变量:内部函数可以访问外部函数的变量,甚至是外部函数的外部函数的变量(词法作用域链)。
  2. 变量持久化:外部函数执行完毕后,其内部变量不会被垃圾回收,而是被闭包持有,直到闭包本身被销毁(比如 myClosure = null)。
  3. 隔离作用域:闭包可以创建私有变量,避免变量污染全局作用域,这是闭包最常用的实际场景。

4.1、示例2:闭包实现私有变量(实用场景)

js 复制代码
// 实现一个简单的计数器,变量 count 仅能通过 increment 和 getCount 操作,无法直接修改
function createCounter() {
  // 私有变量:仅在 createCounter 内部可访问,全局无法直接触及
  let count = 0;

  // 返回一个对象,包含两个方法(这两个方法都是闭包,持有 count 的引用)
  return {
    // 自增方法
    increment: function() {
      count++;
    },
    // 获取当前值方法
    getCount: function() {
      return count;
    }
  };
}

// 创建计数器实例1
const counter1 = createCounter();
counter1.increment();
counter1.increment();
console.log(counter1.getCount()); // 输出:2

// 创建计数器实例2(与 counter1 互不干扰,各自持有独立的 count 变量)
const counter2 = createCounter();
console.log(counter2.getCount()); // 输出:0

示例中,count 就是私有变量,全局无法直接修改,只能通过 increment() 和 getCount() 操作,避免了变量污染,这也是闭包在实际开发中的核心用途之一。

5、注意事项

  1. 内存泄漏风险:如果闭包被长期保存(比如赋值给全局变量),且持有大量无用的外部变量,会导致这些变量无法被垃圾回收,从而造成内存泄漏。

解决方案:不需要使用闭包时,手动解除引用(比如 myClosure = null)。

  1. 变量共享问题:在循环中创建闭包时,容易出现变量共享导致结果不符合预期,需要注意。

5.1、循环中的闭包坑

javascript 复制代码
// 错误示例:循环中创建闭包,期望输出 0、1、2,实际输出 3、3、3
for (var i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i); // 输出:3、3、3
  }, 1000);
}

原因分析:

  • var 声明的变量没有块级作用域,i 是全局变量,循环结束后 i 的值为 3。
  • 定时器中的函数是闭包,它访问的是全局的 i,当定时器执行时,循环已经结束,i 已经是 3,所以输出 3 次 3。

解决方案:

  1. 使用 let 声明变量(let 有块级作用域,每次循环都会创建一个独立的 i)。
javascript 复制代码
for (let i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i); // 输出:0、1、2
  }, 1000);
}
  1. 使用立即执行函数(IIFE)创建独立作用域
javascript 复制代码
for (var i = 0; i < 3; i++) {
  (function(j) {
    setTimeout(function() {
      console.log(j); // 输出:0、1、2
    }, 1000);
  })(i);
}

6、总结

闭包的成立必须满足两个核心条件(缺一不可):

  1. 存在嵌套函数(内部函数嵌套在外部函数中)。
  2. 内部函数被「带出」外部函数的作用域(比如被返回、被赋值给外部变量、被传递给其他函数作为参数),且内部函数访问了外部函数的变量。
  • 示例:不满足条件的 "伪闭包"(不算真正的闭包)
javascript 复制代码
function outerFunc() {
  let outerVar = "外部变量";
  
  // 内部函数仅在外部函数内部调用,没有被带出外部作用域
  function innerFunc() {
    console.log(outerVar);
  }
  
  // 直接在外部函数内部执行内部函数,不是闭包
  innerFunc();
}

outerFunc(); // 输出:外部变量(这只是普通的嵌套函数,不是闭包)

示例中,虽然有内部函数访问外部变量,但内部函数没有被带出外部函数的作用域,外部函数执行完毕后,outerVar会被正常回收,不构成闭包。

相关推荐
智码看视界37 分钟前
老梁聊全栈系列:Vue3核心与组合式API深度解析
javascript·vue.js·ecmascript
想吃火锅10058 小时前
【leetcode】405.数字转换为十六进制数js
开发语言·javascript·ecmascript
阿猫的故乡11 小时前
Vue过渡动画从入门到装X:淡入淡出、滑动、列表动画、第三方库全搞定
前端·javascript·vue.js
小和尚敲木头11 小时前
vue3 vite动态拼接图片路径
javascript
我叫黑大帅12 小时前
前端如何竖屏固定视口背景
前端·javascript·面试
不会敲代码112 小时前
我花了三天时间,终于把 Cookie、XSS、CSRF 和浏览器存储给整明白了
javascript·面试
贩卖黄昏的熊12 小时前
flex 布局快速梳理
开发语言·javascript·css3·html5
swipe12 小时前
Mem0 x Agent 实战系列:分层记忆 + 三路召回,搭建真正可用的长期记忆层
前端·javascript·面试
kyriewen13 小时前
手写 call、apply、bind:从原理到实现,附 3 个最容易忽略的边界情况
前端·javascript·面试
胡萝卜术13 小时前
从内存视角重新认识 JavaScript 数据类型:一份深度学习笔记
前端·javascript·面试