前端函数式编程思想


我与成长,至死方休~

在这里插入图片描述

一、编程思想

函数式编程(Functional Programming, FP),FP 是编程范式之一,我们常听说的编程范式还有面向过程编程面向对象编程

  • 面向对象编程:把现实世界中的事物抽象成程序世界中的类和对象,通过封装、继承和多态来演示事物事件的联系(对事物本身进行抽象)
  • 函数式编程:把现实世界的事物和事物之间的联系抽象到程序世界(对运算过程进行抽
    象)
// 非函数式
let num1 = 2
let num2 = 3
let sum = num1 + num2
console.log(sum)

// 函数式
function add (n1, n2) {
	return n1 + n2
}
let sum = add(2, 3)
console.log(sum)


二、函数是一等公民

在 JavaScript 中,函数就是一个普通的对象,函数可以被储存到对象/数组中,也可以作为另一个函数的参数/返回值,也可以通过new Function()的方式来构造一个新的函数。

  • 函数可以储存在变量
  • 函数可以作为参数
  • 函数可以作为返回值

1. 储存在变量中:

const fn = function(){
	console.log('Hello, First class function')
}
fn()

2. 函数作为参数:

// 实现 filter
function filter(array, fn) { // 数组过滤
  let results = []
  for (let i = 0; i < array.length; i++) {
    if (fn(array[i])) {
      results.push(array[i])
    }
  }
  return results
}

let arr2 = [1, 3, 4, 5, 6, 8, 9]
let r = filter(arr2, function (item) {
  return item % 2 === 0
})
console.log(r)

3. 函数作为返回值:

// 实现 once
function once(fn) { // 只执行一次
  let done = false
  return function () {
    if (!done) {
      done = true
      fn.apply(this, arguments)
    }
  }
}
let pay = once(function (money) {
  console.log(`请支付${money}RMB`)
})
pay(5)
pay(5)
pay(5)


三、高阶函数

1. 什么是高阶函数

  • 函数可以作为参数传递给另一个函数
  • 函数可以作为另一个函数的返回值

2. 高阶函数的意义

  • 抽象通用的问题
  • 屏蔽过程,开发者只需要关注结果
let array = [1, 2, 3, 4]

// 面向过程的方式
for (let i = 0; i < array.length; i++) {  // 遍历
	console.log(array[i])
}

// 高阶高阶函数
forEach(array, item => { // 遍历
	console.log(item)
})

3. 常用的高阶函数

forEach、map、filter、every、some、find/findIndex、reduce、sort…


4. 实现高阶函数

let array = [1, 2, 3, 4]

const map = (arr, fn) => { // map
  let result = []
  for (let val of arr) {
    result.push(fn(val))
  }
  return result
}
// 测试
// let res1 = map(array, (v) => { // v的平方
//   return v * v
// })
let res1 = map(array, v => v * v) // 箭头函数的优雅简化
console.log('res1 =>', res1)
// res1 => [ 1, 4, 9, 16 ]


const every = (arr, fn) => { // every
  let result1 = true
  for (let val of arr) {
    // let result1 = fn(val) // 会出现暂时性死区,导致外部无法访问
    result1 = fn(val) // => var result1 = fn(val)
    if (!result1) {
      break
    }
  }
  return result1
}
// 测试
let res2 = every(array, v => v > 2)
console.log('res2 =>', res2)
// res2 => false


const some = (arr, fn) => { // some
  let result = false
  for(let val of arr) {
    result = fn(val)
    if(result) break
  }
  return result
}

// 测试
let res3 = some(array, v => v > 2)
console.log('res3 =>', res3)
// res3 => true


四、闭包

1. 概念

一个函数包裹了另一个函数,被包裹的内部函数可以访问外部函数的作用域成员;
并且其他函数可以访问这个函数的内部函数,和其作用域成员

本质: 函数在执行的过程中,会在执行栈上压入、移出,但是堆上的作用域成员因为被内部函数应用导致不能释放,因此内部函数可以访问到外部函数的作用域成员


2. 案例

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
</body>
<script>
  // 计算员工工资: 工资 = 绩效工资 + 职级工资
  function makeSalary(performance) { // 闭包
    return function (base) {
      return base + performance
    }
  }

  const salaryP5 = makeSalary(5000) // P5 的职级工资
  const salaryP6 = makeSalary(7000) // P6

  console.log(salaryP5(5000))
  console.log(salaryP5(10000))
  console.log(salaryP6(10000))
</script>
</html>

3. F12(闭包提示、调用栈、作用域)

在这里插入图片描述


五、纯函数

1. 概念

相同的输入永远会得到相同的输出,而且没有任何可观察的副作用


纯函数和不纯函数:

let arr = [1, 2, 3, 4, 5]

// slice
console.log(arr.slice())
console.log(arr.slice())
console.log(arr.slice())
// => [ 1, 2, 3, 4, 5 ]
// => [ 1, 2, 3, 4, 5 ]
// => [ 1, 2, 3, 4, 5 ]
// => 纯函数:相同的输入永远得到相同的数据(slice 不会改变原数组)

