🌟 从龙珠召唤到柯里化:JS 新手的闭包魔法书🐉

110 阅读5分钟

🧙‍♂️ 引言:今天召唤的不是神龙,是代码大神!

"你是不是觉得柯里化像《龙珠》里的黑悟空——神秘又高冷?🤯
其实它就像在玩集邮游戏!每拿到一个参数(邮票),就离召唤神龙(执行函数)更近一步!🐉
今天我们就用外卖订单、快递分拣员和俄罗斯套娃,把闭包和柯里化讲得比奶茶还好喝!🥤"

image.png

🧠 闭包:外卖订单的"记忆回放"功能

image.png

// 想象你在点外卖:选好套餐后,订单会记住你的名字!
function createOrder(name) {
  return function(food) { // 🍕 这是外卖小哥的记忆卡
    console.log(`${name} 订了 ${food}`); 
  }
}

const zhangsan = createOrder("张三"); // 🧾 订单已生成
zhangsan("麻辣香锅"); // 🧾 回忆订单并执行

⚠️ 小白注意点:闭包就像外卖订单,即使你离开柜台(函数执行完毕),订单信息(作用域)依然存在!
💡 闭包本质 = 函数 + 它能访问的自由变量(比如 name
🎯 food 是内部函数的参数,但 name 是外部函数的变量,这就是闭包的"跨代访问"魔法!

🛠 实际应用案例:租借工具的闭包

// 场景:租借工具公司,记录租借次数
function createToolRental(toolName) {
  let rentals = 0;
  return function() {
    rentals++;
    console.log(`${toolName} 已被租借 ${rentals} 次`);
  };
}

const drillRental = createToolRental("电钻");
drillRental(); // 电钻 已被租借 1 次
drillRental(); // 电钻 已被租借 2 次

🧠 闭包在这里记录了租借次数(rentals 变量),即使函数被多次调用,状态依然保留!
🔐 闭包保护了 rentals 的私有性,外部无法直接修改计数器!


🔄 柯里化:快递分拣员的递归魔法📦

image.png

// 柯里化就像快递分拣:参数是快递,fn 是最终收件人
function curry(fn) {
  let collectedArgs = [];
  return function collector(...args) { // 📦 收集快递
    collectedArgs = [...collectedArgs, ...args]; // 🧾 分拣快递
    if (collectedArgs.length === fn.length) { // 🎉 龙珠集齐!
      return fn(...collectedArgs); // 🐉 召唤神龙!
    }
    return collector; // 🔁 继续收集
  }
}

🧠 递归逻辑图解
collector 函数就像俄罗斯套娃(🧩)——
每次调用都会返回一个新的"分拣员",直到参数集齐!
// ⚠️ 这里容易漏掉递归!(递归返回自己才是魔法核心)

🛠 实际应用案例:React 事件处理优化

// 场景:React 中的事件处理柯里化
function handleEvent(type, element) {
  return function(event) {
    console.log(`在 ${element} 上触发了 ${type} 事件`);
  };
}

const clickHandler = handleEvent("点击", "按钮");
clickHandler(); // 在 按钮 上触发了 点击 事件

🧠 通过柯里化,将事件类型和元素固定,后续只需传递事件对象!
🚀 优势:避免重复传递相同参数,代码更简洁!


🧪 代码实验室:手写 curry 的三种姿势

1.js:柯里化基础版(龙珠收集器)🐉

function add(a, b, c) { 
  return a + b + c;
}

function curry(fn) {
  let judge = (...args) => {
    if (args.length == fn.length) { // 🎯 小白注意:fn.length 是函数定义时的参数数量
      return fn(...args)
    }
    return (...newArgs) => judge(...args, ...newArgs) // 🔁 递归套娃
  }
  return judge
}

console.log(addCurry(1)(2)(3)); // 6 → 1+2+3=6(龙珠集齐!)

💡 柯里化本质 = 参数收集 + 递归判断 + 最终执行
🎯 addCurry 是 curry 返回的新函数,每次调用只收一个参数


2.js:类数组转真数组的魔法杖

function add(a, b, c) {
  const args = Array.from(arguments); // 🧙‍♀️ 类数组变真数组的咒语
  console.log(args.map(item => item + 1)); // ✅ 现在可以愉快地用数组方法啦!
  let result = 0;
  for (let i = 0; i < arguments.length; i++) {
    result += arguments[i]; // 1+2+3=6
  }
  return result;
}

console.log(add.length); // 3(函数期望的参数数量)
console.log(add(1, 2, 3)); // 6

⚠️ 小白警告arguments 是伪数组(没有 map 方法),要记得用 Array.from() 转换!
🎯 arguments 的长度由调用时实际传参决定,而 fn.length 是函数定义时的参数数


3.js:剩余参数的终极奥义(Spread Operator)✨

function add(...args) { 
  console.log(args); // [1,2,3](第一次调用)
  return (...newArgs) => {
    const arr = [...args, ...newArgs] // 🎯 小白注意:展开运算符合并数组
    console.log(arr); // [1,2,3,4,5,6](第二次调用)
  }
}

add(1,2,3)(4,5,6); // 先调用 add,再调用返回的函数

🧠 剩余参数 ...args 是 ES6 的终极武器,能一次性捕获所有参数!
🎯 与柯里化的递归收集不同,这是直接返回新函数的"两段式"玩法


🌈 总结:代码界的龙珠传说

image.png

闭包是外卖订单的记忆回放器,柯里化是快递分拣员的递归魔法!
每个参数都是龙珠,递归是套娃,最终执行是召唤神龙!🐉✨
记住:

  • 闭包 = 函数 + 自由变量(外卖订单)
  • 柯里化 = 参数收集 + 递归判断(龙珠收集)
  • 剩余参数 ...args 是现代 JS 的瑞士军刀!

🧩 彩蛋互动:你的柯里化挑战!🚀

题目:用 curry 写一个乘法计算器!
要求:

  1. multiply(2)(3)(4) 应返回 24
  2. 提示:模仿 add 函数的柯里化逻辑

💡 答案彩蛋:

function multiply(a, b, c) {
  return a * b * c;
}
const multiplyCurry = curry(multiply);
console.log(multiplyCurry(2)(3)(4)); // 24

🧠 柯里化的实际应用案例合集

1. 参数复用:正则校验器

// 场景:校验数字和字母
function curryingCheck(reg) {
  return function(txt) {
    return reg.test(txt);
  };
}

const hasNumber = curryingCheck(/\d+/g);
const hasLetter = curryingCheck(/[a-z]+/g);

console.log(hasNumber('test1')); // true
console.log(hasLetter('21212')); // false

🧠 通过柯里化固定正则表达式,避免重复传递参数!


2. 延迟执行:API 请求封装

// 场景:封装 API 请求函数
function fetchData(url) {
  return function(params) {
    console.log(`Fetching data from ${url} with params:`, params);
  };
}

const fetchFromAPI = fetchData('https://api.example.com');
fetchFromAPI({ q: 'search' }); // 输出:Fetching data from https://api.example.com with params: { q: 'search' }

🧠 柯里化延迟执行,先绑定 URL,再传递参数!


3. 函数组合:数据处理流水线

// 场景:组合函数计算最终价格
function calculatePrice(price, discount, taxRate) {
  const discountedPrice = price * (1 - discount);
  return discountedPrice * (1 + taxRate);
}

function curryCalculatePrice(price) {
  return function(discount) {
    return function(taxRate) {
      const discountedPrice = price * (1 - discount);
      return discountedPrice * (1 + taxRate);
    };
  };
}

const calculateWithDiscount = curryCalculatePrice(100)(0.1);
console.log(calculateWithDiscount(0.08)); // 97.2

🧠 柯里化拆分复杂函数,组合成灵活的数据处理链!


🌟 今日学习金句

"代码世界的魔法,不过是把复杂问题拆解成收集龙珠的小游戏!🐉"