此文要用到的库和模块:

1: fs: node中用来处理文件的模块
2: lodash库中的fp模块及lodash库的多个函数
3: folktale库中的: compose及task模块
注: 这里有个好东西: nodemon命令, 全局安装,用来取代node命令, 它可以watch磁盘文件并在文件有改变时执行js文件.

一: 高阶函数

1: 函数作为参数

  1. // forEach模拟实现
  2. const arr = [2, 3, 4, 5, 6, 7];
  3. const forEach = (array, fn) => {
  4. for (let i = 0, len = array.length; i < len; i++) {
  5. fn(array[i]);
  6. }
  7. }
  8. // test forEach
  9. forEach(arr, item => {
  10. //依次打印 4 6 8 10 12 14
  11. console.log(item * 2)
  12. })
  13. // filter模拟实现
  14. const filter = (array, fn) => {
  15. let result = [];
  16. for (let i = 0, len = array.length; i < len; i++) {
  17. if (fn(array[i])) {
  18. result.push(array[i])
  19. }
  20. }
  21. return result;
  22. }
  23. // test filter
  24. const dealData = data => data % 2 === 0;
  25. const filterResult = filter(arr, dealData);
  26. console.log(filterResult); // [ 2, 4, 6 ]
  27. // map的模式实现
  28. const map = (array, fn) => {
  29. let result = [];
  30. for (let i = 0, len = array.length; i < len; i++) {
  31. result.push(fn(array[i])) ;
  32. }
  33. return result;
  34. }
  35. // test map
  36. const result3 = map(arr, item => item * 3);
  37. console.log("myself map:",result3); // myself map: [ 6, 9, 12, 15, 18, 21 ]
  38. // some的模拟实现
  39. const some = (array, fn) => {
  40. let result = false;
  41. for (let i = 0, len = array.length; i < len; i++) {
  42. if (fn(array[i])) {
  43. result = true;
  44. break;
  45. }
  46. }
  47. return result;
  48. };
  49. // test some
  50. const someTest = data => data > 10;
  51. const someResult = some(arr, someTest);
  52. console.log("test some:",someResult, "array:", arr); // test some: false array: [ 2, 3, 4, 5, 6, 7 ]
  53. // every的模拟实现
  54. const every = (array, fn) => {
  55. let result = true;
  56. for (let i = 0, len = array.length; i < len; i++) {
  57. if (!fn(array[i])) {
  58. result = false;
  59. break;
  60. }
  61. }
  62. return result;
  63. }
  64. // test every
  65. const everyTest = data => data > 1;
  66. const everyResult = every(arr, everyTest);
  67. console.log("test every:",everyResult, "array:", arr); // test every: true array: [ 2, 3, 4, 5, 6, 7 ]

2: 函数作为返回值

  1. // 执行一次的函数
  2. function once(fn){
  3. let done = true;
  4. return function() {
  5. if (done) {
  6. done = false;
  7. return fn.apply(this, arguments);
  8. }
  9. }
  10. }
  11. // test once
  12. let pay = once(function (data){
  13. console.log(`once test: ${data}`);
  14. });
  15. // 只打印一次: once test: 23
  16. pay(23)
  17. pay(24)
  18. pay(25)
  19. // 缓存函数
  20. function memoize(fn) {
  21. const cache = {};
  22. return function() {
  23. let key = JSON.stringify(arguments);
  24. return cache[key] ? cache[key] : fn.apply(fn, arguments)
  25. }
  26. }
  27. function getArea(r) {
  28. return Math.PI * r * r;
  29. }
  30. // test
  31. const memoizeTest = memoize(getArea)
  32. console.log(memoizeTest(5)) // 78.53981633974483
  33. console.log(memoizeTest(5)) // 78.53981633974483
  34. console.log(memoizeTest(5)) // 78.53981633974483

二: 函数柯里化

