sum()
题目描述
实现一个 sum() 函数,支持柯里化累加功能:
- 支持连续调用:
sum(1)(2)(3) == 6 - 支持空调用:
sum(1)()(2) == 3 - 默认值为 0:
sum() == 0 - 使用
==比较(触发类型转换)
本题考查 函数柯里化、Symbol.toPrimitive 和 闭包机制 的综合运用。
核心知识点
1. 函数柯里化 (Currying)
- 定义: 将多参数函数转换为一系列单参数函数的技术
- 特点: 每次调用返回一个新函数,支持部分应用
- 优势: 代码复用、参数预设、函数组合
- 实现: 通过闭包保持参数状态
2. Symbol.toPrimitive 类型转换
- 作用: 控制对象到原始值的转换行为
- 触发场景:
==比较、数学运算、字符串拼接 - 优先级: 最高,优先于
valueOf()和toString() - 参数: hint 可为 'number'、'string' 或 'default'
3. 闭包状态管理
- 机制: 内部函数访问外部函数的变量
- 持久化: 变量在函数执行完后依然存在
- 应用: 保持累加状态,实现状态封装
代码实现
javascript
export default function sum(v = 0) {
// 返回包装函数,支持继续调用和累加
function wrapper(newArg = 0) {
return sum(v + newArg) // 递归调用,传入累加后的值
}
// 定义类型转换行为,返回当前累加值
wrapper[Symbol.toPrimitive] = () => v
return wrapper
}关键技术点
1. 递归累加机制
javascript
// 每次调用的执行流程
sum(1)(
// v=1, 返回 wrapper 函数
2,
)(
// v=1+2=3, 返回新的 wrapper 函数
3,
) // v=3+3=6, 返回新的 wrapper 函数
== 6 // 触发 Symbol.toPrimitive,返回 62. Symbol.toPrimitive 的工作原理
javascript
// 当函数用于比较或运算时触发
const result = sum(1)(2)(3)
console.log(result == 6) // true,触发 toPrimitive
console.log(result + 0) // 6,触发 toPrimitive
console.log(String(result)) // "6",触发 toPrimitive
console.log(Number(result)) // 6,触发 toPrimitive
// 直接调用
console.log(result[Symbol.toPrimitive]()) // 63. 闭包变量的生命周期
javascript
function sum(v = 0) {
// v 被闭包捕获,在 wrapper 生命周期内持续存在
function wrapper(newArg = 0) {
return sum(v + newArg) // v 在这里依然可访问
}
wrapper[Symbol.toPrimitive] = () => v // v 在这里也可访问
return wrapper
}4. 默认参数的巧妙运用
javascript
// 支持空调用的关键
function wrapper(newArg = 0) {
return sum(v + newArg) // newArg 默认为 0
}
// 使用效果
sum(1)() // 等价于 sum(1)(0)
sum(1)()(2) // 等价于 sum(1)(0)(2)5. 常见陷阱和坑点
- 严格相等:
===不会触发类型转换,需要使用== - 函数引用: 每次调用返回新函数,引用不相等
- 内存泄漏: 深度嵌套调用可能产生大量闭包
- 类型转换: 必须正确实现 Symbol.toPrimitive
使用示例
javascript
// 基本累加
console.log(sum(1)(2)(3) == 6) // true
console.log(sum(5)(10) == 15) // true
// 空调用处理
console.log(sum(1)()(2) == 3) // true
console.log(sum()(5) == 5) // true
console.log(sum()() == 0) // true
// 默认值
console.log(sum() == 0) // true
console.log(sum(0) == 0) // true
// 负数处理
console.log(sum(-1)(2)(-3) == -2) // true
console.log(sum(10)(-5) == 5) // true
// 小数处理
console.log(sum(1.5)(2.3) == 3.8) // true
console.log(sum(0.1)(0.2) == 0.3) // false (浮点精度问题)
// 多次连续调用
const chain = sum(1)(2)(3)(4)(5)
console.log(chain == 15) // true
console.log(chain(10) == 25) // true
// 中间结果保存
const intermediate = sum(10)(20)
console.log(intermediate == 30) // true
console.log(intermediate(5) == 35) // true
console.log(intermediate == 30) // true (不影响原值)
// 函数特性验证
const fn = sum(5)
console.log(typeof fn) // "function"
console.log(typeof fn === 'function') // true
console.log(fn.length) // 1 (参数个数)记忆要点
核心记忆点
- 递归结构 - 每次调用返回新的 sum 函数
- 闭包保存状态 - v 参数在闭包中累积
- Symbol.toPrimitive - 控制类型转换返回累加值
- 默认参数 - newArg = 0 处理空调用
- 不可变性 - 每次调用创建新函数,不修改原函数
类型转换记忆
javascript
// == 运算符的转换优先级
// 1. Symbol.toPrimitive (hint: 'default')
// 2. valueOf()
// 3. toString()扩展思考
1. 支持多参数版本
javascript
function sum(...args) {
const total = args.reduce((acc, val) => acc + (val || 0), 0)
function wrapper(...newArgs) {
return sum(total, ...newArgs)
}
wrapper[Symbol.toPrimitive] = () => total
return wrapper
}
// 使用示例
console.log(sum(1, 2, 3)(4, 5) == 15) // true2. 支持其他运算符
javascript
function calculator(initial = 0) {
function wrapper(value = 0) {
return calculator(initial + value)
}
// 支持多种转换
wrapper[Symbol.toPrimitive] = (hint) => {
switch (hint) {
case 'number':
return initial
case 'string':
return String(initial)
default:
return initial
}
}
// 支持 valueOf 和 toString
wrapper.valueOf = () => initial
wrapper.toString = () => String(initial)
// 扩展运算方法
wrapper.add = value => calculator(initial + value)
wrapper.subtract = value => calculator(initial - value)
wrapper.multiply = value => calculator(initial * value)
wrapper.divide = value => calculator(initial / value)
return wrapper
}
// 使用示例
console.log(calculator(10).add(5).multiply(2) == 30) // true3. 链式调用终结器
javascript
function sum(v = 0) {
function wrapper(newArg) {
// 如果没有参数且不是第一次调用,返回结果
if (arguments.length === 0 && v !== 0) {
return v
}
return sum(v + (newArg || 0))
}
wrapper[Symbol.toPrimitive] = () => v
wrapper.end = () => v // 显式终结方法
return wrapper
}
// 使用示例
console.log(sum(1)(2)(3)()) // 6
console.log(sum(1)(2)(3).end()) // 64. 支持异步累加
javascript
function asyncSum(v = 0) {
function wrapper(newArg = 0) {
if (newArg instanceof Promise) {
return newArg.then(val => asyncSum(v + val))
}
return asyncSum(v + newArg)
}
wrapper[Symbol.toPrimitive] = () => v
wrapper.then = onResolve => Promise.resolve(v).then(onResolve)
return wrapper
}
// 使用示例
asyncSum(1)(Promise.resolve(2))(3).then((result) => {
console.log(result) // 6
})5. 内存优化版本
javascript
// 使用 WeakMap 缓存结果,避免重复计算
const cache = new WeakMap()
function optimizedSum(v = 0) {
// 检查缓存
if (cache.has(optimizedSum) && cache.get(optimizedSum)[v]) {
return cache.get(optimizedSum)[v]
}
function wrapper(newArg = 0) {
return optimizedSum(v + newArg)
}
wrapper[Symbol.toPrimitive] = () => v
// 缓存结果
if (!cache.has(optimizedSum)) {
cache.set(optimizedSum, {})
}
cache.get(optimizedSum)[v] = wrapper
return wrapper
}6. 类型安全版本
javascript
function typedSum(v = 0) {
// 类型检查
if (typeof v !== 'number' || isNaN(v)) {
throw new TypeError('参数必须是有效数字')
}
function wrapper(newArg = 0) {
if (typeof newArg !== 'number' || isNaN(newArg)) {
throw new TypeError('参数必须是有效数字')
}
return typedSum(v + newArg)
}
wrapper[Symbol.toPrimitive] = () => v
// 添加类型信息
wrapper.getType = () => 'number'
wrapper.getValue = () => v
wrapper.isValid = () => !isNaN(v) && isFinite(v)
return wrapper
}