// splice
console.log(arr.splice(0, 3))
console.log(arr.splice(0, 3))
console.log(arr.splice(0, 3))
// => [ 1, 2, 3 ]
// => [ 4, 5 ]
// => []
// => 不纯函数:相同的输入没有得到相同的数据( splice 会改变原数组)
  • 函数式编程不会保留计算中间的结果,所以变量是不可变的(无状态的)
  • 可以把一个函数的执行结果交给另一个函数去处理

2. 纯函数的优势

  • 可缓存: 减少性能损耗
  • 可测试: 相同输入总是有相同输出
  • 安全并行处理: 纯函数只依赖于参数,不需要访问共享数据,并行环境下可以任意运行 - Web Worker

// 缓存 memoize
const _ = require('lodash')

function getArea(r) {
  console.log('memoize')
  return Math.PI * r * r
}

let getAreaMemoize = _.memoize(getArea)
console.log(getAreaMemoize(4))
console.log(getAreaMemoize(4))
// => memoize
// => 50.26548245743669
// => 50.26548245743669


// 通过闭包实现 memoize
function memoize(r) {
  let cache = {}
  return function () {
    let key = JSON.stringify(arguments) // 数组转字符串
    // apply 的第一个参数修改 this 指向,第二个参数用于结构 arguments
    return cache[key] = cache[key] || r.apply(r, arguments)
  }
}

let getAreaMemoize2 = memoize(getArea)
console.log(getAreaMemoize2(4))
console.log(getAreaMemoize2(4))
// => memoize
// => 50.26548245743669
// => 50.26548245743669

3. 纯函数的副作用

纯函数是相同的输入总会有相同的输出,而副作用就会导致纯函数‘不纯’

// 如果函数依赖于外部的状态就无法保证输出相同,就会带来副作用
let mini = 18
function checkAge (age) {
	return age >= mini
}

soncole.log(checkAge(20))
// => true
let mini = 30
soncole.log(checkAge(20))
// => false

所有的外部交互都有可能带来副作用,副作用也使得方法通用性下降不适合扩展和可重用性,同时副作用会给程序中带来安全隐患给程序带来不确定性,但是副作用不可能完全禁止,尽可能控制它们在可控范围内发生。



六、Lodash

1. 初识 lodash

Lodash 是一个一致性、模块化、高性能的 JavaScript 实用工具库

lodash 官网

安装:

// 初始化 pakeage.json
npm init -y
//  安装
npm i --save lodash

2. 应用

// first / last / toUpper / reverse / each / includes / find / findIndex
const _ = require('lodash')

let arr = ['jack', 'tom', 'lucy', 'am']


// first
console.log(_.first(arr))
// => jack
console.log(_.last(arr))
// => am
console.log(_.toUpper(_.first(arr)))
// => JACK
console.log(_.reverse(arr))
console.log(_.reverse(arr))
// => [ 'am', 'lucy', 'tom', 'jack' ]
// => [ 'jack', 'tom', 'lucy', 'am' ]


// 相同的输入并没有相同的输出(改变了原数组),所以 lodash 并不是纯函数(lodash-fp 是纯函数)


七、柯里化

1. 什么是柯里化

  • 当一个函数有多个参数的时候先传递一部分参数调用它(这部分参数以后永远不变)
  • 然后返回一个新的函数接收剩余的参数,返回结果
// 示例
function checkAge(age) {
  let mini = 18
  return age >= mini
}
console.log(checkAge(20))
// => true

// 普通函数
function checkAge2(mini, age) {
  return age >=mini
}
console.log(checkAge2(18, 20))
// => true

// 柯里化(通过闭包来实现)
// function checkAge3(mini) {
//   return function(age) {
//     return age >= mini
//   }
// }

// 箭头函数简化
const checkAge3 = mini => (age => age >= mini)

let checkAge18 = checkAge3(18)
console.log(checkAge18(20))
// => true

2. lodash中的柯里化

_.curry(func)

  • 功能: 创建一个函数,该函数接收一个或多个 func 的参数,如果 func 所需要的参数都被提供则执行 func 并返回执行的结果。否则继续返回该函数并等待接收剩余的参数。
  • 参数: 需要柯里化的函数
  • 返回值: 柯里化后的函数
// lodash 中 curry 的基本使用
const _ = require('lodash')

function getSum(a, b, c) {
  return a + b + c
}

// 三元函数转换成一元函数
const getSumCurry = _.curry(getSum)

console.log(getSumCurry(1, 2, 3))
// => 6

console.log(getSumCurry(1)(2)(3))
// => 6

console.log(getSumCurry(1, 2)(3))
// =. 6

3. 柯里化案例

// 柯里化案例
const _ = require('lodash')

// 提取 全局中 全部的 空白字符,并返回一个数据
// 正则 ''.match(/\s+/g)

