文章内容输出来源:拉勾教育大前端训练营
函数式编程 (FP Funciotnal Programming)
-
什么是函数式编程?
- 是一种编程思维/规范(其他的编程式思维/规范还有:面向对象编程,面向过程编程).将计算过程分解成合格的可复用的函数,再将其组合成更强大的函数.就是对运算过程中的抽象.
-
什么是合格的函数?
- 只有纯(输入和输出相同,例如数学中的函数)的、没有副作用(跟函数外部环境发生的交互,不确定性的因素) 的函数,才是合格的函数
-
函数式编程中的函数指的是什么?
- 指的不是程序中的函数(方法),而是数学中的函数 y=f(x),一旦x值确定了y的值就确认了.
-
函数式编程的特性?
- 高阶函数就是函数式编程的特性
-
使用函数式编程的好处?
- this 指向问题,打包中Treesharing过滤无用代码,方便测试
-
函数式编程会不会保留中间的运算结果?
- 函数式编程不会保留中间的运算结果.会将最终返回的结果交给另外的函数去执行(函数组合)
-
函数式编程时函数的命名规范重要吗?
- 函数的命名是非常重要的.我们需要把函数运算组合成新的函数.函数的命名可以减少我们不必要的麻烦
-
函数式编程有什么坏处
- 函数式编程中含有大量的闭包会降低程序运行的效率
疑问:
- [ ] 还有没有其他的函数式编程特性?
- [x] 帮助我们学习函数式编程的实用库学习有哪些? loadash,randa,unerscore
code:
// 非函数式编程
let num1=2;
let num2=3;
let num3=3;
let sum=num1+num2+num3;
console.log(sum);
// 函数式编程
function add(n1,n2,n3)
{
return n1+n2+n3;
}
let sum=add(num1,num2,num3);
console.log(sum);
一个我学习中看过的指北:函数式编程指北
https://llh911001.gitbooks.io/mostly-adequate-guide-chinese/content/
函数是一等公民(First-class function)
- 在javascript中函数就是一个普通的对象.
- 特性:
- 可以把函数当成参数来传递
- 可以赋值给变量
- 可以作为返回值
- 函数作为参数的好处:可以使得函数变的更加灵活,可以高复用
高阶函数(Higher-order function)
-
和一等公民的特性相同
-
Arguments对象实例arguments:
- 它引用着函数的实参,可以用数组下标的方式"[]"引用arguments的元素(arguments.length为函数实参个数,arguments.callee引用函数自身)
-
意义:解决抽象通用的问题(抽象可以使得我们的关注点始终放在我们目标上,而非细节问题)
-
常用的高阶函数
- forEach 遍历数组
- map 返回array 对每个元素进行处理(fn),并返回新的数组
- find
- filter 返回array 返回满足指定条件(fn)的元素
- every 返回bool 判断数组中的每个元素是否都满足指定的条件(fn)
- once 使得传入的函数只被执行一次
- some 返回bool 判断数组中的元素是否有一个满足指定的条件(fn)
- reduce
- sort
- ...
闭包
- 概念: 函数内部的成员有引用,导致函数中的成员无法释放(在内存中缓存).使得我们在函数中的内部函数可以在函数相同的作用域下,在调用时可以访问到函数的成员变量.
- 作用:使得函数中的成员变量的作用范围扩大.
code
function makeFn(){
let msg="函数成员";
return function(){
console.log(msg);
}
}
let fn=makeFn();
fn();
纯函数(函数式编程的核心)
- 概念:即数学中的函数,表达输入输出之间的关系:y=f(x).多次调用函数相同的输入得到的是相同的输出
- 好处
- 可以被缓存,加快执行速度
- 可测试
- 可并行处理,纯函数是封闭的空间,不需要访问内存数据.
- 副作用
柯里化 (curry)
- 概念: 当函数有多个参数的时候,我们调用的时候先传递一部分参数调用它,然后返回一个函数接收剩下的参数,如果所有的参数都被传递了就会返回函数执行结果
- 作用: 解决硬编码
code
//定义纯函数
function getSum(a, b, c) {
return a + b + c;
}
//定义柯里化函数
function curry(func) {
//arg 调用该函数的时候传递的实参伪数组
return function curriedFn(...args) {
//判断实参和形参个数差,实参小于形参继续返回函数curried,直到全部参数都传递.
//因为是闭包 内部函数使用了func 所以func 会被一直保留
if (args.length < func.length) {
//返回的应该是个函数而不是一个被执行的函数
return function () {
//在这个函数被调用的时候传递的参数 用arguments去获取,然后和args进行合并再次进入curriedFn函数等待判断
return curriedFn(...args.concat(Array.from(arguments)));
};
// return function (...args1) {
//
// //在这个函数被调用的时候传递的参数args1,然后和args进行合并再次进入curriedFn函数等待判断
// return curriedFn(...args.concat(...args1));
// };
// return curriedFn(...args.concat(Array.from(arguments)));
}
return func(...args);
};
}
const curried = curry(getSum);
console.log(curried(1, 2, 3));
console.log(curried(1, 2)(3));
console.log(curried(1)(2)(3));
// arguments和 function.length 的区别
// arguments 是实际传递的参数 伪数组
// function.length 可以拿到数组设定的形参个数
function test(a,b,c,d,e,f,g){
console.log(arguments.length)
}
console.log(test.length)
test(1,2,3,4)
函数的组合
- 概念: 如果一个函数需要经由多个函数处理才能得到最终的值,那个我们把这个处理过程合并成一个函数,这就是函数组合.函数组合的顺序是由右→左的顺序执行的,需要满足结合律
- 注意点:函数组合选用的都是一元函数,多元函数可以柯里化处理下(多个参数情况下,前面的函数处理的过后返回的是一个值.需要用柯里化处理成一元函数等待接收完毕后统一处理并返回处理过后的值)
code
// function compose(...args) {
// return function (value) {
// return args.reverse().reduce(function (acc, fn) {
// return fn(acc);
// }, value);
// };
// }
const compose=(...args)=>value=>args.reverse().reduce((acc,fn)=>fn(acc),value)
const toUpper = (arr) => arr.toUpperCase();
const first = (arr) => arr[0];
const reverse = (arr) => arr.reverse();
const f = compose(toUpper, first, reverse);
console.log(f(["one", "two", "three"]));
- [ ] 为什么使用的是一元函数?
point free (编程风格)
- 概念:把简单运算步骤结合到一起(函数组合),不用到代表数据的参数.具体实现是用到函数组合的概念
functor (函子)
- 概念: 一个特殊的容器,是一个对象,约定具有1个map方法,map方法可以是一个函数对值进行处理
code
class functor {
//静态方法可以直接.调用
//静态方法是属于类的,不属于对象.
//静态方法加载后对象还不一定存在,所以不能使用this调用
static of(value) {
return new functor(value);
}
constructor(value) {
this._value = value;
}
map(fn) {
return functor.of(fn(this._value));
}
}
const fn = functor
.of(5)
.map((x) => x + 2)
.map((x) => x * x);
// const fn = new functor(5).map((x) => x + 2).map((x) => x * x);
console.log(fn);
//Maybe 函子 处理空值
class maybe {
static of(value) {
return new maybe(value);
}
constructor(value) {
this._value = value;
}
map(fn) {
return this.isNothing() ? maybe.of(null) : maybe.of(fn(this._value));
}
isNothing() {
return this._value === null || this._value === undefined;
}
}
let mb = maybe.of(null).map((x) => x.toUpperCase());
console.log(mb);
//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));
}
}
// let rf = right.of(12).map((x) => x + 2);
// let lf = left.of(12).map((x) => x + 2);
// console.log(rf);
// console.log(lf);
function parseJSON(str) {
try {
return right.of(JSON.parse(str));
} catch (error) {
return left.of({ error: error.message });
}
}
let r = parseJSON('{"name":"zs"}').map((x) => x.name.toUpperCase());
console.log(r);
// IO 函子 将不纯的操作包装起来,延迟执行,交给调用者执行 控制副作用 IO函子存在嵌套问题 monad 函子可以解决函子嵌套的问题 monad就是实现了静态的of方法 和join方法的函子
const fp = require("lodash/fp");
const fs = require("fs");
class io {
static of(value) {
return new io(function () {
return value;
});
}
constructor(fn) {
this._value = fn;
}
map(fn) {
return new io(fp.flowRight(fn, this._value));
}
join() {
return this._value();
}
flatMap(fn) {
return this.map(fn).join();
}
}
let p = io.of(process).map((p) => p.execPath);
console.log(p);
console.log(p._value());
let readFile = function (filename) {
return new io(function () {
return fs.readFileSync(filename, "utf-8");
});
};
let print = function (x) {
return new io(function () {
console.log(x);
return x;
});
};
let read = readFile("package.json")
// .map((x) => x.toUpperCase())
.map(fp.toUpper)
.flatMap(print)
.join();
console.log(read);
forktale 库 及task函子 异步处理 pointed函子实现了of 静态方法的函子
code
// folktale 库
// const { compose, curry } = require("folktale/core/lambda");
// const { toUpper, first } = require("lodash/fp");
// let f = curry(2, (x, y) => x + y);
// console.log(f(1,2))
// console.log(f(1)(2))
// let f = compose(toUpper, first);
// console.log(f(["one", "two"]));
//task 函子
const fs = require("fs");
const { task } = require("folktale/concurrency/task");
// const { resolve } = require("path");
// const { reject } = require("lodash");
const { split, find } = require("lodash/fp");
function readFile(fileName) {
return task((resolver) => {
fs.readFile(fileName, "utf-8", (err, data) => {
if (err) resolver.reject(err);
resolver.resolve(data);
});
});
}
readFile("package.json")
.map(split("
"))
.map(find((x) => x.includes("version")))
.run()
.listen({
onRejected: (err) => {
console.log(err);
},
onResolved: (value) => {
console.log(value);
},
});