当前位置: 首页 > news >正文

JS闭包--函数式编程的核心概念

闭包

闭包(Closure)是 JavaScript 中最强大的特性之一,也是函数式编程的核心概念。


🔍 闭包是什么?

闭包 = 函数 + 创建时的环境

当一个函数可以记住并访问所在的词法作用域时,就产生了闭包。

🧩 闭包三要素

  1. 🏗️ 函数嵌套 - 一个函数内部定义另一个函数
  2. 🔗 变量引用 - 内部函数引用外部函数的变量
  3. 🚀 外部执行 - 内部函数在外部函数之外被调用

💡 基础示例

🎯 示例1:计数器闭包

function createCounter() {
  let count = 0;  // 🎒 被闭包"背走"的变量
  
  return function() {
    count++;      // 🏷️ 每次调用都记住这个count
    return count;
  };
}

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

🎯 示例2:立即执行函数(IIFE)

const uniqueId = (function() {
  let id = 0;  // 🔒 私有变量
  
  return function() {
    return id++; // 📈 每次调用自增
  };
})();

console.log(uniqueId()); // 0
console.log(uniqueId()); // 1

⚙️ 闭包的工作原理

闭包的核心原理是 JavaScript 的作用域链。当一个内部函数被返回并在外部作用域中调用时,它依然保持对它创建时的作用域的引用,因此可以访问该作用域中的变量。

这意味着闭包可以访问并“捕获”它外部函数中的变量,即使外部函数已经执行完毕。

  1. 作用域链 🌐
    • 函数在定义时会记住自己的词法作用域
    • 形成一条作用域链条
  2. 变量保持 🧳
    • 即使外部函数执行完毕
    • 被引用的变量依然存在
  3. 内存不释放 🚫🗑️
    • 被闭包引用的变量不会被垃圾回收

🛠️ 闭包的四大用途

🔒 封装私有变量

//创建银行账户
function createBankAccount() {
  let balance = 0;  // 🏦 私有变量(只能在闭包内访问)
  
  return {
    deposit(amount) { balance += amount },  // 存款方法
    withdraw(amount) { balance -= amount },  // 取款方法
    getBalance() { return balance }         // 查询余额方法
  };
}

const account = createBankAccount();
account.deposit(100);                       // 存入100元
console.log(account.getBalance());          // 显示余额 100

🧩 模块模式

通过闭包,可以模拟模块的概念,将变量和函数封装在一个函数作用域内,不污染全局作用域。

const game = (function() {
  let score = 0;  // 🎮 游戏分数(私有变量)
  
  function addPoints(points) {
    score += points;  // 内部加分函数
  }
  
  return {
    play() {          // 游戏玩法
      addPoints(10);  // 每次玩加10分
      console.log(`得分: ${score}`);
    },
    reset() {         // 重置游戏
      score = 0;      // 分数归零
    }
  };
})();

game.play();  // 输出: 得分: 10
game.play();  // 输出: 得分: 20(再次调用会继续累加)
game.reset(); // 重置分数为0
game.play();  // 输出: 得分: 10(重置后重新开始)

💡关键概念解析:

  1. IIFE (立即调用函数表达式) - (function(){...})() 创建了一个独立作用域
  2. 闭包 - 内部函数(addPoints)可以访问外部函数的变量(score)
  3. 模块模式 - 只暴露playreset方法,保持score的私有性

✂️ 函数柯里化

function createMultiplier(multiplier) {
  return function(number) {
    return number * multiplier;  // 🔢 闭包记住了乘数参数
  };
}

const double = createMultiplier(2);  // 创建一个乘以2的函数
console.log(double(5));  // 输出: 10 (5×2)

const triple = createMultiplier(3);  // 创建一个乘以3的函数 
console.log(triple(5));  // 输出: 15 (5×3)

💡关键概念解析:

  1. 高阶函数createMultiplier 是一个返回函数的函数(高阶函数)
  2. 闭包特性:返回的函数记住了 multiplier 参数(即使外部函数已执行完毕)
  3. 应用场景:工厂模式创建特定功能的函数

⏱️延迟执行