const match = _.curry(function (reg, str) {
  return str.match(reg)
})
const haveSpace = match(/\s+/g) // /\d+/g  匹配数字

console.log(haveSpace(' hello world '))
// => [ ' ', ' ', ' ' ]
console.log(haveSpace('helloWorld'))
// => null


// 过滤含有空白字符的数组成员
const filter = _.curry(function(fn, array) {
  return array.filter(fn)
})

console.log(filter(haveSpace)(['hello world', 'helloWorld']))
// => [ 'hello world' ]

4. 柯里化原理模拟

// 模拟 lodash 的 curry 实现原理

// lodash 的 柯里化方法
const _ = require('lodash')

function getSum(a, b, c) {
  return a + b + c
}

const curried = _.curry(getSum)

console.log(curried(1)(2, 3))
// => 6


// 自己来模拟 _curry 方法
function curry2(fn) {
  return function curriedFn(...args) { // 返回一个新的函数
    if (args.length < fn.length) { // 形参和实参对比
      return function () {
        return curriedFn(...args.concat(Array.from(arguments)))
        // => return curriedFn(...args.concat(...arguments))
      }
    }
    return fn(...args)
  }
}

const curried2 = curry2(getSum)
console.log(curried2(1)(2, 3))

5. 柯里化总结

  • 柯里化可以让我们给一个函数传递较少的参数得到一个已经记住了某些固定参数的新函数
  • 这是一种对函数参数的’缓存’
  • 让函数变的更灵活,让函数的粒度更小
  • 可以把多元函数转换成一元函数,可以组合使用函数产生强大的功能


八、组合函数

1. 概念

函数组合可以让我们把细粒度的函数重新组合生成一个新的函数

  • 如果一个函数要经过多个函数处理才能得到最终值,这个时候可以把中间过程的函数合并成一个函数
  • 函数就像是数据的管道,函数组合就是把这些管道连接起来,让数据穿过多个管道形成最终结果
  • 函数组合默认是从右到左执行
// 组合函数
let str = "a, b, c, d"

function compose(f, g) { // 组合函数的方法
  return function (val) {
    return f(g(val))
  }
}

function reverse(arr) { // 翻转数组
  return arr.reverse()
}

function first(arr) { // 第一个元素
  return arr[0]
}

function toUpper(val) { // 大写字母
  return val.toUpperCase()
}

const last = compose(toUpper, first, reverse)
console.log(last(str))
// => 4

2. lodash 中的组合函数

  • lodash 中组合函数 flow() 或者 flowRight(),他们都可以组合多个函数
  • flow() 是从左到右运行
  • flowRight() 是从右到左运行,使用的更多一些
// lodash
const _ = require('lodash')

const reverse = v => v.reverse()
const first  = v => v[0]
const upper = v => v.toUpperCase()
const lastUpper = _.flowRight(upper, first, reverse)

let str = ['one', 'two', "three"]
console.log(lastUpper(str))
// => THREE

3. 模拟组合函数 实现原理

const _ = require('lodash')

// 模拟 flowRight 方法
function flowRight1(...arg) {
  return function(val) { // 接收传入的参数
    return arg.reverse().reduce(function (acc, fn) { // reverse 翻转 /  reduce 将处理的值汇总
      return fn(acc) // 每个方法对值进行处理
    }, val) // val => acc 的初始值
  }
}

// 箭头函数简化
const flowRight2 = (...arg) => (val) => arg.reverse().reduce((acc, fn) => fn(acc), val)

const lastUpper1 = flowRight1(_.toUpper, _.first, _.reverse)
let str1 = ['one', 'two', "three"]
console.log(lastUpper1(str1))
// => THREE

let str2 = ['one', 'two', "three"]
const lastUpper2 = flowRight2(_.toUpper, _.first, _.reverse)
console.log(lastUpper2(str2))
// => THREE

4. 组合函数 调试

const _ = require('lodash')

// const log = (val) => { // 调试工具
//   console.log(val) // 打印传入的参数
//   return val // 将参数传出(供下个方法调用)
// }

const trace = _.curry((tag, val) => { // 跟踪
  console.log(tag, val) // 返回标记内容
  return val // 传出参数(供下个方法调用)
})

// NEVER GIVE UP => never-give-up
// 思路: 第一步先切割字符串,第二步转换成小写,第三步用'-'拼接

// split 切割
const split = _.curry((sep, str) => _.split(str, sep))

// _.toLower 小写(根据调试,返回的是字符串,所以需要用 map 进行处理)
const map = _.curry((fn, str) => _.map(str, fn))

//  join 拼接
const join = _.curry((sep, str) => _.join(str, sep))

const fn = _.flowRight(join('-'), trace('map 之后'), map(_.toLower), trace('split 之后'), split(' '))


let str = "NEVER GIVE UP"
console.log(fn(str))

