学习笔记(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会被正常回收,不构成闭包。

相关推荐
半梅芒果干2 小时前
vue3 实现无缝循环滚动
前端·javascript·vue.js
冰敷逆向2 小时前
京东h5st纯算分析
java·前端·javascript·爬虫·安全·web
多多*2 小时前
2026年最新 测试开发工程师相关 Linux相关知识点
java·开发语言·javascript·算法·spring·java-ee·maven
会编程的土豆3 小时前
简易植物大战僵尸游戏 JavaScript版之html
javascript·游戏·html
雯0609~3 小时前
hiprint-官网vue完整版本+实现客户端配置+可实现直接打印(在html版本增加了条形码、二维码拖拽等)
前端·javascript·vue.js
VT.馒头3 小时前
【力扣】2705. 精简对象
javascript·数据结构·算法·leetcode·职场和发展·typescript
摘星编程3 小时前
在OpenHarmony上用React Native:Switch禁用状态
javascript·react native·react.js
huangyiyi666663 小时前
Vue + TS 项目文件结构
前端·javascript·vue.js
码农六六4 小时前
js函数柯里化
开发语言·前端·javascript