JS

loadsh中的一些纯函数及JavaScript中的函子

2021-08-24  本文已影响0人  A_走在冷风中

一.loadsh中常见的纯函数

主要有 first / last / toUpper / reverse / each / includes / find / findIndex等

先引入loadsh

const _ = require("lodash");

定义一个数组

const array = ["jack", "tom", "jerry", "kate"];

1. lodash.first()

获取数组中的第一个参数

console.log(_.first(array)); //jack

2. lodash.last()

获取数组中的最后一个参数

console.log(_.last(array)); //kate

3. lodash.toUpper()

将字符串转为大写

console.log(_.toUpper(_.first(array))); //JACK

4. lodash.reverse()

颠倒数组顺序

console.log(_.reverse(array)); //[ 'kate', 'jerry', 'tom', 'jack' ]

5.lodash.each()

循环数组

const r = _.each(array, (item, index) => {
  console.log(item, index);
  //jack 0
  //tom 1
  //jerry 2
  //kate 3
});

console.log(r); //["jack", "tom", "jerry", "kate"]

6. lodash.includes()

判断数组中是否包含某个元素,返回true 或 false

console.log(_.includes(array, "tom")); //true
console.log(_.includes(array, "rose")); //false

7. lodash.find()

根据下标查找数组中的某个元素

console.log(_.find(array, 1)); //jack

8.Loadsh.findIndex()

用于查找元素首次出现的索引。它与indexOf不同,因为它采用了遍历数组每个元素的谓词函数。当元素存在时,返回元素的下标,元素不存在时,返回-1

let index = _.findIndex(
  array,
  (item) => {
    return (item = "tom");
  },
  0
);
console.log(index); //0
let index = _.findIndex(
  array,
  (item) => {
    return (item = "rose");
  },
  0
);
console.log(index); // -1

当元素在某个索引“i”之后被查找时。这里的元素存在于数组中,但输出仍然为-1,因为它存在于索引0中,并且搜索从索引1开始。

let index = _.findIndex(
  array,
  (item) => {
    return (item = "tom");
  },
  1
);
console.log(index); // -1

findIndex() 接收三个参数,第一个参数为要查找的数组,第二个参数为数组遍历后的需要满足的条件,第三个参数为从哪个索引开始查找.
注:第三个参数可以省略,省略时默认从0开始查找

二.Functor(函子)

什么是Functor
案例:
//定义一个容器
 class Container {
   constructor(value) {
     //私有属性
     this._value = value
   }
   map(fn) {
     return new Container(fn(this._value))
   }
 }
 //返回新的函子对象
 let r = new Container(5).map((x) => x + 1).map((x) => x * x)
 console.log(r) //Container { _value: 36 }

此时,每次调用Container,都要通过new方法来进行调用
我们可以通过在class中定义static方法来进行改造:

class Container {
  constructor(value) {
    //私有属性
    this._value = value
  }
  map(fn) {
    return Container.of(fn(this._value))
  }
  //定义静态方法
  static of(value) {
    return new Container(value)
  }
}

如此一来,我们就可以通过函数调用来调用Container类

let r = Container.of(5)
  .map((x) => x + 2)
  .map((x) => x * x)

console.log(r) //Container { _value: 49 }
总结:

JavaScript中常用的函子

1.MayBe函子:
//定义一个MayBe函子
class MayBe {

  static of(value) {
    return new MayBe(value)
  }

  constructor(value) {
    this._value = value
  }
  map(fn) {
    //参数为null时,返回value 为 null
    return this.isNothing() ? MayBe.of(null) : MayBe.of(fn(this._value))
  }
  //判断参数是否为null
  isNothing() {
    return this._value === null || this._value === undefined
  }
}

正常调用:

let r = MayBe.of('hello world').map(x => x.toUpperCase())
console.log(r) //MayBe { _value: 'HELLO WORLD' }

传入null时不会报错

let r2 = MayBe.of(null).map(x => x.toUpperCase())
console.log(r2) //MayBe { _value: null }

如果map中间有好几步,最后返回null,但是并不知道那一步出现了问题

let r3 = MayBe.of('hello world')
  .map(x => x.toUpperCase())
  .map(x => null)
  .map(x => x.splice(''))

console.log(r3) //MayBe { _value: null }

为解决这个问题, 需要看下一个函数

2.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 r1 = left.of(12).map(x => x + 2)
let r2 = Right.of(12).map(x => x + 2)
console.log(r1) //left { _value: 12 }
console.log(r2) //left { _value: 14 }

为什么返回的结果会不一样呢,因为left返回的是this(当前对象),并没有调用fn
那么如何处理异常呢 ?
我们定义一个字符串转换成对象的函数

const parseJson = (str) => {
  //对于可能出现错误的环节使用 try catch
  //正确的情况使用Right函子
  try {
    return Right.of(JSON.parse(str))
  } catch (e) {
    //错误的情况使用left函子,并返回错误信息
    return left.of({ err: e.message })
  }
}

let error = parseJson('{name:zs}')
console.log(error) //left { _value: { err: 'Unexpected token n in JSON at position 1' } }
let success = parseJson('{"name":"zs"}')
console.log(success) //Right { _value: { name: 'zs' } }