5. lodash_FP 模块

  • lodash 的 fp 模块提供了实用的对函数式编程友好的方法( lodash_FP 也是指函数式编程的 lodash 工具)
  • 提供了不可变 auto-curried iteratee-first data-last 的方法(函数优先,数据滞后
⑴. 函数优先,数据滞后
// lodash
const _ = require('lodash')
_.map(['a', 'b', 'c'], _.toUpper)
// => ['A', 'B', 'C']

// lodash/fp 模块
const fp = require('lodash/fp')
fp.map(fp.toUpper, ['a', 'b', 'c'])
// => ['A', 'B', 'C']

⑵. 自带柯里化属性(纯函数)
// NEVER GIVE UP => never-give-up

const fp = require('lodash/fp')
const f = fp.flowRight(fp.join('-'), fp.map(_.toLower), fp.split(' '))
console.log(f('NEVER SAY DIE'))
// => never-give-up

⑶. lodash 中 map 方法的小问题
// lodash map 方法的问题
const _ = require('lodash')

console.log(_.map([12, 11, 10], parseInt))
// => [ 12, NaN, 2 ]

// parseInt 回接收三个参数, 第一个是当前元素, 第二个是元素下标, 第三个是该数组
// parseInt('12', '0', [ 12, 11, 10 ]) => 12 的 0 进制, 会被理解为 10 进制 => 12
// parseInt('11', '1', [ 12, 11, 10 ]) => 11 的 1 进制, 会无法计算 => NaN
// parseInt('10', '2', [ 12, 11, 10 ]) => 10 的 2 进制 => 2


const fp = require('lodash/fp')

// fp 的 map 方法, 只会接收一个参数
console.log(fp.map(parseInt, [12, 11, 10]))
// => [ 12, 11, 10 ]

6. Point Free

可以把数据处理的过程定义成与数据无关的合成运算,不需要用到代表数据的那个参数,只要把简单的运算步骤合成到一起,在使用这种模式之前我们需要定义一些辅助的基本运算函数 (一种组合函数的编程思维)

// Hello World => hello_world

// 非 Point Free 模式
function f (word) {
	return word.toLowerCase().replace(/\s+/g, '_');
}
// => hello_world

// Point Free
const fp = require('lodash/fp')
const f = fp.flowRight(fp.replace(/\s+/g, '_'), fp.toLower)
console.log(f('Hello World'))
// => hello_world

案例:

const fp = require('lodash/fp')

// world will web => W. W. W.
// 思路: 空格切割 => 首字母 => 大写 => '.'拼接

const firstLetterToUpper = fp.flowRight(fp.join('. '), fp.map(fp.toUpper) ,fp.map(fp.first) , fp.split(' '))

// => 重复性简化
const firstLetterToUpper2 = fp.flowRight(fp.join('. '), fp.map(fp.flowRight(fp.toUpper, fp.first)), fp.split(' '))

console.log(firstLetterToUpper('world will web'))
// => W. W. W
console.log(firstLetterToUpper2('world will web'))
// => W. W. W


九、Functor( 函子 )

1. 概念

在函子中,将看到函数式编程中如何把副作用控制在可控的范围内、异常处理、异步操作等

  • 容器:包含值和值的变形关系(这个变形关系就是函数)
  • 函子:是一个特殊的容器,通过一个普通的对象来实现,该对象具有 map 方法,map 方法可以运行一个函数对值进行处理(变形关系)
class Container { // 容器

  // => 新增
  static of (val) { // 返回函子对象
    return new Container(val) 
  }

  constructor (val) { // 内部储存值, 不对外公布
    this._value = val
  }

  map(fn) { // map 方法可以运行对值的处理
    return new Container(fn(this._value))
  }
}

// let result = new Container(5)
//   .map(v => v + 1)
//   .map(v => v * v)
// console.log(result) // 接收的是容器对象, 和内部储存的值
// // Container { _value: 36 }

let result2 = Container.of(5)
  .map(v => v + 2)
console.log(result2)
// => Container { _value: 7 }

特点:

  • 函数式编程的运算不直接操作值,而是由函子完成
  • 函子就是一个实现了 map 契约的对象
  • 我们可以把函子想象成一个盒子,这个盒子里封装了一个值
  • 想要处理盒子中的值,我们需要给盒子的 map 方法传递一个处理值的函数(纯函数),由这个函数来对值进行处理
  • 最终 map 方法返回一个包含新值的盒子/函子(可以不断的链式调用)

2. MayBe 函子(空值)

  • 我们在编程的过程中可能会遇到很多错误,需要对这些错误做相应的处理
  • MayBe 函子的作用就是可以对外部的空值情况做处理(控制副作用在允许的范围)
class MayBe {
  static of (val) {
    return new MayBe(val)
  }

  constructor (val) {
    this._value = val
  }

  isNothing() { // 这里做了一层空值校验
    return this._value === null || this._value === undefined
  }

  map (fn) { // 判断值是否为空
    return this.isNothing() ? MayBe.of(null) : MayBe.of(fn(this._value))
  }
}

let result = MayBe.of(5)
  .map(v => v + 1)
console.log(result)
// => MayBe { _value: 6 }

let result2 = MayBe.of(null)
  .map(v => v + 1)
console.log(result2)
// => MayBe { _value: null }

let result3 = MayBe.of('Hello world')
  .map(v => v.toUpperCase())
  .map(v => null)
  .map(v => v.split(''))
console.log(result3)
// => MayBe { _value: null }  不能知道 null 是由哪一次产生的

3. MayBe 函子(空值)

  • Either 两者中的任何一个,类似于 if…else…的处理
  • 异常会让函数变的不纯,Either 函子可以用来做异常处理
class Left{
  static of(val) { // 静态的 of 方法返回当前对象
    return new Left (val)
  }
  constructor(val) { // 通过构造函数记录函子的值
    this._value = val
  }
  map(fn) { // 对值进行处理
    return this
  }
}

class Right{
  static of(val) {
    return new Right (val)
  }
  constructor(val) {
    this._value = val
  }
  map(fn) {
    return Right.of(fn(this._value))
  }
}


let result1 = Left.of(5).map(v => v + 1)
console.log(result1)
// Left { _value: 5 }
let result2 = Right.of(5).map(v => v + 1)
console.log(result2)
// Right { _value: 6 }


function parseJson(str) {
  try {
    return Right.of(JSON.parse(str))
  } catch(e) {
    return Left.of({error: e.message})
  }
}

let result3 = parseJson('{ name: zs }')
console.log(result3)
// { error: 'Unexpected token n in JSON at position 2' }

let result4 = parseJson('{ "name": "zs" }')
console.log(result4)
// => { name: 'zs' }

let result5 = parseJson('{ "name": "zs" }')
  .map(v => v.name.toUpperCase())
console.log(result5)
// => Right { _value: 'ZS' }

4. Pointed 函子(of 静态方法)

  • Pointed 函子是实现了 of 静态方法的函子
  • of 方法是为了避免使用 new 来创建对象,更深层的含义是 of 方法用来把值放到上下文Context(把值放到容器中,使用 map 来处理值)
class Container {
static of (value) { // 实现 将 value 包裹在 新的函子内部并返回
return new Container(value)
}
...
}

5. IO 函子

⑴. 解决不纯操作
  • IO(input/output) 函子中的 _value 是一个函数,这里是把函数作为值来处理
  • IO(input/output) 函子可以把不纯的动作存储到_value 中,延迟执行这个不纯的操作(惰性执行),保证当前的操作纯
  • 把不纯的操作交给调用者来处理
const fp = require('lodash/fp')

class IO {
  static of (val) { // 返回的是包裹的值
    return new IO(function() {
      return val
    })
  }
  constructor (fn) { // 储存的是方法
    this._value = fn
  }

  map(fn) { // 返回的是 value 和 传入的 fn 组合成一个新的函数
    return new IO(fp.flowRight(fn, this._value))
  }
}

let io = IO.of(process) // 获取路径
  .map(e => e.execPath)
console.log(io)
// => IO { _value: [Function (anonymous)] }
console.log(io._value())
// => /usr/local/bin/node

⑵. 存在的嵌套问题

IO 函子存在嵌套问题, 如果调用函子的话, 需要一直执行下一级函子方法, 代码风格不够优雅

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

class IO {
  static of (val) { // 返回的是包裹的值
    return new IO(function() {
      return val
    })
  }
  constructor (fn) { // 储存的是方法
    this._value = fn
  }

  map(fn) { // 返回的是 value 和 传入的 fn 组合成一个新的函数
    return new IO(fp.flowRight(fn, this._value))
  }
}


// io err
let readFile = function (filename) { // 获取文件路径
  return new IO(function () {
    return fs.readFileSync(filename, 'ntf-8') // 同步
  })
}

let print = function (val) { // 对参数打印
  return new IO(function () {
    console.log(val)
    return val
  })
}

let cat = fp.flowRight(print, readFile) // 组合
let result = cat('functor_io_err.js')
console.log(result)
// => IO { _value: [Function (anonymous)] }
// => 解析: 现在拿到的函子, 是一个函子嵌套了一个函子(由于 IO 函子的缘故)

console.log(result._value())
// => IO { _value: [Function (anonymous)] }
// => IO { _value: [Function (anonymous)] }
// => 解析: 第一个打印的是 print 中的函子, 第二次打印的是 console.log(result)

console.log(result._value()._value()) // 报错了, 没找到原因
// => this file msg(应该是打印此文件内容, 但是没能实现)
// => err.msg:  throw new ERR_INVALID_ARG_VALUE(encoding, 'encoding', reason);

6. Monad 函子(扁平化)

  • Monad 函子是可以变扁的 Pointed 函子,IO(IO(x))
  • 一个函子如果具有 join 和 of 两个方法并遵守一些定律就是一个 Monad
const fp = require('lodash/fp')
const fs = require('fs')

class IO {
  static of (val) {
    return new IO(function() {
      return val
    })
  }
  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 readFile = function (filename) {
  return new IO(function () {
    return fs.readFileSync(filename, 'ntf-8')
  })
}

let print = function (val) {
  return new IO(function () {
    console.log(val)
    return val
  })
}

let result = readFile('functor_monad.js')
  .map(fp.toUpper)
  .flatMap(print)
  .join()
console.log(result)
// => 和上面一样的打印问题(但是之前的执行顺畅)

7. Task 函子(异步)

⑴. folktale

安装:

npm i folktale

基础使用:

const { curry, compose } = require('folktale/core/lambda')
const { first, toUpper } = require('lodash/fp')

// 柯里化
const f = curry(2, (x, y) => { // 参数个数, 参数执行的方法
  return x + y
})

console.log(f(1, 2))
// => 3
console.log(f(1)(2))
// => 3


// 函数组合
const f2 = compose(toUpper, first)
console.log(f2(['one', 'two']))
// => ONE

⑵. task(异步执行)
const { task } = require('folktale/concurrency/task')
const fs = require('fs')

function readFile(fileName) {
  return task(resolver => { // task 自带的函数
    fs.readFile(fileName, 'utf-8', (err, data) => {
      if(err) resolver.reject(err) // 执行错误回调

      resolver.resolve(data) // 执行成功回调
    })
  })
}

readFile('functor_task.js')
  .run()
  .listen({
    onRejected: err => console.log(err),
    onResolved: val => console.log(val)
  })

// =>  const { task } = require('...  (this file msg...)


下一篇:JavaScript 异步编程特性

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
新手编程导论 ———— A Programming Introduction For Beginners By Minlearn @ http://www.actyou.com.cn/ 设计才是真正的编程! 对类型的设计才是设计! 面向对象并非一切? 无论你以为上述观点是惊天大秘或不过尔尔,你都需要这本书! -------------------------------------------------------------------------------------------------------------- Todo: 整合过长的目录 完善前二十页 -------------------------------------------------------------------------------------------------------------- 目 录 第一部分 9 前 言 9 By Chenyi 9 By Minlearn 10 导 读 14 任何语言都是有门槛的 14 什么是语言级和语言外要学习的(数据结构与代码结构) 15 什么是语言级要学习的 17 编程学习方法 18 计算机学生专业课程本质讲解 18 用C++开发要学到什么程度 20 本书目录安排 21 第二部分 基础:导论 25 第1章 系统 25 1.1 何谓PC 25 1.2 图灵机与冯氏架构 26 1.3计算机能干什么 27 1.4 内存地址 28 1.5 分段和分页以及保护模式 30 1.7 操作系统 31 1.6 并发与协程 33 1.6 CPU与异常 33 1.7 所谓堆栈 34 1.8 真正的保护模式 36 1.9 异常与流转 38 1.10 最小,最完美的系统 39 1.11 操作系统与语言的关系 41 1.12 虚拟机与语言 41 1.13 虚拟机与语言 42 1.14 调试器与汇编器 43 1.15 平台之GUI 45 1.16 界面的本质应该是命令行功能支持下的配置描述文件 45 1.17 命令行下编程实践 46 第2章 语言 47 2.1 真正的计算模型 47 2.2 开发模型与语言模型 49 2.3 正规表达式与有限自动机 53 2.4 联系编译原理学语言 56 2.6 如何理解运行时 59 2.7 运行时环境 60 2.7 运行时 60 6.3 语言的类型系统 60 2.8 编译与解释 62 2.9 运行期与编译期 62 2.9 编译与运行期分开的本质与抽象 63 2.10 脚本语言 63 2.11 灵活性与安全性 65 2.12 二进制指令与循环 66 2.13 所谓函数 67 2.14 所谓流程 68 2.15 为什么需要数据类型和数据结构 68 2.16 数据类型和数据结构是二种不一样的东西 69 2.17 为什么需要变量这些东东 69 2.18 面向类型化的设计和面向无类型泛化的设计-OO不是银弹 70 第3章 语言之争 71 3.1 学编程之初,语言之争 71 3.2 语言与应用与人(1) 72 3.2 语言与应用与人(2) 73 3.3 C与Ruby 74 3.4 你为什么需要Ruby 75 3.5 C++还是Ruby 76 3.6 C++与Java 76 3.7 .NET与JVM 77 3.8 你为什么需要Ruby 78 3.9 语言功能上的差别 79 3.10 C与C++之争 80 3.11 我为什么选择C而不是C++及其它语言 81 3.12 类VB,DELPHI类RAD语言分析 82 第4章 语言最小内核(C) 83 4.1 C与C++是二种不同的语言 83 4.2 C的数组,指针与字符串 84 4.3 C的输入与输出流 84 4.4 C的类型系统与表达式 85 4.5 二进制指令看循环 85 4.6 所谓指针:当指针用于设计居多时 86 4.7 指针成就的C语言 86 4.8 指针是语言的一种抽象机制 88 4.9 学C千万不能走入的一个误区(其实JAVA比C难) 88 4.10 C抽象惯用法 90 4.11 C的抽象范式之OOP 91 4.12 C的观点:底层不需要直接抽象 93 4.13 指针:间接操作者 94 4.14 真正的typedef 95 4.15 真正的指针类型 95 4.16 真正的函数指针 97 4.17 真正的句柄 97 4.18 真正的循环 98 4.19 真正的static 98 4.20 真正的数组索引 99 4.21 类型和屏看原理 100 4.22 位操作与多维数组指针与元素 101 4.23 变量与VOID 102 第5章 抽象 102 5.1 人与软件 103 5.2 软件活动的特点 103 5.2 抽象与接口 104 5.3 过度抽象 105 5.3 OO为什么不是银弹 - 过度抽象与抽象偏差 106 5.4 真正的设计与编码 107 5.5 真正的构件库 109 5.6 大逻辑与小逻辑 112 5.7 什么是范式 112 第6章 抽象之数据结构 113 6.1 所谓数据结构 113 6.2 算法+数据结构的本质 115 6.4 算法不是设计 115 6.5 函数增长与算法复杂性分析 115 6.6 数据结构初步引象(1) 116 6.7 数据结构初步引象(2) 117 6.8 数据结构初步引象(3) 118 6.9 数据结构初步引象(4) 119 6.10 ordered与sorted 119 6.11 数据结构与抽象 119 6.12 真正的逻辑数据结构只有二种 120 6.12 树与图初步引象 121 6.13 树初步引象 122 6.14 B减树 123 6.15 图初步引象 124 6.16 树的平衡与旋转 125 6.17 完全与满 129 6.18 多路234树与红黑树的导出 129 6.19 快速排序思想 130 6.20 数据结构之数组 131 6.21 数据结构的抽象名字 132 6.22 真正的ADT 133 6.23 Vector的观点 135 6.24 真正的数据结构 136 6.25 堆栈与队列 138 6.26 真正的递归 140 6.27 树与单链表,图 145 6.28 树 146 6.29 真正的散列表 148 6.30 算法设计方法 148 第7章 抽象之高级语法机制 149 7.1 真正的OO解 149 7.2真正的对象 151 7.3真正的继承 152 7.4真正的OO 152 7.5真正的OOP 154 7.6 真正的构造函数 155 7.7 真正的私有,保护和公有 156 7.8 真正的重载与复写 156 7.9 C++的元编程 156 7.10 模板的意义在于编译前端的设计 157 7.11 C++的元编程和泛型编程 159 7.12 元编程和函数式编程 159 7.13 C++的模板编译技术本质 161 7.14 OO的缺点 161 7.15 模板的继承 162 7.16 模板的偏特化 162 7.17 真正的策略 162 7.18 为设计产生代码 164 7.19 真正的metaprogramming 165 7.20 元编程技术 166 第8章 抽象之设计和领域逻辑 167 8.1 大设计 167 8.1 什么是设计 167 8.2 编程能力,代码控制能力,复用与接口,轮子发明与使用 170 8.3 OO,模板,设计模式与设计 171 8.4 设计能力和程序员能力模型 172 8.4 自上而下设计和自下而上设计 173 8.5 大中型软件和复用与逻辑达成 177 8.6 通用设计与专门设计 178 8.7 具象与抽象 178 8.7 架构与应用 179 8.8 应用与设计 179 8.9 与软件有关的哲学 联系 180 8.10 与软工有关的哲学 唯物主义 180 8.11 真正的设计模式 182 8.12 设计模式与数据结构 182 8.12 设计模式之基础 183 8.12 真正的开闭原则 183 8.13 真正的通米特原则 184 8.14 真正的好莱钨原则 185 8.15 真正的策略模式 185 8.16 真正的观察者模式 185 8.17 真正的装饰模式 186 8.18 真正的单例模式 186 8.19 真正的迭代器模式 186 8.20 真正的工厂模式 187 8.21 真正的门面模式 187 8.22 真正的命令模式 188 8.23 真正的模板方法模式 188 8.24 真正的适配器模式 188 8.25 业务与逻辑分开 189 8.26 架构不是功能的要求,但却是工程的要求 189 8.27 你需不需要一个库 190 8.28 可复用与可移殖的区别 190 8.28 再谈可复用 193 8.29 真正的可复用 193 8.30 你能理解XP编程吗 194 8.31 构件与接口,软工 195 8.32 设计方法论 196 8.33 真正的interface 198 8.34 真正的对接口进行编程 200 8.35 实践方法之极限编程 200 8.36 设计模式复用与框架复用 201 第三部分 进阶: C,C++代码阅读与控制 201 第9章 语法与初级标准库 202 9.1 C++的基于过程设计 203 9.2 C++的基于对象设计: 模板与设计 203 9.3 面向对象设计 204 9.4 泛型开发与初级StdC库 204 第10章 数据逻辑与STL库 204 10.1 仿函数 204 10.2 iterater 204 10.3 adapter 205 第11章 高级代码逻辑与LOKI库 205 11.1 typelist 205 11.2 traits 206 11.2 policy 206 第四部分 一个例子:游戏引擎和实现 206 第12章 设计(需求分析) 207 12.1 第一天:接到一个案子 207 12.2 第二天:需求分析 208 第13章 设计(领域分析与抽象) 210 13.1 原语设计 210 13.2 了解Yake 216 13.3 GVWL1.0开发 222 13.4 范型设计与实作 223 第14章 编码 224 14.1 原语设计 224 14.2 实现《梦想与财富》 224 第15章 重构 225 15.1 增加Jxta 225 第五部分 225 选读 225 字符与字符串 226 为什么我说Java是脚本语言 226 宽松语法,无语法语言 227 Linux与3D 228 伪码语言 229 最强大的语言原来是预处理 230 语言宿主 231 shell编程和交互式语句编程 232 Debug,编译期断言 232 图形原理之位图,图象和字体 233 为Windows说些好话 233 Minlearn Ruby (5) 网络原理与P2P 234 Minlearn Ruby(4) 字符串与WEB 234 加密与解密 235 Minlearn(3)载体逻辑 236 Minlearn(2) 平台与并发 237 Minlearn(1)平台之持久 237 平台之多媒体编程 237 Minlearn Ruby 238 思想,维度,细节 240 理想 241 XML 242 面向更好复用的封装机制 243 SOA 244 浮点标准与实现 244 Desktop,web,internet,云计算不过WEB的集中化这种说法的偷换概念 246 编程设计与经济 246 晕计算 247 在形式主义与直觉主义之间:数学与后现代思想的根源 248 Scheme 程序语言介绍之一 248 与软工有关的哲学 辩证 251 富网页技术 251 形式维度 252 开源与开放 253 Core learning and min learing编程 253 与软工有关的哲学 联系 254 本地化和语言编码 254 学习与维度 255 跟软件有关的哲学.唯物主义 255 关于逻辑的逻辑 256 合理性 257 语言,道理和感情 258 可恶OO 259 互联网与企业开发 259 会学习的人首先要学历史 260 离散数学与代数系统 262 线代与矩阵 262 计算机与编程 263 真正的二进制 265 真正的文件 266 真正的数据 267 真正的Unicode 267 真正的Windows消息 269 真正的XML 270 真正的GUI 271 设备环境 271 真正的MFC 273 真正的虚拟机 275 真正的.NET 276 真正的脚本 278 真正的并发性 279 真正的反工程 280 真正的DSL 282 真正的多范型设计 284 真正的调试 285 真正的浮点数 286 真正的布尔 288 真正的整型数 289 真正的引用 290 真正的RTTI 290 真正的句柄 292 真正的循环 293 真正的STL 293 真正的容器 295 真正的智能指针 295 真正的数组索引 296 数据库 297 真正的可持久化 298 真正的类库 299 真正的COM 300 真正的DCOM 301 真正的Sun策略 302 真正的J2EE 303 真正的EJB 304 附录:一些领域逻辑,通用OO设计策略,框架设计 305 附录:参考文献 305 附录:一些建议 305
当今的前端框架体系主要分为三个大类:基于MVC的框架、基于MVVM的框架和基于函数式编程的框架。下面将分别进行概述。 1. 基于MVC的框架 MVC框架是前端框架的先驱,它的核心思想是将应用程序分为三个部分:模型(Model)、视图(View)和控制器(Controller)。模型负责处理数据,视图负责展示数据,控制器负责处理用户输入和业务逻辑。常见的MVC框架包括AngularJS、Backbone.js和Ember.js等。 2. 基于MVVM的框架 MVVM框架是MVC框架的进化版,它的核心思想是将视图和模型进行双向绑定,使数据的变化可以自动反映到视图中,而视图的变化也可以自动反映到模型中。MVVM框架的典型代表是Vue.js和React.js等。Vue.js是一种轻量级的MVVM框架,它的核心是数据驱动和组件化开发模式。React.js是一种基于组件的UI库,它的核心是虚拟DOM和函数式编程。 3. 基于函数式编程的框架 函数式编程框架的核心思想是将应用程序看作一系列的函数调用,而不是一系列的对象和方法。函数式编程框架的主要特点是可组合性和可重用性。常见的函数式编程框架有Redux和MobX等。Redux是一种状态管理框架,它的核心是单一数据源和纯函数。MobX是一种响应式状态管理框架,它的核心是可观察数据和自动化依赖追踪。 总的来说,这三种框架体系各有优缺点,开发人员应根据具体的应用场景和需求选择合适的框架。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

后海 0_o

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值