函数式编程 | 学了就能分配女朋友?
什么是函数式编程:
函数式编程(FP):指的是一种编程范式, 常说的编程范式还有面向对象编程,还有面向过程的编程。
面向对象是将现实世界中的事物抽象成程序世界中的对象。
函数式编程是将事物间的联系抽象到程序世界中,是对运算过程的抽象。函数式编程中的函数指的不是程序中的函数,而是数学中的函数即映射关系。
好处就是函数的重用和函数的重组。
高阶函数:
什么是高阶函数?
函数做参数或函数作为返回值就是高阶函数。
函数作为返回值的例子
function once(fn) {
let done = false;
return function () {
if (!done) {
done = true;
fn.apply(this, arguments); //可以使用apply方法指定调用环境
}
};
}
let fn = once(function (money) {
console.log(`支付了${money}元`);
});
fn(5);
高阶函数用来抽象,让我们只需要关注实现的目标就可以了
//使用高阶函数实现map方法
const map = function (array, fn) {
let newResult = [];
for (let value of array) {
//for of 和for in循环的差别
newResult.push(fn(value));
}
return newResult;
};
let arr = [1, 2, 3, 4, 5];
let newArr = map(arr, function (item) {
return item * item;
});
//使用高阶函数实现every方法
const every = function (array, fn) {
let isTorF = true;
for (let value of array) {
if (fn(value)) {
isTorF = false;
if(!result){ //当错误的时候跳出循环
break
}
}
}
return isTorF;
};
let arr = [1, 2, 3, 4];
console.log(
every(arr, function (item) {
return item > 3;
})
);
//高阶函数实现some方法
const some = function (array, fn) {
let isTorF = false;
for (let value of array) {
if (fn(value)) {
isTorF = true;
if (isTorF) {
break;
}
}
}
return isTorF;
};
let arr = [1, 2, 3, 4, 5, 6];
console.log(some(arr, function (value) {
return value > 3;
}));
知识点补充:for of 和for in循环区别
for in 只能获得键名但是不能获得键值,for of可以获得键值
闭包:
闭包就是在一个作用域中调用一个函数内部函数并访问到该函数作用域中的成员。函数在执行的时候会被放在一个执行栈上,直送完毕之后会被移除,但是堆上的作用域成员因为被外部引用而不会能被释放。
纯函数:
概念:相同输入永远 会得到相同输出,没有可观察的副作用。
副作用就是函数依赖于外部的状态是就无法保证相同的输出
let a = 20
function(b){ //每次输入b=30,当a被改变成为其他函数的时候就无法保证相同的输出,也就是副作用
return a>b
}
//副作用的来源:所有的外部交互都会产生副作用,但是副作用不能完全禁止,要将其控制在可控的范围内。
比如说slice是纯函数,但是splice不是纯函数。多次调用结果是相同的。
纯函数的好处,可缓存,方便测试。
function area(r) { //计算圆面积的函数
console.log(123);
return Math.PI * r * r;
}
function memorize(fn) { //将一个函数转换成为记忆函数
let cache = {};
return function () {
let key = JSON.stringify(arguments);
cache[key] = cache[key] || fn.apply(fn, arguments);
return cache[key];
};
}
let areaMemorize = memorize(area); //将计算圆面积的函数记忆化
areaMemorize(2); //123
areaMemorize(2); //无打印,结果被记忆了,不用调用area函数重新计算
areaMemorize(3); //123
Lodash:
npm install lodash //lodash 引入
柯里化:
当一个函数有多个参数的时候先传递一部分参数调用它(这部分参数以后永远不变)
然后返回一个函数接受剩余的参数,返回结果。
lodash中柯里化函数: _.curry(func)
功能创建一个函数,该函数接受一个或者多个func的参数,如果函数参数被满足则直接调用func函数,否则继续返回该函数并等待接受剩余的参数
参数:需要柯里化的函数
返回值:柯里化后的函数
const _ = require("lodash");
function sum(a, b, c) {
return a + b + c;
}
let curried = _.curry(sum);
console.log(curried(1, 2, 3)); //6
console.log(curried(1, 2)(3)); //6
柯里化原理:
//探究原理自顶向下
function curry(func) {
return function curried(...args) {//柯里化返回函数
//调用的时候参数不满足形参个数,返回函数
if (args.length < func.length) {
return function () {
return curried(...args.concat(Array.from(arguments)));
};
} else {
//调用的时候参数满足,直接执行
return func(...args);
}
};
}
函数组合:
也就是将中间函数组合起来,成为一个函数就行了。
lodash中的函数_.flowRight()可以用来函数组合,注意执行顺序是从右至左
const _ = require("lodash");
let reverse = (arr) => arr.reverse();
let getFirst = (arr) => arr[0];
let toUpper = (str) => str.toUpperCase();
let fn = _.flowRight(toUpper, getFirst, reverse);
console.log(fn(["one", "two", "three"]));
组合函数的原理:
function flowRight(...args) {
return function (value) {
return args.reverse().reduce((acc, fn) => {
return fn(acc);
}, value);
};
}
结合律:
let fn = flowRight(toUpper, flowRight(getFirst, reverse));
lodash的fp模块:
因为函数式编程遵循数据滞后函数优先的原则,所以lodash的fp库里面的函数就提供这些方法,不需要改造lodash函数。使用let fp = require('lodash/fp')引入
lodash中的方法都是数据优先函数滞后的。
Point Free:
point free编程风格,就是将数据处理的过程定义成于函数无关的合成运算,不需要用到代表数据的那个函数,只需要将简单的运算数据合成到一起。即函数的组合。
Functor:
是一个特殊的容器,通过一个普通的对象来实现,该对象有map方法,map方法可以运行一个函数对值进行处理(变形关系)
class Container {
static of(value) { //使用of静态方法,不实用new,不会认为面向对象
return new Container(value);
}
constructor(value) {
this._value = value;
}
map(fn) { //map方法,传入变形函数将容器里面的值变形到另外一个容器
return Container.of(fn(this._value));
}
}
Container.of(5) //但是如果这里传的值是null,就会报错,这时我们就要对外部的空值做处理
.map((x) => x + 2)
.map((x) => x * x);
MayBe函子:
针对编程时出现的报错做处理,改造成为可以处理空值和undefined的
class MayBe {
static of(value) {
//使用of静态方法,不实用new,不会认为面向对象
return new MayBe(value);
}
constructor(value) {
this._value = value;
}
map(fn) {
//map方法,传入变形函数将容器里面的值变形到另外一个容器
return this.isNothing() ? MayBe.of(null) : MayBe.of(fn(this._value));
}
isNothing() {
return this._value === null || this._value === undefined;
}
}
console.log(
MayBe.of(null) //null问题解决了,但是我们不知道哪一步产生空值的问题
.map((x) => x + 2)
.map((x) => x * x)
);
Either函子:
异常的出现,会让函数变得不纯,那么我们就要使用Either函子来做异常处理
class Left { //错误的处理
static of(value) {
return new Left(value);
}
constructor(value) {
this._value = value;
}
map(fn) { //直接返回错误信息
return this;
}
}
class Right { //正确处理
static of(value) {
return new Right(value);
}
constructor(value) {
this._value = value;
}
map(fn) {
return Right.of(fn(this._value));
}
}
function parseJSON(json) {
try {
return Right.of(JSON.parse(json));
} catch (e) {
return Left.of({ error: e.message });
}
}
let r = parseJSON({ name: "zs" }).map((x) => x.name.toUpperCase());
console.log(r);
fortile库实现函数式编程,使用闭包回造成内存泄露,可以将引用赋值为空。
宏任务微任务
function fn(){
let c =3
return function sum(a,b){
return a+b+c
}
}
let sum = fn()
sum()
sum = null //取消引用
但是多数情况下,闭包函数都是要重用的,可以不取消。
记笔记及时复习。
微任务队列优先于宏任务
数组reduce方法补充:
arr.reduce(callback,initialValue) //为数组中的每一个函数一次执行回调函数
// callback函数执行数组每个值的函数,包含四个参数
// 1、previousValue (上一次调用回调返回的值,或者是提供的初始值(initialValue))
// 2、currentValue (数组中当前被处理的元素)
// 3、index (当前元素在数组中的索引)
// 4、array (调用 reduce 的数组)
// initialValue (作为第一次调用 callback 的第一个参数。)
arguments知识点补充:
参数匹配是从左到右进行匹配,实参小于形参,则后面的实参值为underfined,可以使用函数名.length的方式获得函数形参长度,函数内部使用arguments.length可以获得函数实参个数,使用arguments.callee引用函数本身
关于arguments更多:https://www.jianshu.com/p/d7ed5ade67a3
Array.from()//表示从一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例
正则表达知识点补充:
//正则基本模式
let expression = /pattern/flags
// 匹配模式(flags)包括g,全局模式,i不区分大小写模式,m多行模式
// 使用构造函数创建正则表达式
let pattern = new RegExp(pattern,flags)
//cvte面试,电话号码正则
let exp1 = /^1[3456789]\d{9}$/
let exp2 = new RegExp("/^1[3456789]\d{9}$/","g")
关于正则表达式更正式的整理:https://www.cnblogs.com/xiaoshen666/p/11200961.html