console.log(success.map(x => x.name.toUpperCase())) //Right { _value: 'ZS' }
3. IO函子
const fp = require('lodash/fp')

class IO {

  //of方法快速创建一个值返回一个函数,将来需要的时候再调用函数
  static of(value) {
    return new IO(() => value)
  }

  //传入的是一个函数
  constructor(fn) {
    this._value = fn
  }

  map(fn) {
    //这里用的是new一个新的构造函数,是为了把当前_value的函数和map传入的fn进行组合成新的函数
    return new IO(fp.flowRight(fn, this._value))
  }
}

node执行环境可以传入一个process对象(进程)
调用of的时候把当前取值的过程包装到函数里面,再在需要的时候再获取process

const R = IO.of(process)
  //map需要传入一个函数,函数需要接受一个参数,这个参数就是of中传递的参数process
  //返回一下process中的execPath属性,即当前执行路径
  .map(p => p.execPath)

console.log(R) //IO { _value: [Function (anonymous)] }

//上面返回的是一个函数,如需获取路径可执行下面的代码
console.log(R._value()) //C:\Program Files\New Folder\node.exe
4.task函子
const { task } = require('folktale/concurrency/task')
const fs = require('fs')
const { split, find } = require('lodash/fp')
//读取文件
function readFile(fileName) {
  //task传递一个函数,参数是resolver
  return task(resolver => {
    //node中读取文件,第一个参数是文件路径,第二个参数是编码方式,第三个参数是回调(错误在前面)
    fs.readFile(fileName, 'utf-8', (err, data) => {
      if (err) {
        resolver.reject(err)
      } else {
        resolver.resolve(data)
      }
    })
  })
}

readFile('package.json')
  .map(split('\n'))
  .map(find(x => x.includes('version')))
  .run()
  .listen({
    onRejected: err => {
      console.log(err)
    },
    onResolved: value => {
      console.log(value)
    }
  }) //"version": "1.0.0",
5.monad函子
const fp = require('lodash/fp')
const fs = require('fs')

class IO {
  //of方法快速创建一个值返回一个函数,将来需要的时候再调用函数
  static of(value) {
    return new IO(() => value)
  }

  //传入的是一个函数
  constructor(fn) {
    this._value = fn
  }

  map(fn) {
    //这里用的是new一个新的构造函数,是为了把当前_value的函数和map传入的fn进行组合成新的函数
    return new IO(fp.flowRight(fn, this._value))
  }
}

//读取文件
let readFile = (fileName) => {
  return new IO(() => {
    return fs.readFileSync(fileName, 'utf-8')
  })
}

//打印函数
let print = (value) => {
  return new IO(() => {
    console.log(value)
    return value
  })
}

//组合函数,先读文件再打印
let cat = fp.flowRight(print, readFile)
let r = cat('package.json')
//拿到的是嵌套的IO函子 IO(IO())
console.log(r) //IO { _value: [Function (anonymous)] }

console.log(r._value()._value())
/**
 * {
  "name": "pd_funxtor",
  "version": "1.0.0",
  "description": "",
  "main": "either.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "folktale": "^2.3.2",
    "lodash": "^4.17.21"
  }
 }
 */

上面遇到多个IO函子嵌套的时候,_value就会调用很多次,这样的调用体验很不好,所以进行优化

什么是Monad函子
实现一个Monad函子

实际开发中 不会这么难,主要是知道Monad的实现

const fp = require('lodash/fp')
const fs = require('fs')

class IO {
  //of方法快速创建一个值返回一个函数,将来需要的时候再调用函数
  static of(value) {
    return new IO(() => value)
  }

  //传入的是一个函数
  constructor(fn) {
    this._value = fn
  }
  map(fn) {
    //这里用的是new一个新的构造函数,是为了把当前_value的函数和map传入的fn进行组合成新的函数
    return new IO(fp.flowRight(fn, this._value))
  }
  join() {
    return this._value()
  }
  flatMap(fn) {
    return this.map(fn).join()
  }
}

//读取文件
let readFile = (fileName) => {
  return new IO(() => {
    return fs.readFileSync(fileName, 'utf-8')
  })
}

//打印函数
let print = (value) => {
  return new IO(() => {
    console.log(value)
    return value
  })
}

let r = readFile('package.json')
  .flatMap(print)
  .join()
console.log(r)
//执行顺序
/**
 * readFile读取了文件,然后返回了一个 IO函子
 * 调用flatMap使用readFile返回的IO函子调用的
 * 并且传入了一个print函数
 * 调用flatMap的时候 内部先调用map,当前的print和this._value进行合并,返回一个新的函子
 * flatMap中的map函数执行完,print函数返回的一个IO函子,里面包裹的还是一个IO函子
 * 下面调用join函数,返回内部的this._value
 * 这个this._value就是之前print和this._value的组合函数,调用之后返回的就是print的返回结果
 * 所以flatMap执行完毕之后,返回的就是print函数返回的IO函子
 */
上一篇下一篇

猜你喜欢

热点阅读