1. 函数式编程的基础
1.1 什么是函数式编程?
函数式编程是一种编程范式,强调将计算过程建模为数学函数的求值,避免状态变化和可变数据。其核心思想包括:
- 纯函数:函数的输出仅依赖于输入,且无副作用。
- 不可变性:数据一旦创建不可修改,变化通过创建新数据实现。
- 高阶函数:函数可以作为参数传递或作为返回值返回。
- 声明式:关注“做什么”而非“怎么做”,代码更简洁。
在 JavaScript 中,函数是一等公民(First-Class Citizen),支持高阶函数、闭包等特性,使其天然适合函数式编程。
1.2 函数式编程的核心原则
-
纯函数:相同的输入始终产生相同的输出,无副作用。例如:
function add(a, b) { return a + b; } console.log(add(2, 3)); // 5反例(非纯函数):
let total = 0; function addToTotal(value) { total += value; return total; } -
不可变性:避免直接修改数据:
const numbers = [1, 2, 3]; const doubled = numbers.map(n => n * 2); // [2, 4, 6] console.log(numbers); // [1, 2, 3](原数组未变) -
避免副作用:函数不应修改外部状态:
let total = 0; // 外部状态 function addToTotal(num) { total += num; // 修改了外部状态 total return total; } -
函数组合:通过组合小函数实现复杂逻辑:
const compose = (f, g) => x => f(g(x)); const addOne = x => x + 1; const double = x => x * 2; const addOneThenDouble = compose(double, addOne); console.log(addOneThenDouble(5)); // 12
1.3 为什么在 JavaScript 中使用函数式编程?
- 可预测性:纯函数和不可变性降低调试难度。
- 模块化:高阶函数和函数组合提升代码复用性。
- 并发友好:无状态操作更适合异步和并发场景。
- 现代框架支持:React、Redux 等框架大量采用函数式思想。
2. 核心函数式编程概念
2.1 纯函数与副作用
纯函数是函数式编程的基石。实现纯函数需遵循:
- 输入决定输出:不依赖外部变量。
- 无外部修改:不更改全局状态或 DOM。
避免副作用:
// 非纯函数
let counter = 0;
function increment() {
counter++;
return counter;
}
// 纯函数
function incrementCounter(current) {
return current + 1;
}
2.2 高阶函数
高阶函数接受函数作为参数或返回函数。JavaScript 的数组方法(如 map、filter、reduce)是典型的高阶函数。
const numbers = [1, 2, 3, 4];
const doubled = numbers.map(n => n * 2); // [2, 4, 6, 8]
const evens = numbers.filter(n => n % 2 === 0); // [2, 4]
const sum = numbers.reduce((acc, n) => acc + n, 0); // 10
2.3 闭包与函数式编程
闭包允许函数访问其定义时的词法作用域,是实现函数式编程的重要机制。
function createCounter() {
let count = 0;
return {
increment: () => ++count,
get: () => count,
};
}
const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.get()); // 1
闭包实现私有状态:
function createUser(name) {
let _name = name;
return {
getName: () => _name,
setName: newName => (_name = newName),
};
}
const user = createUser('Alice');
console.log(user.getName()); // Alice
user.setName('Bob');
console.log(user.getName()); // Bob
2.4 不可变性
JavaScript 中的数组和对象是可变的,需通过复制实现不可变性。
数组不可变操作:
const numbers = [1, 2, 3];
const newNumbers = [...numbers, 4]; // [1, 2, 3, 4]
console.log(numbers); // [1, 2, 3]
对象不可变操作:
const user = { name: 'Alice', age: 30 };
const updatedUser = { ...user, age: 31 };
console.log(user); // { name: 'Alice', age: 30 }
console.log(updatedUser); // { name: 'Alice', age: 31 }
使用 Object.freeze:
const config = Object.freeze({
apiUrl: 'https://api.example.com',
timeout: 5000,
});
config.apiUrl = 'new-url'; // 无效果
console.log(config.apiUrl); // https://api.example.com
什么是柯里化
将一个多元(多参数)函数,转换成一个可以依次调用的单元函数
这里调用curryAdd后,返回的函数就通过闭包的形式记住了curryAdd的第一个参数
封装柯里化函数
下面封装一个curry函数
function curry(fn){
return function curriedFn(){
var args = Array.prototype.slice.call(arguments)
//将 arguments 转换为一个真正的数组,方便后面的concat操作
if (args.length < fn.length){ //参数没传完
return function (){
var args2 = Array.prototype.slice.call(arguments)
return curriedFn(...args.concat(args2))
//数组合并后展开,否则传入的是一个数组参数
}
} else { //参数传完了
return fn(...args)
}
}
}
封装好后,测试使用
const add = (x,y,z) =>{
console.log(x,y,z);
}
var _add = curry(add) // 分批调用
var a = _add(1)
var b = a(2)
var c = b(3)
// 1 2 3
_add(1)(2)(3) // 一次调用
// 1 2 3
柯里化的用途
总的来说,就是实现参数的复用
- 表单的校验
function checkByRegExp(reg, string){
return reg.test(string)
}
// 校验手机号,regExp是重复的,很麻烦
checkByRegExp(/^\d{12}$/, '15152525634');
checkByRegExp(/^\d{12}$/, '13456574566');
checkByRegExp(/^\d{12}$/, '18123787385');
var curried = curry(checkByRegExp)
var checkPhone = curried(/^\d{12}$/) checkPhone('18848272720')
- 网络请求
// 假设一个通用的请求 API
const request = (type, url, options) => ...
// GET 请求
request('GET', 'http://....')
// POST 请求 request('POST', 'http://....')
// 但是通过部分调用后,我们可以抽出特定 type 的 request
const get = request('GET');
get('http://', {..})
这里转载了这篇文章
JavaScript 函数式编程思想与柯里化的深度剖析JavaScript 作为一门多范式语言,既支持面向对象编程,也为 - 掘金