一、为什么要学习函数式编程
1.1 函数式编程是一个非常古老的概念,早于第一台计算机的诞生,函数式编程历史
1.2 那我们为什么现在还要学习函数式编程呢?
- 函数式编程随着 React 的流行受到越来越多的关注
- Vue3也开始拥抱函数式编程
- 函数式编程可以抛弃 this
- 打包过程中可以更好利用 tree-shaking 过滤无用代码
- 方便测试、方便并行处理
- 有很多库,可以帮助我们进行函数式开发:lodash、underscore、ramda
1.3 什么是函数式编程
函数式编程(Functional programming, FP)FP是编程范式之一,我们常听说的编程范式还有面向过程、面向对象编程。
- 面向对象编程的思维方式:把现实世界中的事物抽象成程序世界中的类和对象,通过封装、继承和多态来演示事物事件的联系
- 函数式编程的思维方式:把现实世界的事物和事物之间的联系抽象到程序世界(对运算过程进行抽象)
- 程序的本质:根据输入通过某种运算获得相应的输出,程序开发过程中会涉及很多有输入和输出的函数
- x -- f --> y , y=f(x) , f为映射
- 函数式编程中的函数不是指程序中的函数(方法),而是数学中的函数,即映射关系,例如y=sin(x)
- 相同的输入始终要得到相同的输出(纯函数)
- 函数式编程用来描述数据(函数)之间的映射
// 非函数式
let num1 = 1;
let num2 = 2;
let sum1 = num1 + num2;
console.log(sum1);
// 函数式:主要思想是把有关系的运算过程尽量写成一系列嵌套的函数调用
function add(n1, n2) {
return n1 + n2;
}
let sum2 = add(2, 3);
console.log(sum2);
二、函数是一等公民(First-class function)
为什么说函数是JavaScript的一等公民?
JavaScript 中的函数相对于数据类型而言更加复杂,它可以有属性,也可以被赋值给一个变量,还可以作为参数被传递......正是这些强大特性让它成了 JavaScript 的“一等公民”。
- 函数可以存储在变量中
- 函数作为参数
- 函数作为返回值
// 把函数赋值给变量
let fn = function () {
console.log("First-class Function");
}
fn();
// 一个示例
const BlogController = {
index(posts) { return View.index(posts) },
show(post) { return View.show(post) },
create(post) { return Db.create(attrs) },
update(post, attrs) { return Db.update(post, attrs) },
destory(post) { return Db.destory(post) },
};
// 优化
const BlogController = {
index: View.index,
show: View.show,
create: Db.create,
update: Db.update,
destory: Db.destory,
};
三、什么是高阶函数
高阶函数(Higher-order function)
1.可以把函数作为参数传给另一个函数
// 高阶函数-函数作为参数
// forEach实现:把功能函数作为参数传递
function forEach(array, fn) {
for (let i = 0; i < array.length; i++) {
fn(array[i]);
}
}
// 测试
let arr = [6, 4, 2, 5, 3, 1];
forEach(arr, function (item) {
console.log(item);
});
// filter实现:定义新数组results,遍历array,把调用fn得到的结果放入results返回
function filter(array, fn) {
let results = [];
for (let i = 0; i < array.length; i++) {
if (fn(array[i])) {
results.push(array[i]);
}
}
return results;
}
// 测试
let res = filter(arr, function (item) {
return item % 2 === 0;
});
console.log(res);
2.可以把函数作为另一个函数的返回值
// 高阶函数-函数作为返回值
function makeFn() {
let message = "hello world";
return function () {
console.log(message);
};
}
const fn = makeFn();
makeFn()();
fn();
// once:只能执行一次
function once(fn) {
let done = false;
return function () {
if (!done) {
done = true;
return fn.apply(this, arguments);
} else {
console.log("无需重复支付~");
}
};
}
let pay = once(function (money) {
console.log(`支付了${money} RMB`);
});
pay(5);
pay(5);
pay(5);
常用的高阶函数
forEach、filter、map、every、some、find/findIndex、reduce、sort ...
模拟常见的高阶函数: map every some
let arr = [6, 3, 2, 5, 1, 4];
const map = (array, fn) => {
let results = [];
for (let value of arr) {
results.push(fn(value));
}
return results;
};
// 测试:将数组中每个元素平方
arr = map(arr, v => v*v);
console.log(arr);
// every:判断数组中的元素是否都匹配条件
const every = (array, fn) => {
let result = true;
for (let value of array) {
result = fn(value);
if (!result) { // 只要有元素不满足就返回false
break;
}
}
return result;
};
// 测试
let arr = [6, 3, 2, 5, 1, 4];
newArr = every(arr, (v) => v > 0);
console.log(newArr);
// some:用来检测数组中的元素是否有一个满足条件
const some = (array, fn) => {
let result = false;
for (let value of array) {
result = fn(value);
if (result) break; // 只要有一个满足,返回true
}
return result;
};
// 测试
let arr = [6, 3, 2, 5, 1, 4];
newArr = some(arr, (v) => v > 15);
console.log(newArr);
四、什么是闭包
1.1闭包(Closure):函数和其周围的状态(词法环境)的引用捆绑在一起形成闭包
- 可以在另一个作用域中调用另一个函数的内部函数并访问到该函数的作用域中的成员
// 函数作为返回值
function makeFn () {
let msg = "hello world";
return function () { // 返回的函数对makeFn的内部变量有引用,makeFn调用后是不能被释放掉的
console.log(message);
}
}
const fn = makeFn();
fn();
// once:传入的函数只会被执行一次
function once (fn) {
let done = false;
return function () { // 利用闭包,延长了外部函数once的局部变量done的作用范围
if(!done) {
done = true;
return fn.apply(this, arguments);
}
}
}
let pay = once(function(money){
console.log(`支付了${money}RMB`)
})
// 只会支付一次
pay(5);
pay(5);
- 闭包的本质:函数在执行的时候会放到一个执行栈上,当函数执行完毕后会从执行栈上移除,但是堆上的作用域成员因为被外部引用而不能被释放,因此内部函数依然可以访问外部函数的成员
1.2 闭包案例
Math.pow(4, 2);
Math.pow(5, 3);// 每次求一个数的n次幂结果,都要传入两个参数
function makePower (power) {
return function (number) {
return Math.pow(number, power)
}
}
// 求平方 求三次方
let pow2 = makePower(2);
let pow3 = makePower(3);
// 对比之前
// Math.pow(4, 2); ---> pow2(4);
// Math.pow(5, 4); ---> pow3(5);
可以看到,每次调用makePower,都会形成闭包,缓存内部的power