此文要用到的库和模块:
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 forEachforEach(arr, item => {//依次打印 4 6 8 10 12 14console.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 filterconst 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 mapconst 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 someconst 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 everyconst 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 oncelet pay = once(function (data){console.log(`once test: ${data}`);});// 只打印一次: once test: 23pay(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;}// testconst memoizeTest = memoize(getArea)console.log(memoizeTest(5)) // 78.53981633974483console.log(memoizeTest(5)) // 78.53981633974483console.log(memoizeTest(5)) // 78.53981633974483
二: 函数柯里化
定义: 函数式编程中的重要概念,就是把一个多参数函数转化为单参数函数.
使用场景: 思考这样一个问题: 当我们调用一个函数的时候,可能要传递四五个参数,这时候让人头疼的一件事就是查看这个函数的每个参数是什么意思, 如果我们把这个函数转化为只传递一个参数, 一个函数只做一件事,这样是不是可以最大成大的复用这个函数呢.
// lodash中的curryfunction getSum(a, b, c) {return a + b + c;}const curryResult = _.curry(getSum);// test curryconsole.log(curryResult(1)(2)(3)); // 6console.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);}}// testconst 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.Wconst 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"},})
