题目重点:Salesforce Fullstack 面经明确要求把普通加法函数改成逐次调用的 addThreeNumbers(a)(b)(c);本文讲闭包捕获、调用阶段与 JS 加法类型边界。
题意与假设
这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。
- 训练版三个参数均为 JavaScript number,最后一次调用返回数值和;前两次调用返回函数。若允许字符串参与,+ 可能拼接,需另定义类型约束或显式转换。
- 只支持固定三次、每次一个参数的调用形状 a)(b)(c;不要求 addThreeNumbers(1,2,3)、任意长度累加或 this 绑定。
- 闭包持有已传入参数,直到返回的函数不再被引用;代码不修改外部变量。
解题路线
- 把三参数函数分阶段:第一次接收 a 返回第二阶段函数;第二阶段接收 b 返回第三阶段函数;第三阶段接收 c 才计算 a+b+c。内层函数通过词法作用域访问前两层参数。
- 正确性可按调用拆解:f1=addThreeNumbers(a) 闭包固定 a;f2=f1(b) 固定 a,b;f2(c) 计算并返回三者之和。每次调用都新建自己的闭包,两个并行调用链不会混用参数。
- 如果面试官追问泛化,先区分 currying(固定 arity 转成多次单参数调用)与 partial application(一次预填部分参数);任意参数量/提前终止需要明确结束标志,不能从本题直接推断。
手动走一遍
addThreeNumbers(1)(2)(3) 先得到记住 1 的函数,再得到记住 1、2 的函数,最后返回 6。const addOne=addThreeNumbers(1); addOne(2)(3)=6,addOne(5)(7)=13;两个调用链共享 a=1,但 b 各自独立。
JavaScript 参考实现
下面的代码按上文假设实现,适合本地运行并追加自己的测试用例。
代码区可横向滚动;使用键盘时,先聚焦代码区再按方向键查看。
export function addThreeNumbers(a) {
return function addSecond(b) {
return function addThird(c) {
return a + b + c;
};
};
}
复杂度
调用三阶段时每阶段常数操作,整体时间 O(1);共创建两个返回闭包,辅助空间 O(1),但若调用方长期保存部分应用函数,其捕获参数会随函数一起存活。固定三参时不需要递归或数组累积。
易错点与追问
容易失分的边界
- 在第一层直接返回 a+b+c,此时 b/c 尚不存在;前两层必须返回函数。
- 使用一个可变全局 accumulator,会让两个并行调用链互相污染。
- 忽略 JavaScript + 的字符串拼接语义,却声称任意输入总是数值求和。
- 把 `addThreeNumbers(1)(2)(3)` 与 `addThreeNumbers(1,2,3)` 当同一调用形状。
继续深挖
- 若要求支持任意 n 个参数,如何指定 n 或结束调用,避免无限等待下一个参数?
- 若原函数依赖 this,使用箭头函数或 bind 时会如何改变 this 的捕获?
- 如何用 TypeScript 为每个阶段标注参数与返回函数类型,拒绝传入字符串?
