Functional Programming
1.什么是函数式编程
函数式编程是通过复合春函数来构建软件的过程,它避免了共享的状态,易变的数据以及副作用。函数式编程是声明式
而不是命令式
,并且应用程序状态通过纯函数流转。对比面向对象编程,后者的应用程序状态是共享并共用于对象方法。
函数式编程的核心概念:
- 纯函数
- 函数复合
- 避免共享状态
- 避免改变状态
- 避免副作用
纯函数
给它同样的输入,总是返回同样的结果,并且没有副作用
函数复合
函数复合是结合两个或者多个函数,从而产生一个新函数或进行某些计算的过程。例如,复合操作f·g在javasrcipt中相当于执行f(g(x))。
共享状态
- 共享状态是任意变量,对象或者内存空间存在于共享作用域下,或者作为对象的属性在各个作用域之间被传递。共享作用域包括全局作用域和闭包作用域。通常,在面向对象编程中,对象以添加属性到其他对象上的方式在作用域之间共享。
- 举个例子,一个电脑游戏可能会控制一个游戏对象(game object),它上面有角色(characters)和游戏道具(items),这些数据作为属性存储在游戏对象之上。而函数式编程避免共享状态 —— 与前者不同地,
它依赖于不可变数据结构和纯粹的计算过程来从已存在的数据中派生出新的数据
。- 共享状态的问题是为了理解函数的作用,你需要了解那个函数所用到的全部共享变量的变化历史。
- 共享状态的另一个常见问题是改变函数调用次序可能导致一连串的错误,因为函数操作共享数据是依时序的
//使用共享数据,函数调用的次序会改变函数调用的结果
const x = {
val: 2
};
const x1 = () => x.val += 1;
const x2 = () => x.val *= 2;
x1();
x2();
console.log(x.val); // 6
//下面的例子与上面的相同,除了……
const y = {
val: 2
};
const y1 = () => y.val += 1;
const y2 = () => y.val *= 2;
// ...函数的调用次序颠倒了一下...
y2();
y1();
// ... 这改变了结果值:
console.log(y.val); // 5
不可变性
- 一个不可变的对象是指一个对象不会再它创建之后被改变,对应的,一个可变对象是指任何在创建之后可以被改变的对象
- 不要混淆const和不可变性,const 创建一个变量绑定,让该变量不能再次被赋值, const 并不创建不可变对象。虽然不能改变绑定到这个变量名上的对象,但你仍然可以改变它的属性,所以说const的变量仍然是可变的,而不是不可变的. 不可变的对象完全不能被改变,可以通过深度冻结对象来创造一个真正不可变的值
const a = Object.freeze({
foo: 'Hello',
bar: 'world',
baz: '!'
});
a.foo = 'Goodbye';
// Error: Cannot assign to read only property 'foo' of object Object
然而冻结的对象只是表面一层不可变,例如,深层的属性还是可以被改变:
const a = Object.freeze({
foo: { greeting: 'Hello' },
bar: 'world',
baz: '!'
});
a.foo.greeting = 'Goodbye';
console.log(`${ a.foo.greeting }, ${ a.bar }${a.baz}`);
副作用
副作用是值除了函数返回值以外,任何在函数调用之外观察到的应用程序状态改变,比如:
- 改变任何外部变量或对象属性(全局变量,或者在父级函数作用域脸上的变量)
- 写日志
- 在屏幕输出
- 写文件
- 发网络请求
- 触发任何外部进程
- 调用另外一个有副作用的函数
2.通过高阶函数提高可重用性
在js中,函数是一等公民
, js允许使用者将函数作为数据,可以将他们赋值给变量,作为参数传递给其他函数,将他们作为返回值返回等
高阶函数是指以函数为参数或者以函数为返回值,或者既以函数为参数又以函数为返回值。
const double = n => n * 2;
const doubleMap = numbers => numbers.map(double);
console.log(doubleMap([2, 3, 4])); // [ 4, 6, 8 ]
3.对比生命式与命令式
-
函数式编程是一个声明式范式,意思是说程序逻辑不需要通过明确描述控制流程来表达。
-
命令式 程序花费大量代码来描述用来达成期望结果的特定步骤 —— 控制流:即如何做。
-
声明式 程序抽象了控制流过程,花费大量代码描述的是数据流:即做什么。
命令式:
const doubleMap = numbers => {
const doubled = [];
for (let i = 0; i < numbers.length; i++) {
doubled.push(numbers[i] * 2);
}
return doubled;
};
console.log(doubleMap([2, 3, 4])); // [4, 6, 8]
声明式:
const doubleMap = numbers => numbers.map(n => n * 2);
console.log(doubleMap([2, 3, 4])); // [4, 6, 8]
命令式 代码中频繁使用语句。语句是指一小段代码,它用来完成某个行为。通用的语句例子包括 for、if、switch、throw,等等……
声明式 代码更多依赖表达式。表达式是指一小段代码,它用来计算某个值。表达式通常是某些函数调用的复合、一些值和操作符,用来计算出结果值。
4.结论
- 使用纯函数而不是使用共享状态和副作用
- 让可变数据成为不可变的
- 用函数复合替代命令控制流
- 使用高阶函数来操作许多数据类型,创建通用、可复用功能取代只是操作集中的数据的方法。
- 使用声明式而不是命令式代码(关注做什么,而不是如何做)
- 使用表达式替代语句
- 使用容器与高阶函数替代多态