function delayedAlert(msg, time) {
    setTimeout(() => {
      // 🕒 闭包记住了msg参数,即使外部函数已执行完毕
      console.log(msg); 
    }, time);
    console.log("外部函数执行完毕")
  }
  
  delayedAlert("延迟执行参数", 1000);

🖱️ 事件处理

function setupButtons() {
  const colors = ['red', 'green', 'blue'];  // 可用颜色数组
  
  // 为每个颜色创建按钮事件监听
  colors.forEach((color, index) => {
    // 获取对应ID的按钮元素
    document.getElementById(`btn-${index}`)
      // 添加点击事件监听器
      .addEventListener('click', () => {
        // 🎨 通过闭包记住当前迭代的color值
        console.log(`选择了 ${color}`);  
      });
  });
}

/* 
实际HTML结构示例:
<button id="btn-0">红色</button>
<button id="btn-1">绿色</button>
<button id="btn-2">蓝色</button>
*/

⚠️闭包注意事项

🧠 内存泄漏风险

闭包会捕获并“记住”外部函数中的局部变量,即使外部函数已经执行完毕。这是因为闭包仍然持有对这些变量的引用,这样变量不会被垃圾回收机制回收,从而继续存在于内存中。

function createHeavyObject() {
  const bigData = new Array(1000000).fill('*');  // 🏋️ 大数据
  
  return function() {
    console.log('可能泄漏内存');
    // bigData 一直被引用,无法释放
  };
}

✅ 正确做法

JavaScript 的垃圾回收机制会回收那些不再被引用的对象

function safeClosure() {
  const data = getLargeData();
  
  function process() {
    // 使用data...
  }
  
  // 使用完后清除引用
  data = null;
  
  return process;
}

🎯 经典面试题解析

🔄 循环中的闭包问题

for (var i = 0; i < 3; i++) {
  setTimeout(() => {
    console.log(i);  // 🤔 输出什么?
  }, 1000);
}
// 输出: 3, 3, 3

💡 解决方案

// 方案1: 使用let
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 1000); // 0,1,2
}

// 方案2: IIFE 立即执行函数--》闭包
for (var i = 0; i < 3; i++) {
  (function(j) {
    setTimeout(() => console.log(j), 1000); // 0,1,2
  })(i);
}

🏆 闭包最佳实践

  1. 🎯 只在需要时使用 - 不要滥用闭包
  2. 🧹 及时清理引用 - 避免内存泄漏
  3. 🚫 避免循环滥用 - 不要在循环中创建不必要闭包
  4. 📦 模块化组织 - 用闭包实现模块化
延迟执行

setTimeoutsetInterval闭包在延迟执行中发挥了重要作用,因为即使外部函数已经执行完毕,闭包仍然可以保持对外部变量的引用

相关文章:

  • Springboot 集成 Flowable 6.8.0
  • docker远程debug
  • SpringBoot项目图片上传成功,访问404
  • WordPress自动代码高亮插件Code Prettify插件
  • function、var、let 和 const 用于不同的声明场景
  • 【Linux进程】理解进程地址空间
  • 8.非监督学习与关系挖掘:聚类分析、客户细分、关联规则与协同过滤的全面解析——Python数据挖掘代码实践
  • 通过php连接redis数据库
  • 网络安全之STP(1)
  • Kotlin泛型: 协变|逆变|不变
  • mysql慢查询日志
  • Python - 爬虫-网页抓取数据-工具wget
  • ngx_http_core_root
  • Vue 中 v-if 和 v-show 的区别
  • 如何设计有效的用户反馈闭环机制
  • 微信小程序面试内容整理-如何优化小程序的启动速度?
  • 人工智能通识速览
  • 基于Spring Boot的消防物资存储系统的设计与实现(LW+源码+讲解)
  • deepseek实战教程-第五篇支持deepseek的大模型应用安装及使用
  • 基于 SGLang 部署 Qwen2.5 7B 模型
  • 网站备案掉了/百度旗下的所有产品
  • 合伙合同网站建设协议/深圳精准网络营销推广
  • houzz室内设计/上海网络seo优化公司
  • 邯郸移动网站建设公司/seo研究
  • 网站升级维护需要多久/天津seo结算
  • 设计做笔记的网站/广告优化师适合女生吗