函数式编程
- 很多库可以辅助进行函数式开发:lodash,underscore,ramda
- 函数式编程可以抛弃 this
- vue3 也是越来多使用函数式编程,如 composition
- 方便测试,并行处理
概念
函数式编程(Functional Programming, FP),FP 是编程范式之一,我们常听说的编程范式还有面向过程 编程、面向对象编程。
- 面向对象编程的思维方式:把现实世界中的事物抽象成程序世界中的类和对象,通过封装、继承和 多态来演示事物事件的联系
- 函数式编程的思维方式:把现实世界的事物和事物之间的联系抽象到程序世界(对运算过程进行抽 象)
- 程序的本质:根据输入通过某种运算获得相应的输出
- x -> f(联系、映射) -> y,y=f(x)
- 函数式编程中的函数指的不是程序中的函数(方法),而是数学中的函数即映射关系,例如:y = sin(x),x 和 y 的关系
- 相同输入始终得到相同输出(纯函数)
总结就是:函数式编程用来描述数据(函数)之间的映射
案例:
// 非函数式
let num1 = 1;
let num2 = 2;
let sum = num1 + num2;
// 函数式
function sum (n1,n2) {
return n1+n2;
}
sum (1,2)
函数式编程就是对运算过程的抽象
函数是一等公民
- 可以存储在变量中;
- 可以作为参数;
- 可以作为返回值;
在 JavaScript 中函数就是一个普通的对象 (可以通过 new Function() ),我们可以把函数存储到变量/ 数组中,它还可以作为另一个函数的参数和返回值,甚至我们可以在程序运行的时候通过 new Function(‘alert(1)’) 来构造一个新的函数。
- 把函数赋值给变量
// 把函数赋值给变量
let fn = function() {
console.log('hello word')
}
fn()
// 一个示例
const BlogController = {
index(posts) {
return views.index(posts)
}
}
// 优化
const BlogController = {
index: Views.index
}
- 函数是一等公民,是学习高阶函数和柯里化等的基础;
高阶函数
- 可以把函数作为参数传递给另一个函数
- 可以把函数作为另一个函数返回值
函数作为参数
// 函数作为参数
// 模拟 forEach:遍历数组每一个元素,并对每个元素进行处理
function forEach(array,fn) {
for(var i = 0; i < array.length; i++) {
fn(array[i])
}
}
let arr = [1,2,3,4];
forEach(arr,function(v) {
console.log(v)
});
// 模拟 filter:过滤数组中满足条件的元素
function filter(array,fn) {
let result = [];
for(let value of array) {
if (fn(value)) {
result.push(value)
}
}
return result;
}
let arr = [11,12,3,2,5];
let r = filter(arr,function(v) {
return v%2;
})
console.log(r)
函数作为返回值
function makeFn() {
let msg = 'hello word function';
return function() {
console.log(msg)
}
}
const fn = makeFn();
fn();
makeFn()()
模拟 once:对一个函数只执行一次
意义:订单多次点击只执行一次
function once(fn) {
let done = false;
return function() {
if (!done) {
done = true
return fn.apply(this,arguments)
}
}
}
let pay = once(function(money) {
console.log(money)
})
pay(5)
pay(5)
pay(5)
意义
- 运算过程抽像成函数,然后在任何地方可以重用,这样可以不需要关注细节,只需要关注我们的目标就好了;
- 高阶函数就是用来抽象通用的问题;
常用的高阶函数
- forEache
- map
- filter
- every
- some
- find/findIndex
- reduce
- sort
- …
模拟 map
// 对数组中每个元素遍历并处理,并将处理结果存储到一个新数组返回
const map = (array,fn) => {
let result = [];// 新数组
for(let value of array) {
result.push(fn(value))
}
return result
}
// 测试
let arr = [1,2,3,4]
arr = map(arr,v => v * v)
map 函数参数是个函数,它是高阶函数,不管可以对数组中每个元素求平方,也可以通过传入不同函数,对数组中任意元素操作;
模拟 every
数组方法:用来判断数组中每个元素是否匹配我们指定的条件,这个条件是可变的;
const every = (array, fn) => {
let result = true;
for(let value of array) {
result = fn(value)
if (!result) {
// 当有一个元素不满足则跳出循环
break;
}
}
return result;
}
// 测试
let arr = [2,12,11,13,15];
// 判断数组中每个元素是否都大于 10
let r = every(arr,v => v > 10)
模拟 some
检测数组中元素,是否有有一个满足指定条件
const some = (array,fn) => {
let result = false;
for(let value of array) {
result = fn(value);
if (result) {
return
}
}
return result;
}
// 测试
let arr = [2,3,5,7,11];
// 判断数组中每个元素是否有偶数
let r = every(arr,v => v > 10)
闭包
- 函数和其周围的状态(词法环境)的引用捆绑在一起形成闭包
- 可以在另一个作用域中调用一个函数内部函数,并访问该函数作用域中的成员
本质
函数执行时会放在一个执行栈上当函数执行完毕会从执行栈上移除,但是堆上的作用域成员因为被外部应用不能释放,因此内部函数依然可以访问外部函数成员。
案例
求幂:
Math.pow(4,2) // 求 4 的 2 次方
Math.pow(5,2)
// 生成求平方和三次方函数
function makePower(power) {
return funnction (number) {
return Math.pow(number,power)
}
}
// 求平方
let power2 = MakePower(2);
// 求三次方
let power3 = MakePower(3);
console.log(power2(4))
console.log(power3(4))
案例二
// 求员工公司(基本工资加绩效工资)
// getSalary(12000,2000)
// getSalary(15000,3000)
// getSalary(15000,4000)
// 为不同级别员工生成一个去求工资函数
fucntion makeSalary(base) {
return function (performance) {
return base + performace;
}
}
let salaryLevel1 = makeSalary(12000);
let salaryLevel2 = makeSalary(15000);