此文要用到的库和模块:
1: fs: node中用来处理文件的模块
2: lodash库中的fp模块及lodash库的多个函数
3: folktale库中的: compose及task模块
注: 这里有个好东西: nodemon命令, 全局安装,用来取代node命令, 它可以watch磁盘文件并在文件有改变时执行js文件.
一: 高阶函数
1: 函数作为参数
// forEach模拟实现
const arr = [2, 3, 4, 5, 6, 7];
const forEach = (array, fn) => {
for (let i = 0, len = array.length; i < len; i++) {
fn(array[i]);
}
}
// test forEach
forEach(arr, item => {
//依次打印 4 6 8 10 12 14
console.log(item * 2)
})
// filter模拟实现
const filter = (array, fn) => {
let result = [];
for (let i = 0, len = array.length; i < len; i++) {
if (fn(array[i])) {
result.push(array[i])
}
}
return result;
}
// test filter
const dealData = data => data % 2 === 0;
const filterResult = filter(arr, dealData);
console.log(filterResult); // [ 2, 4, 6 ]
// map的模式实现
const map = (array, fn) => {
let result = [];
for (let i = 0, len = array.length; i < len; i++) {
result.push(fn(array[i])) ;
}
return result;
}
// test map
const result3 = map(arr, item => item * 3);
console.log("myself map:",result3); // myself map: [ 6, 9, 12, 15, 18, 21 ]
// some的模拟实现
const some = (array, fn) => {
let result = false;
for (let i = 0, len = array.length; i < len; i++) {
if (fn(array[i])) {
result = true;
break;
}
}
return result;
};
// test some
const someTest = data => data > 10;
const someResult = some(arr, someTest);
console.log("test some:",someResult, "array:", arr); // test some: false array: [ 2, 3, 4, 5, 6, 7 ]
// every的模拟实现
const every = (array, fn) => {
let result = true;
for (let i = 0, len = array.length; i < len; i++) {
if (!fn(array[i])) {
result = false;
break;
}
}
return result;
}
// test every
const everyTest = data => data > 1;
const everyResult = every(arr, everyTest);
console.log("test every:",everyResult, "array:", arr); // test every: true array: [ 2, 3, 4, 5, 6, 7 ]
2: 函数作为返回值
// 执行一次的函数
function once(fn){
let done = true;
return function() {
if (done) {
done = false;
return fn.apply(this, arguments);
}
}
}
// test once
let pay = once(function (data){
console.log(`once test: ${data}`);
});
// 只打印一次: once test: 23
pay(23)
pay(24)
pay(25)
// 缓存函数
function memoize(fn) {
const cache = {};
return function() {
let key = JSON.stringify(arguments);
return cache[key] ? cache[key] : fn.apply(fn, arguments)
}
}
function getArea(r) {
return Math.PI * r * r;
}
// test
const memoizeTest = memoize(getArea)
console.log(memoizeTest(5)) // 78.53981633974483
console.log(memoizeTest(5)) // 78.53981633974483
console.log(memoizeTest(5)) // 78.53981633974483
二: 函数柯里化
定义: 函数式编程中的重要概念,就是把一个多参数函数转化为单参数函数.
使用场景: 思考这样一个问题: 当我们调用一个函数的时候,可能要传递四五个参数,这时候让人头疼的一件事就是查看这个函数的每个参数是什么意思, 如果我们把这个函数转化为只传递一个参数, 一个函数只做一件事,这样是不是可以最大成大的复用这个函数呢.
// lodash中的curry
function getSum(a, b, c) {
return a + b + c;
}
const curryResult = _.curry(getSum);
// test curry
console.log(curryResult(1)(2)(3)); // 6
console.log(curryResult(1,2)(3)); // 6
// 柯里化案例
const isMatchStr = (reg, str) => str.match(reg);
const filterArr = (fn, array) => array.filter(fn);
const match = _.curry(isMatchStr);
const filter2 = _.curry(filterArr);
const hasSpace = match(/\s+/g);
const hasNumber = match(/\d+/g);
const findSpace = filter2(hasSpace);
const arr2 = ["i am jeck", "i_am_rose"];
console.log(filter2(hasSpace)(arr2) ); // [ 'i am jeck' ]
console.log(findSpace(arr2)); // [ 'i am jeck' ]
// 模拟lodash中curry的实现
function curry(func) {
// 返回一个柯里化的函数
return function carriedFn(...args) {
// 判断实参和型参的长度
if (args.length < func.length) {
return function () {
return carriedFn(...args.concat(Array.from(arguments)));
}
}
return func(...args);
}
}
// test
const matchMy = curry(isMatchStr);
const filterMy = curry(filterArr);
const findSpaceMy = filterMy(hasSpace);
console.log("self curry result:", filterMy(hasSpace)(arr2)); // self curry result: [ 'i am jeck' ]
console.log( "self curry result:", findSpaceMy(arr2)); // self curry result: [ 'i am jeck' ]
// 函数组合
function composeSelf(f, g) {
return function (value) {
return f(g(value));
}
};
const reverse = array => array.reverse();
const first = array => array[0];
const emptyUnderline = str => str.replace(/\s+/g, "_");
const last = composeSelf(first, reverse);
console.log(arr, "last=:",last(arr)); // last=: 7
// lodash中的函数组合 _.flowRight()
const toUpper = str => _.toUpper(str);
const f = _.flowRight(toUpper, first);
console.log(f(arr2)); // I AM JECK
// 模拟lodash中的flowRight函数
function flowRight(...args) {
return function(value) {
return args.reverse().reduce((acc, fn) => {
return fn(acc);
}, value)
}
}
// 箭头函数改写
// const flowRight = (...args) => value => args.reverse().reduce((acc, fn) => fn(acc), value);
const f2 = flowRight(emptyUnderline, flowRight(toUpper, first));
console.log(f2(arr2)) // I AM JECK
// 组合函数都是一个个的函数,那如何调试呢,添加一个追踪函数来辅助调试
const trace = _.curry(function (tag, v) {
console.log(tag, v);
return v;
})
const join = _.curry((sep, array) => _.join(array, sep));
const f3 = flowRight(trace("emptyUnderline之后"), emptyUnderline , trace("toUpper之后"), toUpper, first)
console.log(f3(arr2)); // toUpper之后 I AM JECK emptyUnderline之后 I_AM_JECK
// lodash中fp模块对函数组合的封装
const str = "NEVER SAY DIE";
const f4 = fp.flowRight(fp.join("~"), fp.map(fp.toLower), fp.split(" ") );
console.log(f4(str)); // never~say~die
// 把一个字符串中的首字母提取并转换成大写, 使用.作为分隔符
// world wild web => W.W.W
const str2 = "world wild web";
// const firstWordUpper = fp.flowRight(fp.join("."),fp.map(fp.first),fp.map(fp.toUpper),fp.split(" "))
const firstWordUpper = fp.flowRight(fp.join("."),fp.map(fp.flowRight(fp.first, fp.toUpper)),fp.split(" "))
console.log(firstWordUpper(str2)) // W.W.W
三: 函子
1: Meby函子
class Container {
static of(value) {
return new Container(value)
}
constructor(value) {
this._value = value;
}
map(fn) {
return this.isNothing() ? Container.of(null) : Container.of(fn(this._value));
}
// Mybe函子就是对值为null或undefined的处理
isNothing() {
const value = this._value;
return value === null || value === undefined;
}
}
const r = Container.of(str2)
.map(v => v.toUpperCase())
.map(v => v.split(" "))
console.log(r); // Container { _value: [ 'WORLD', 'WILD', 'WEB' ] }
2: Either函字
class Either extends Container {
constructor(left, right) {
// super();
this.left = left;
this.right = right;
}
map(fn) {
return this.right ?
Either.of(this.left, fn(this.right)):
Either.of(fn(this.left), this.right)
}
}
const rEither = Either.of("hello word")
.map(x => x.toUpperCase(), x => x.split(" "))
console.log(rEither); // Container { _value: 'HELLO WORD' }
3: IO函子
class IO {
static of(value) {
return new IO(function () {
return value;
})
}
constructor(fn) {
this._value = fn;
}
map(fn) {
return new this.constructor(fp.flowRight(fn, this._value))
}
}
// 调用
let rIO = IO.of(process).map(p => p.execPath)
// console.log(rIO)
console.log("IO Functor:",rIO._value()); // IO Functor: /usr/local/bin/node
4: Monad函子
class Monad extends IO {
// constructor(fn) {
// super(fn);
// }
join() {
return this._value()
}
flatMap(fn) {
return this.map(fn).join()
}
}
const readFileMonad = fileName => {
return new Monad(function() {
return fs.readFileSync(fileName, "utf-8");
})
}
const printFileMonad = x => {
return new Monad(function(){
// {
// "NAME": "FUNCTIONAL-PROGRAM",
//"VERSION": "1.0.0",
//"MAIN": "INDEX.JS",
//"LICENSE": "MIT",
//"DEVDEPENDENCIES": {
// "FOLKTALE": "^2.3.2",
//"LODASH": "^4.17.20"
//}
//}
console.log(x);
return x;
})
}
const monadResult = readFileMonad("./package.json")
.map(fp.toUpper)
.flatMap(printFileMonad)
.join()
5: folktale库中task处理异步任务
// folktale库: 一个函数式编程库
const folkTaleR = compose(fp.toUpper, fp.first)
console.log(folkTaleR(["one", "two"])); // ONE
// folktale 的task处理异步任务
const readFile = fileName => {
return task(resolver => {
fs.readFile(fileName, "utf-8", (err, data) => {
if (err) {
throw new Error(err)
}
resolver.resolve(data)
})
})
};
readFile("package.json")
.map(v => v.split("\n"))
.map(fp.find(v => v.includes("lodash")))
.run()
.listen({
onRejected: (error) => {
console.log("folktale error: ", error);
},
onResolved: (data) => {
console.log("folktale result: ", data); // folktale result: "lodash": "^4.17.20"
},
})