定义: 函数式编程中的重要概念,就是把一个多参数函数转化为单参数函数.
使用场景: 思考这样一个问题: 当我们调用一个函数的时候,可能要传递四五个参数,这时候让人头疼的一件事就是查看这个函数的每个参数是什么意思, 如果我们把这个函数转化为只传递一个参数, 一个函数只做一件事,这样是不是可以最大成大的复用这个函数呢.

  1. // lodash中的curry
  2. function getSum(a, b, c) {
  3. return a + b + c;
  4. }
  5. const curryResult = _.curry(getSum);
  6. // test curry
  7. console.log(curryResult(1)(2)(3)); // 6
  8. console.log(curryResult(1,2)(3)); // 6
  9. // 柯里化案例
  10. const isMatchStr = (reg, str) => str.match(reg);
  11. const filterArr = (fn, array) => array.filter(fn);
  12. const match = _.curry(isMatchStr);
  13. const filter2 = _.curry(filterArr);
  14. const hasSpace = match(/\s+/g);
  15. const hasNumber = match(/\d+/g);
  16. const findSpace = filter2(hasSpace);
  17. const arr2 = ["i am jeck", "i_am_rose"];
  18. console.log(filter2(hasSpace)(arr2) ); // [ 'i am jeck' ]
  19. console.log(findSpace(arr2)); // [ 'i am jeck' ]
  20. // 模拟lodash中curry的实现
  21. function curry(func) {
  22. // 返回一个柯里化的函数
  23. return function carriedFn(...args) {
  24. // 判断实参和型参的长度
  25. if (args.length < func.length) {
  26. return function () {
  27. return carriedFn(...args.concat(Array.from(arguments)));
  28. }
  29. }
  30. return func(...args);
  31. }
  32. }
  33. // test
  34. const matchMy = curry(isMatchStr);
  35. const filterMy = curry(filterArr);
  36. const findSpaceMy = filterMy(hasSpace);
  37. console.log("self curry result:", filterMy(hasSpace)(arr2)); // self curry result: [ 'i am jeck' ]
  38. console.log( "self curry result:", findSpaceMy(arr2)); // self curry result: [ 'i am jeck' ]
  39. // 函数组合
  40. function composeSelf(f, g) {
  41. return function (value) {
  42. return f(g(value));
  43. }
  44. };
  45. const reverse = array => array.reverse();
  46. const first = array => array[0];
  47. const emptyUnderline = str => str.replace(/\s+/g, "_");
  48. const last = composeSelf(first, reverse);
  49. console.log(arr, "last=:",last(arr)); // last=: 7
  50. // lodash中的函数组合 _.flowRight()
  51. const toUpper = str => _.toUpper(str);
  52. const f = _.flowRight(toUpper, first);
  53. console.log(f(arr2)); // I AM JECK
  54. // 模拟lodash中的flowRight函数
  55. function flowRight(...args) {
  56. return function(value) {
  57. return args.reverse().reduce((acc, fn) => {
  58. return fn(acc);
  59. }, value)
  60. }
  61. }
  62. // 箭头函数改写
  63. // const flowRight = (...args) => value => args.reverse().reduce((acc, fn) => fn(acc), value);
  64. const f2 = flowRight(emptyUnderline, flowRight(toUpper, first));
  65. console.log(f2(arr2)) // I AM JECK
  66. // 组合函数都是一个个的函数,那如何调试呢,添加一个追踪函数来辅助调试
  67. const trace = _.curry(function (tag, v) {
  68. console.log(tag, v);
  69. return v;
  70. })
  71. const join = _.curry((sep, array) => _.join(array, sep));
  72. const f3 = flowRight(trace("emptyUnderline之后"), emptyUnderline , trace("toUpper之后"), toUpper, first)
  73. console.log(f3(arr2)); // toUpper之后 I AM JECK emptyUnderline之后 I_AM_JECK
  74. // lodash中fp模块对函数组合的封装
  75. const str = "NEVER SAY DIE";
  76. const f4 = fp.flowRight(fp.join("~"), fp.map(fp.toLower), fp.split(" ") );
  77. console.log(f4(str)); // never~say~die
  78. // 把一个字符串中的首字母提取并转换成大写, 使用.作为分隔符
  79. // world wild web => W.W.W
  80. const str2 = "world wild web";
  81. // const firstWordUpper = fp.flowRight(fp.join("."),fp.map(fp.first),fp.map(fp.toUpper),fp.split(" "))
  82. const firstWordUpper = fp.flowRight(fp.join("."),fp.map(fp.flowRight(fp.first, fp.toUpper)),fp.split(" "))
  83. console.log(firstWordUpper(str2)) // W.W.W

