前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
首页程序员面试题库柯里化 currying 闭包实现原理
JavaScript函数与作用域

柯里化是怎么用闭包实现的?能手写一个 curry 吗?

柯里化把一个接收多个参数的函数,变成可以一次传一部分、分几次传完的函数。每次调用都返回一个新函数,用闭包把已经传过的参数存起来,参数凑够了就执行原函数。

前端进阶之旅 · 一题精讲更新于 2026.09.29
JavaScript#函数与作用域
先看核心答案读代码示例
理解线索

curry 的三个组成部分

  1. 原函数真正做计算的函数,它的 length 决定要凑几个参数
  2. 收集函数每次调用把新参数和旧参数拼起来,存在闭包里
  3. 执行条件参数个数达到要求就调用原函数,否则继续返回收集函数

每个中间函数都有自己的一份参数,互不影响。所以 add(1) 可以存下来反复用,每次在它基础上继续传都是一条独立的分支。

核心回答

先记住这个答案

柯里化是把 add(a, b, c) 变成 add(a)(b)(c) 这种分次传参的形式。实现靠闭包:每调用一次,就返回一个新函数,新函数通过闭包记住之前所有的参数。每次调用时把新旧参数合在一起,数量达到原函数需要的个数就执行原函数,否则继续返回新函数等着收参数。原函数需要几个参数,一般用 fn.length 判断。

  • 每次调用返回新函数,闭包攒着前面的参数
  • 参数数量够了才真正执行原函数
  • 默认参数和剩余参数会让 fn.length 变小
  • 用处是提前固定部分参数,得到更专用的函数

手写一个支持分批传参的 curry

面试里最常要求的是这种:既能 add(1)(2)(3),也能 add(1, 2)(3)。思路就是一个递归的收集函数。

闭包攒参数,攒够了执行JavaScript
function curry(fn, arity = fn.length) {
  return function collect(...args) {
    if (args.length >= arity) return fn(...args) // 参数够了,执行原函数
    return (...next) => collect(...args, ...next) // 不够,把旧参数存进闭包
  }
}

const add = curry((a, b, c) => a + b + c)
console.log(add(1)(2)(3))
console.log(add(1, 2)(3))

const add10 = add(10) // 固定第一个参数
console.log(add10(1)(2), add10(5, 5)) // 两次调用互不影响
查看输出与解释
6
6
13 20

每次返回的箭头函数,都通过闭包拿着当时的 args。add(10) 返回的函数里 args 是 [10],之后不管调用多少次,都是在 [10] 的基础上再拼新参数,生成新数组,原来那份不会被改。所以 add10 可以反复复用。

为什么要留一个 arity 参数

fn.length 并不总是函数真正需要的参数个数,这是手写 curry 最容易出问题的地方。

fn.length 什么时候不准
length 只统计第一个默认参数之前的形参,剩余参数不算。比如 (a, b = 1, c) => {} 的 length 是 1,(...nums) => {} 的 length 是 0。拿这种函数去 curry,第一次调用就会直接执行,或者根本停不下来。所以通用实现要允许调用方手动传 arity,比如 curry(sum, 3)。
参数多传了怎么办
上面的实现是参数达到或超过 arity 就执行,多出来的一起传给原函数,原函数自己决定用不用。有些库会截断到正好 arity 个,这属于约定,面试时说明你选了哪种即可。

柯里化在项目里有什么用

核心价值是提前固定一部分参数,得到一个更专用的函数。比如 const logError = log('error'),之后到处调用 logError(msg),不用每次都传级别。再比如数组处理里 list.map(multiply(2)),比写 list.map((x) => multiply(2, x)) 更短。

什么时候别用
业务代码里只固定一两个参数的话,直接写一个普通函数,或者用 fn.bind(null, 'error'),更容易读。通用的 curry 会让调用栈变深,TypeScript 类型推导也很难写准确。函数式风格的库,比如 Ramda、lodash/fp,才会大量使用柯里化。
回答前,多想一步

容易答错的地方

把柯里化和偏函数当成一回事
偏函数是一次固定几个参数,返回一个接收剩余参数的函数,bind 就是偏函数。柯里化是把函数改造成可以一个一个传参的链。两者都靠闭包,上面的 curry 实现同时支持两种用法,但概念上要分清。
用一个共享数组存参数
有人在 curry 外面定义一个 let args = [],每次调用往里 push。这样第一次用完,数组里还残留着旧参数,第二次调用结果就错了,add10 这种复用也做不到。必须每次调用都生成新数组。
试着用自己的话回答

面试官还会怎么问?

怎么实现 add(1)(2)(3)() 这种不定参数的版本?

不看参数个数,改成看是否传了空调用。每次有参数就继续收集,遇到 () 空调用时才把攒下的参数求和返回。另一种写法是让返回的函数重写 toString 或 valueOf,在被当作数字使用时输出结果,但这种写法依赖隐式转换,可读性差。

柯里化后 this 还能用吗?

上面的实现没有处理 this,调用原函数时 this 是 undefined。如果要包装对象方法,需要在 collect 里用普通函数记下 this,最后用 fn.apply(this, args) 调用。一般建议只对不依赖 this 的纯函数做柯里化。

柯里化和函数组合有什么关系?

compose(f, g)(x) 要求每个函数都只接收一个参数。柯里化能把多参数函数变成一次接收一个参数的形式,所以两者经常一起用,先用 curry 固定配置参数,再把得到的单参数函数串起来。

从一道题,走向一组知识

把知识连起来

函数与作用域

函数声明和函数表达式有什么区别?为什么一个能提前调用一个不能?

curry 内部返回的都是现场创建的函数表达式,理解函数表达式何时求值,才能看懂每一层闭包是怎么来的。

函数与作用域

所有函数都有 prototype 吗?箭头函数为什么没有?

curry 返回的是箭头函数,没有 prototype,不能拿来 new,所以不适合包装构造函数。

参考资料

  • JavaScript:函数与作用域参考资料

示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。

本题目录
  1. 先记住这个答案
  2. 手写一个支持分批传参的 curry
  3. 为什么要留一个 arity 参数
  4. 柯里化在项目里有什么用
  5. 容易答错的地方
  6. 面试官还会怎么问
  7. 把知识连起来
读懂,再试着讲出来

先看核心答案,再读代码。最后展开追问,检查自己有没有遗漏边界。

试着回答追问
浏览全部面试题理解原理,也关注真实的使用场景。回到顶部 ↑