主要的编程范式有三种:命令式编程,声明式编程和函数式编程。
命令式编程:
命令式编程的主要思想是关注计算机执行的步骤,即一步一步告诉计算机先做什么再做什么。
比如:如果你想在一个数字集合 collection(变量名) 中筛选大于 5 的数字,你需要这样告诉计算机:
第一步,创建一个存储结果的集合变量 results;
第二步,遍历这个数字集合 collection;
第三步:一个一个地判断每个数字是不是大于 5,如果是就将这个数字添加到结果集合变量 results 中。
很明显,这个样子的代码是很常见的一种,不管你用的是 C, C++ 还是 C#, Java, Javascript, BASIC, Python, Ruby 等等,你都可以以这个方式写。
声明式编程:
声明式编程是以数据结构的形式来表达程序执行的逻辑。它的主要思想是告诉计算机应该做什么,但不指定具体要怎么做。
SQL 语句就是最明显的一种声明式编程的例子,例如:
SELECT*FROM collection WHERE num>5
除了 SQL,网页编程中用到的 HTML 和 CSS 也都属于声明式编程。
通过观察声明式编程的代码我们可以发现它有一个特点是它不需要创建变量用来存储数据。
函数式编程:
“函数式编程”, 又称泛函编程, 是一种”编程范式”(programming paradigm),也就是如何编写程序的方法论,和指令式编程相比,函数式编程的思维方式更加注重函数的计算。函数式编程和声明式编程是有所关联的,因为他们思想是一致的:即只关注做什么而不是怎么做。但函数式编程不仅仅局限于声明式编程。它的主要思想是把问题的解决方案写成一系列嵌套的函数调用。
函数式编程最重要的特点是“函数第一位”,即函数可以出现在任何地方,比如你可以把函数作为参数传递给另一个函数,不仅如此你还可以将函数作为返回值。大部分常见的编程语言一半都已经提供了对这种编程方式的支持,比如 JavaScript,再有 C# 中的 LINQ 和 Java 中的 Lambda 和闭包的概念。
使用指令式编程时,当情况变得更加复杂,表达式的写法会遇到几个问题:表意不明显,逐渐变得难以维护复用性差,会产生更多的代码量会产生很多中间变量。这个时候使用函数式编程的优势就提现出来了:
函数式编程的优点
1. 代码简洁,开发快速
函数式编程大量使用函数,减少了代码的重复,因此程序比较短,开发速度较快。
// 数组中每个单词,首字母大写
// 一般写法
const arr = ['apple','pen','apple-pen'];
for(const i in arr){
const c = arr[i][0];
arr[i] = c.toUpperCase() + arr[i].slice(1);
}
console.log(arr);
// 函数式写法一
function upperFirst(word){
returnword[0].toUpperCase() + word.slice(1);
}
function wordToUpperCase(arr){
returnarr.map(upperFirst);
}
console.log(wordToUpperCase(['apple','pen','apple-pen']));
// 函数式写法二
console.log(arr.map(['apple','pen','apple-pen'], word => word[0].toUpperCase() + word.slice(1)));
2. 接近自然语言,易于理解
函数式编程的自由度很高,可以写出很接近自然语言的代码。
将表达式(1 + 2) * 3 - 4,写成函数式语言:subtract(multiply(add(1,2), 3), 4)
对它进行变形,不难得到另一种写法:add(1,2).multiply(3).subtract(4)
这基本就是自然语言的表达了。再看下面的代码,大家应该一眼就能明白它的意思吧:
merge([1,2],[3,4]).sort().search("2")
因此,函数式编程的代码更容易理解。
3. 更方便的代码管理
函数式编程不依赖、也不会改变外界的状态,只要给定输入参数,返回的结果必定相同。因此,每一个函数都可以被看做独立单元,很有利于进行单元测试(unit testing)和除错(debugging),以及模块化组合。
函数编程的一些基本特点包括:
1. 函数是”第一等公民”:
函数与其他数据类型一样,处于平等地位,可以赋值给其他变量,也可以作为参数,传入另一个函数,或者作为别的函数的返回值。
2. 高阶函数(Higher Order Function):
将函数作为参数,或者是可以将函数作为返回值的函数。比如我们平常用到的回调函数.
3. 函数柯里化(Currying):
把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数。
其过程就是消化一个参数,然后生成一个新的函数,剩余的参数作为新函数的参数。
// 柯里化之前
function add(x, y) {
return x + y;
}
add(1, 2) // 3
// 柯里化之后
function addX(y) {
return function (x) {
return x + y;
};
}
addX(2)(1) // 3
4. 懒惰计算(lazy evaluation):
在惰性计算中,表达式不是在绑定到变量时立即计算,而是在求值程序需要产生表达式的值时进行计算。
5. 引用透明性:
指的是函数的运行不依赖于外部变量或"状态",只依赖于输入的参数,任何时候只要参数相同,引用函数所得到的返回值总是相同的。
6. 没有副作用:
意味着函数要保持独立,所有功能就是返回一个新的值,没有其他行为,尤其是不得修改外部变量的值。
7. 纯函数:
- 其结果只能从它的参数的值来计算
- 不能依赖于能被外部操作改变的数据
- 不能改变外部状态
函数式编程思维在我们工作中的体现
1.map/reduce/filter方法
传入一个函数作为参数,返回一个新的数组,不对原来的数组进行修改,不使用中间变量,没有副作用。
2.闭包
闭包的主要用途就是可以定义一些作用域局限的持久化变量,这些变量可以用来做缓存或者计算的中间量等等。
下面的例子是一个简单的缓存工具的实现,匿名函数创造了一个闭包,使得 store 对象 ,一直可以被引用,不会被回收。
// 简单的缓存工具
const cache = (function() {
const store = {};
return {
get(key) {
return store[key];
},
set(key, val) {
store[key] = val;
}
}
}());
cache.set('a', 1);
cache.get('a'); // 1
3.链式优化
比如回调函数和promise函数
// 优化写法 (loadsh 的链式写法)
constutils = {
chain(a) {
this._temp = a;
returnthis;
},
sum(b) {
this._temp += b;
returnthis;
},
sub(b) {
this._temp -= b;
returnthis;
},
value() {
const_temp =this._temp;
this._temp =undefined;
return_temp;
}
};
console.log(utils.chain(1).sum(2).sum(3).sub(4).value());
4.连续箭头函数
function add(a) {
return function(b) {
return a + b
}
}
var add3 = add(3)
add3(4) === 3 + 4 //true
add 函数 在 es6 里的写法等价为
let add = a => b => a + b
add(3)(4)
参考文章:
https://github.com/EasyKotlin/chapter8_fp
http://taobaofed.org/blog/2017/03/16/javascript-functional-programing/