为什么说Javascript函数是一等公民及其函数式编程实践?

JavaScript中函数是一等公民,可赋值、传参、返回和存储;高阶函数如map/filter/reduce及pipe等是解决实际问题的工具,纯函数需满足相同输入得相同输出且无副作用。

因为 JavaScript 中的函数能像数字、字符串一样被赋值、传参、返回和存储——这是“一等公民”的全部定义,不是修辞,而是语言底层能力。

函数能当变量用,是所有实践的起点

这不是语法糖,而是运行时真实行为:函数对象可随时被引用、重赋值、放进数组或对象里。一旦理解这点,就自然跳出“函数只能声明后调用”的思维定式。

  • 错误写法:function sayHi() { console.log('Hi') } 然后试图 typeof sayHi === 'function' 是对的,但若没赋值给变量,它只是全局作用域的一个绑定,无法动态传递
  • 正确姿势:const sayHi = () => console.log('Hi');const handlers = { click: sayHi, submit: () => alert('done') };
  • 常见坑:把匿名函数直接塞进 addEventListener 又想 later 移除?不行——没引用就无法 removeEventListener,必须先存成变量

高阶函数不是概念,是每天都在写的代码

mapfilterreduce 都是高阶函数,它们接收函数作为参数;oncedebouncepipe 都返回函数——这些不是 FP 专属玩具,而是解决实际问题的工具。

  • filter 不改原数组,但你自己写的 filterByAge(arr, min) 若用 arr.splice() 就破坏了不可变性,不再是纯函数
  • pipe 的实现很短,但能让逻辑线性可读:
    const pipe = (...fns) => (x) => fns.reduce((v, f) => f(v), x);
  • 别在循环里反复定义函数(比如 for (...) arr.push(() => {...})),闭包捕获的是同一份变量,容易引发意料外的引用问题

纯函数不是道德要求,而是可预测性的保障

所谓“相同输入 → 相同输出 + 无副作用”,本质是为了让函数行为不依赖外部状态,方便测试、复用和组合。

  • 反例:const now = () => Date.now() 每次调用结果不同,无法缓存、无法单元测试断言
  • 正例:const formatTime = (timestamp, fmt) => ... 所有依赖都显式传入,哪怕 fmt 是默认值,也建议用参数而非闭包捕获
  • 容易忽略的副作用:修改传入的对象属性(obj.name = 'new')、发请求、console.log —— 这些都会让函数脱离“纯”的范畴,影响组合可靠性

真正卡住人的往往不是“怎么写 pipe”,而是忘记函数作为值时的生命周期管理——比如事件监听器没清理、闭包长期持有大对象、setTimeout 回调里还引用着已卸载组件的 this。一等公民的权利越大,责任越具体。