大数跨境
0
0

到底什么是JS面向函数式编程?

到底什么是JS面向函数式编程? 前端新次元
2025-12-07
0

 

字数 1963,阅读大约需 10 分钟

今天我们来聊聊JavaScript里的函数式编程。

你可能听过这个词,感觉它很高深。其实,它的核心思想很简单。函数式编程就是教我们怎么找到这些好用的“零件”,以及怎么把它们拼在一起。

一、它到底是什么?

我们先看一个最普通的代码。你想把一组数字都加上10,通常会这样写:


   
    
   let numbers = [1, 2, 3, 4, 5];
let
 results = [];
for
 (let i = 0; i < numbers.length; i++) {
  results.push(numbers[i] + 10);
}
console
.log(results); // [11, 12, 13, 14, 15]

这段代码没问题,能完成任务。但它有几个小麻烦:

  1. 1. 我们创建了一个新数组 results,专门用来装结果。
  2. 2. 我们手动管理了循环计数器 i
  3. 3. 我们明确地告诉计算机“怎么做”:循环、取值、加10、放入新数组。

函数式编程的思路不一样。它更关心“做什么”。上面的任务,用函数式的方法写出来是这样的:


   
    
   let numbers = [1, 2, 3, 4, 5];
let
 results = numbers.map(num => num + 10);
console
.log(results); // [11, 12, 13, 14, 15]

看,代码变短了,也变清晰了。我们直接对数组说:“请你把里面的每个元素都映射(map)成一个新值,新值是旧值加10。”我们不用管循环怎么跑,不用管中间变量,只声明了我们要的转换规则。

所以,函数式编程是一种编程范式。它把计算过程看作是数学函数的求值,避免改变状态和使用可变数据。 这句话有点绕,我们拆开看它的几个核心特点。

二、三个核心

函数式编程建立在几个重要的概念上。理解了它们,你就理解了大部分内容。

1. 纯函数

纯函数是函数式编程的基石.

一个纯函数有两个要求:

  • • 相同的输入,永远得到相同的输出。
  • • 没有副作用。 意思是它不会改变函数外部的任何东西(比如修改全局变量、改变传入的参数)。

我们看例子:


   
    
   // 不纯的函数
let
 taxRate = 0.1; // 依赖外部变量
function
 calculateTax(price) {
    return
 price * taxRate; // 如果taxRate变了,同样的price会得到不同结果
}

// 纯的函数

function
 calculateTaxPure(price, rate) {
    return
 price * rate; // 结果只由参数决定,不影响任何外部状态
}

纯函数的好处太多了:

  • • 好测试:你不用设置一堆环境,给输入,断言输出就行。
  • • 好理解:你看函数签名就知道它能干什么,不用担心它暗地里搞小动作。
  • • 好复用:它不依赖特定上下文,搬到哪都能用。
  • • 好缓存:如果输入一样,我们可以直接把上次的结果给你,不用再算。

写代码时,多写纯函数,你的程序会稳定很多。

2. 不可变性

在函数式编程里,我们不修改已有的数据。如果想改变数据,我们就创建一份新的。

比如,我们有一个用户对象:


   
    
   let user = { name: '小明', age: 20 };

传统做法可能是直接改:


   
    
   user.age = 21; // 小明长大了

函数式做法是创建新对象:


   
    
   let updatedUser = { ...user, age: 21 }; // 使用扩展运算符创建新对象

user 还是20岁,updatedUser 是21岁。原来的数据没动。

这样做有什么好处?最大的好处是安全。当数据不可变时,你就不用担心它在某个角落被意外修改,导致难以追踪的bug。尤其是在多线程或异步环境下,不可变数据能避免很多头疼的竞争问题。在JavaScript这种单线程语言里,它也让你对数据流更有把握。

数组操作也一样。不要用 pushpopsplice 去修改原数组,而是用 mapfiltersliceconcat 这些返回新数组的方法。

3. 函数可以当“值”用

在JavaScript里,函数和其他值(数字、字符串)没什么不同。你可以:

  • • 把函数赋值给变量
  • • 把函数当作参数传给另一个函数
  • • 让一个函数返回另一个函数

这听起来平常,但威力巨大。它允许我们进行“高阶函数”操作。

高阶函数:要么接收函数作为参数,要么返回一个函数,要么两者都是。

