介绍
JavaScript中的函数式编程是一种编程范式,它将函数视为一等公民,并强调使用纯函数、不可变数据和函数组合来构建程序。函数式编程在JavaScript中得到了广泛应用,特别是在处理异步操作、函数组合和数据转换
等方面。
React 和 Vue 3 都在向函数式编程靠拢,尤其是在最新的版本中,它们都引入了一些函数式编程的概念和特性。
React 在 Hooks 的引入中,采用了函数组件作为主要的组件形式,这使得组件可以更加纯粹地关注状态和渲染。Hooks 提供了一系列的函数,如 useState
、useEffect
、useMemo
等,使得状态管理和副作用处理更加简洁和可组合。这些函数可以帮助开发者遵循函数式编程的原则,例如使用纯函数来处理状态更新,避免共享状态和可变数据等。
Vue 3 在 Composition API 的引入中,也采用了函数式的风格。Composition API 允许开发者将逻辑按照功能进行组合,而不是按照生命周期钩子进行组织。这样可以更好地重用逻辑和提高代码的可读性。Composition API 还提供了一些函数,如 reactive
、computed
、watch
等,用于处理响应式数据和副作用。这些函数使得状态管理和副作用处理更加灵活和可控。
并且函数作为JavaScript的一等公民
,这意味着函数可以像其他数据类型一样进行操作和传递。
JS
//1.函数可以被赋值给变量
const greet = function (name) {
console.log(`Hello, ${name}!`)
}
greet('Alice') // 输出 "Hello, Alice!"
//2函数可以作为参数传递给其他函数:
function sayHello(greetingFunction) {
greetingFunction("Alice");
}
function greet(name) {
console.log(`Hello, ${name}!`);
}
sayHello(greet); // 输出 "Hello, Alice!
//3 函数可以作为其他函数的返回值
function createMultiplier(multiplier) {
return function(number) {
return number * multiplier;
};
}
const double = createMultiplier(2);
console.log(double(5)); // 输出 10
//4.函数可以存储在对象中:
const person = {
name: "Alice",
greet: function() {
console.log(`Hello, ${this.name}!`);
}
};
person.greet(); // 输出 "Hello, Alice!"
//这种灵活性使得函数可以更加灵活地使用和组合,
//从而实现更好的代码可读性、可维护性和可重用性。
接下来让我们学习一下函数式编程吧
JavaScript 中函数式编程的主要特点
1.纯函数
纯函数是指在相同的输入下,返回相同的输出,并且没有副作用的函数。纯函数不依赖于外部状态,使得代码更加可靠、可测试和可维护。
js
//纯函数应该满足以下条件:
//相同的输入总是产生相同的输出。
//函数执行过程中不会改变外部状态。
//函数没有产生可观察的副作用
function add(a, b) {
return a + b
} //是纯函数
function test(a,b){
console.log("a")
} //不是纯函数 有可观察的副作用
function HelloWorld(props) {
props.info = {}
props.info.name = 'why'
} //不是纯函数 直接修改了`props`对象的属性
//修改为纯函数
function HelloWorld(props) {
const newProps = { ...props };
newProps.info = {};
newProps.info.name = 'ZhaiMou';
return newProps;
}
- 不可变性:
函数式编程中的数据结构
是强调不可变的,即一旦创建就不能再被修改。这样做的好处是避免了共享状态
和并发冲突
,因为不需要对数据进行加锁,也可以提高程序的可靠性。
js
const arr = [1, 2, 3];
// 使用concat()方法创建一个新的数组,而不是修改原始数组
const newArr = arr.concat(4);
const newArr1 = [...arr, 4]
console.log(arr); // 输出: [1, 2, 3]
console.log(newArr); // 输出: [1, 2, 3, 4]
console.log(newArr1); // 输出: [1, 2, 3, 4]
const obj = { name: "Alice", age: 25 };
// 使用Object.assign()方法创建一个新的对象,而不是修改原始对象
const newObj = Object.assign({}, obj, { age: 30 });
console.log(obj); // 输出: { name: "Alice", age: 25 }
console.log(newObj); // 输出: { name: "Alice", age: 30 }
- 高阶函数:函数式编程中的函数
可以作为参数传递给其他函数
,也可以作为返回值返回给调用者
。这样做的好处是可以提高代码的复用性和灵活性
。
js
// 高阶函数示例
function multiplyBy(factor) {
return function(number) {
return number * factor;
}
}
const double = multiplyBy(2); // 创建一个乘以2的函数
console.log(double(5)); // 输出: 10
函数柯里化 ⭐⭐⭐⭐⭐
js
// 原始函数
function add(a, b, c) {
return a + b + c;
}
//柯里化函数
function curryAdd(a) {
return function(b) {
return function(c) {
return a + b + c;
};
};
}
// 柯里化函数单一职责的原则
function sum(x) {
x = x + 2
return function(y) {
y = y * 2
return function(z) {
z = z * z
return x + y + z
}
}
}
console.log(sum(10)(20)(30))
// 柯里化函数的实现hyCurrying
function hyCurrying(fn) {
function curried(...args) {
// 判断当前已经接收的参数的个数, 可以参数本身需要接受的参数是否已经一致了
// 1.当已经传入的参数 大于等于 需要的参数时, 就执行函数
// 函数的length为参数的个数 fn.length
if (args.length >= fn.length) {
// fn(...args)
// fn.call(this, ...args)
return fn.apply(this, args)
} else {
// 没有达到个数时, 需要返回一个新的函数, 继续来接收的参数
function curried2(...args2) {
// 接收到参数后, 需要递归调用curried来检查函数的个数是否达到
return curried.apply(this, args.concat(args2))
}
return curried2
}
}
return curried
}
let curryAdd = hyCurrying(add1)
console.log(curryAdd(10, 20, 30)) //60
console.log(curryAdd(10, 20)(30)) //60
console.log(curryAdd(10)(20)(30)) //60
- 函数组合:函数组合是指将
多个函数组合成一个新函数
。这种特点可以让代码更简洁、高效和可读性更强。
js
// 函数组合示例
function addOne(number) {
return number + 1;
}
function multiplyByTwo(number) {
return number * 2;
}
function compose(func1, func2) {
return function(arg) {
return func1(func2(arg));
};
}
const addOneAndMultiplyByTwo = compose(multiplyByTwo, addOne); // 组合函数
console.log(addOneAndMultiplyByTwo(3)); // 输出: 8
通用组合函数实现
js
function hyCompose(...fns) {
let length = fns.length
for (let i = 0; i < length; i++) {
if (typeof fns[i] !== 'function') {
throw new TypeError('Expected arguments are functions')
}
}
function compose(...args) {
let index = 0
let result = length ? fns[index].apply(this, args) : args
while (++index < length) {
result = fns[index].call(this, result)
}
return result
}
return compose
}
function double(m) {
return m * 2
}
function square(n) {
return n * 2
}
var newFn = hyCompose(double, square)
console.log(newFn(10)) //40
- 函数式编程支持延迟计算,也就是推迟表达式的求值直到需要的时候。这种方式可以减少不必要的计算,并提高性能。例如,JavaScript中的
lazy evaluation
特性允许我们使用生成器、迭代器和惰性操作来实现延迟计算。
js
// 延迟计算示例
function* generateNumbers() { // 定义生成器函数
let number = 0;
while (true) {
yield number++;
}
}
const numbers = generateNumbers(); // 创建一个生成器对象
console.log(numbers.next().value); // 输出: 0
console.log(numbers.next().value); // 输出: 1
由于生成器函数是惰性求值的,即只有在需要时才会计算值,因此它可以减少不必要的计算,并提高性能。常见的还有异步请求和惰性加载和防抖节流
等等
- 数据转换和操作
js
// 数据转换和操作示例
const numbers = [1, 2, 3, 4];
const doubledNumbers = numbers.map(num => num * 2); // 映射操作
console.log(doubledNumbers); // 输出: [2, 4, 6, 8]
const evenNumbers = numbers.filter(num => num % 2 === 0); // 过滤操作
console.log(evenNumbers); // 输出: [2, 4]
const sum = numbers.reduce((acc, curr) => acc + curr, 0); // 归约操作
console.log(sum); // 输出: 10
我们使用数组的map
、filter
和reduce
方法对数字数组进行了转换和操作。我们可以使用这些操作以声明性的方式处理数据,而不需要显式的循环和状态管理。
文章更多作为自我学习,有错欢迎指出。