三: 函子

1: Meby函子

  1. class Container {
  2. static of(value) {
  3. return new Container(value)
  4. }
  5. constructor(value) {
  6. this._value = value;
  7. }
  8. map(fn) {
  9. return this.isNothing() ? Container.of(null) : Container.of(fn(this._value));
  10. }
  11. // Mybe函子就是对值为null或undefined的处理
  12. isNothing() {
  13. const value = this._value;
  14. return value === null || value === undefined;
  15. }
  16. }
  17. const r = Container.of(str2)
  18. .map(v => v.toUpperCase())
  19. .map(v => v.split(" "))
  20. console.log(r); // Container { _value: [ 'WORLD', 'WILD', 'WEB' ] }

2: Either函字

  1. class Either extends Container {
  2. constructor(left, right) {
  3. // super();
  4. this.left = left;
  5. this.right = right;
  6. }
  7. map(fn) {
  8. return this.right ?
  9. Either.of(this.left, fn(this.right)):
  10. Either.of(fn(this.left), this.right)
  11. }
  12. }
  13. const rEither = Either.of("hello word")
  14. .map(x => x.toUpperCase(), x => x.split(" "))
  15. console.log(rEither); // Container { _value: 'HELLO WORD' }

3: IO函子

  1. class IO {
  2. static of(value) {
  3. return new IO(function () {
  4. return value;
  5. })
  6. }
  7. constructor(fn) {
  8. this._value = fn;
  9. }
  10. map(fn) {
  11. return new this.constructor(fp.flowRight(fn, this._value))
  12. }
  13. }
  14. // 调用
  15. let rIO = IO.of(process).map(p => p.execPath)
  16. // console.log(rIO)
  17. console.log("IO Functor:",rIO._value()); // IO Functor: /usr/local/bin/node

4: Monad函子

  1. class Monad extends IO {
  2. // constructor(fn) {
  3. // super(fn);
  4. // }
  5. join() {
  6. return this._value()
  7. }
  8. flatMap(fn) {
  9. return this.map(fn).join()
  10. }
  11. }
  12. const readFileMonad = fileName => {
  13. return new Monad(function() {
  14. return fs.readFileSync(fileName, "utf-8");
  15. })
  16. }
  17. const printFileMonad = x => {
  18. return new Monad(function(){
  19. // {
  20. // "NAME": "FUNCTIONAL-PROGRAM",
  21. //"VERSION": "1.0.0",
  22. //"MAIN": "INDEX.JS",
  23. //"LICENSE": "MIT",
  24. //"DEVDEPENDENCIES": {
  25. // "FOLKTALE": "^2.3.2",
  26. //"LODASH": "^4.17.20"
  27. //}
  28. //}
  29. console.log(x);
  30. return x;
  31. })
  32. }
  33. const monadResult = readFileMonad("./package.json")
  34. .map(fp.toUpper)
  35. .flatMap(printFileMonad)
  36. .join()

5: folktale库中task处理异步任务

  1. // folktale库: 一个函数式编程库
  2. const folkTaleR = compose(fp.toUpper, fp.first)
  3. console.log(folkTaleR(["one", "two"])); // ONE
  4. // folktale 的task处理异步任务
  5. const readFile = fileName => {
  6. return task(resolver => {
  7. fs.readFile(fileName, "utf-8", (err, data) => {
  8. if (err) {
  9. throw new Error(err)
  10. }
  11. resolver.resolve(data)
  12. })
  13. })
  14. };
  15. readFile("package.json")
  16. .map(v => v.split("\n"))
  17. .map(fp.find(v => v.includes("lodash")))
  18. .run()
  19. .listen({
  20. onRejected: (error) => {
  21. console.log("folktale error: ", error);
  22. },
  23. onResolved: (data) => {
  24. console.log("folktale result: ", data); // folktale result: "lodash": "^4.17.20"
  25. },
  26. })