我们之前用的 map 就是一个高阶函数,它接收一个函数作为参数。setTimeout 和 addEventListener 也是,它们都接收一个回调函数。


   
    
   // 函数当参数
setTimeout
(() => console.log('时间到!'), 1000);

// 函数当返回值

function
 createMultiplier(factor) {
    return
 function(number) {
        return
 number * factor;
    };
}
let
 double = createMultiplier(2);
console
.log(double(5)); // 10

能轻松地操作函数,是我们组合功能、构建复杂逻辑的关键。

三、组合与柯里化

掌握了基础,我们可以玩点更厉害的。

函数组合

把多个小函数,组合成一个复杂的新函数。就像流水线,数据从一个函数流向下一个函数。

假设我们有两个简单的函数:


   
    
   const add = (a, b) => a + b;
const
 square = x => x * x;

我们想先加再平方。可以手动组合:


   
    
   let result = square(add(2, 3)); // (2+3)的平方 = 25

我们可以写一个通用的组合函数:


   
    
   function compose(f, g) {
    return
 function(x) {
        return
 f(g(x));
    };
}
// 或者用箭头函数更酷

const
 compose = (f, g) => x => f(g(x));

const
 addThenSquare = compose(square, add);
// 注意:add接收两个参数,这里需要特殊处理,仅作概念演示

组合让复杂逻辑由简单部件搭建而成,每个部件都容易测试和理解。

柯里化

把一个接收多个参数的函数,变成一系列接收一个参数的函数。

比如,一个普通的加法函数:


   
    
   function add(a, b) {
    return
 a + b;
}
add
(2, 3); // 5

柯里化之后:


   
    
   function curriedAdd(a) {
    return
 function(b) {
        return
 a + b;
    };
}
// 或者用箭头函数

const
 curriedAdd = a => b => a + b;

let
 add2 = curriedAdd(2); // 固定第一个参数为2
add2
(3); // 5
add2
(10); // 12

柯里化的好处是参数复用延迟执行。你先提供一部分参数,得到一个更具体的函数,以后再用。这在组合函数时特别有用,因为它让每个函数都变成只接收一个参数的形式,更容易被组合。

四、一个例子

我们来看一个更贴近实际的例子。假设我们有一组用户数据,需要:

  1. 1. 筛选出活跃用户。
  2. 2. 获取他们的名字。
  3. 3. 生成欢迎邮件内容。

命令式写法(传统写法):


   
    
   let users = [
    { name: '张三', active: true, email: 'zhang@example.com' },
    { name: '李四', active: false, email: 'li@example.com' },
    { name: '王五', active: true, email: 'wang@example.com' }
];

let
 activeUserNames = [];
for
 (let i = 0; i < users.length; i++) {
    if
 (users[i].active) {
        activeUserNames.push(users[i].name);
    }
}

let
 messages = [];
for
 (let i = 0; i < activeUserNames.length; i++) {
    messages.push(`欢迎回来,${activeUserNames[i]}!`);
}
console
.log(messages);

函数式写法:


   
    
   let users = [
    { name: '张三', active: true, email: 'zhang@example.com' },
    { name: '李四', active: false, email: 'li@example.com' },
    { name: '王五', active: true, email: 'wang@example.com' }
];

let
 messages = users
    .filter(user => user.active)          // 第一步:过滤
    .map(user => user.name)               // 第二步:提取名字
    .map(name => `欢迎回来,${name}!`);   // 第三步:生成消息

console
.log(messages);
// ["欢迎回来,张三!", "欢迎回来,王五!"]

哪个更清晰?哪个更容易修改?

最后

函数式编程不是魔法,它是一套经过时间考验的、让代码变得更清晰、更稳定的思想和工具。
它不要求你重写整个项目,而是鼓励你从下一个函数、下一段数据处理开始尝试。

 


【声明】内容源于网络
0
0
前端新次元
聚焦前端核心技术,分享实用干货与深度解析。每日分享 JavaScript、Vue、React等文章。关注我,持续提升开发力!
内容 115
粉丝 0
前端新次元 聚焦前端核心技术,分享实用干货与深度解析。每日分享 JavaScript、Vue、React等文章。关注我,持续提升开发力!
总阅读33
粉丝0
内容115