JS 函数式编程 01 —— FP,纯函数,柯里化

js函数式编程01

  • 什么是函数式编程
    • 函数式编程和面向对象编程的不同
    • 高阶函数
    • 闭包
  • 纯函数
    • 纯函数的好处
        • 可缓存
        • 可测试
        • 并行处理
    • 副作用
  • 柯里化
    • lodash 中的 curry() 方法
    • 封装模拟lodash的curry柯里化转化方法
    • 柯里化总结

什么是函数式编程

  • 函数式编程,Functional Programming,简称FP,是一种编程范式,也是一种编程风格。
  • 简单理解就是把事物与事物之间的联系抽象到程序世界

函数式编程和面向对象编程的不同

  • 面向对象 是把事物本身进行抽象
  • 函数式编程 是对运算过程的抽象
  • 对于函数式编程的理解:
    • 程序本质是通过某种运算获得响应的输出,而函数就是包括输入(参数)输出(返回值)
    • 函数式编程这里的函数指的不是Function,把它理解为数学中的函数 y=f(x) 这种映射关系更加合理

高阶函数

高阶函数(Higher-order function)

  • 函数作为参数,下面我们来封装一个类似于forEach的函数,其就是把函数作为参数
// 定义一个类似于js中数组方法forEach的函数
// 接收两个参数 一个是数组 另一个是数组中成员要执行的函数
function myForEach (array, fn) {
     
	for(var i=0; i<array.length; i++ ){
     
		fn(array[i])
	}
}
// test
myForEach(['tom','jerry','jim'],item =>{
      console.log(item) })
  • 函数作为返回值
// 一个函数返回另一个函数
function makeFn () {
     
    let msg = 'Hello function' 
    return function () {
      
        console.log(msg) 
    } 
}

// test
// 第一种调用方式
const fn = makeFn() 
fn() //Hello function

// 第二种调用方式
makeFn()()///Hello function
  • 使用高阶函数的意义
    抽象可以帮助我们屏蔽细节,高阶函数是用来抽象通用的问题

闭包

  • 对于闭包的理解,首先要了解一点函数的局部变量存在栈中,当函数执行完就会被释放,但是闭包的产生是由于函数中局部变量被捕获了,导致无法释放,那么这个被捕获的局部变量为了保证不会被销毁,就会在堆中生成对象scope记录下来
  • 所以闭包的本质就是,函数在执行的时候会放到一个执行栈上,当函数执行完毕之后会从执行栈上移除。但是堆上的作用域成员因为被外部引用不能释放,因此内部函数依然可以访问外部函数的成员。
// 闭包典型案例
for(var i=0; i<10; i++){
     
	((j)=>{
      
		setTimeout( ()=>{
     
			console.log(j)
		} )
	 })(i)
}

纯函数

纯函数其实就是数学中的函数概念 类似于 y=f(x),用来描述输入和输出的对应关系,是函数式编程重要的概念。
相同的输入永远会得到相同的输出,而且没有任何可观察的副作用。

let numbers = [1, 2, 3, 4, 5] 
// 纯函数 
// 对于相同的函数,输出是一样的

// slice方法,截取的时候返回截取的函数,不影响原数组
numbers.slice(0, 3) // => [1, 2, 3] 
numbers.slice(0, 3) // => [1, 2, 3] 
numbers.slice(0, 3) // => [1, 2, 3] 

// 不纯的函数 
// 对于相同的输入,输出是不一样的

// splice方法,返回原数组,改变原数组
numbers.splice(0, 3) // => [1, 2, 3] 
numbers.splice(0, 3) // => [4, 5] 
numbers.splice(0, 3) // => []
  • 可以把一个函数的执行结果交给另一个函数处理

纯函数的好处

可缓存

因为对于相同的输入始终有相同的结果,那么可以把纯函数的结果缓存起来,可以提高性能

  • 模拟lodash中的memoize函数,对纯函数的结果进行缓存
function memoize(f){
      
	// 定义一个对象用于记录纯函数的参数和执行结果
	let cache = {
     }
	return function(){
      
		// 将传入的参数作为对象的key
		let key = JSON.stringfiy( arguments )
		// 如果key有对应值说明是再次调用直接获取缓存中的值
		cache[key] = cache[key] || f.apply(f,arguments)
		return cache[key]
	 }
 }
function getArea(r) {
     
  console.log(r)
  return Math.PI * r * r
}
let getAreaWithMemory = memoize(getArea)
console.log(getAreaWithMemory1(4))
console.log(getAreaWithMemory1(4))
console.log(getAreaWithMemory1(4))
// 4
// 50.26548245743669
// 50.26548245743669
// 50.26548245743669

可测试

纯函数让测试更加的方便

并行处理

  • 多线程环境下并行操作共享的内存数据很可能会出现意外情况。纯函数不需要访问共享的内存数据,所以在并行环境下可以任意运行纯函数
  • 虽然JS是单线程,但是ES6以后有一个Web Worker,可以开启一个新线程

副作用

副作用就是让一个函数变得不纯,纯函数的根据市相同的输入返回相同的输出,如果函数依赖于外部的状态就无法保证输出相同
比如纯函数中如果依赖于 全局变量进行计算 那么只要改变了全局变量 就会导致函数不纯

柯里化

当函数有多个参数的时候,我们可以对函数进行改造。我们可以调用一个函数,只传递部分的参数(这部分参数以后永远不变),然后让这个函数返回一个新的函数。新的函数传递剩余的参数,并且返回相应的结果
函数的柯里化可以帮助我们解决函数中的硬编码问题

// 定义一个checkAge函数
// 先传递比较年龄的min 
function checkAge (min) {
     
    return function (age) {
     
        return age >= min
    }
}
// 使用es6改写
let checkAge = min => (age => age >= min)

lodash 中的 curry() 方法

_.curry(func)

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

//match函数是根据一些正则,匹配字符串,返回匹配结果
const match = _.curry((reg, str) => str.match(reg))

//haveSpace函数是一个匹配空格的函数
const haveSpace = match(/\s+/g)

//haveNumber函数是一个匹配数字的函数
const haveNumber = match(/\d+/g)

//filter函数是定义一个数组和过滤规则,返回符合匹配规则的数组

const filter = _.curry((func, array) => array.filter(func))

//findSpace函数是匹配数组元素中有空格并返回符合情况的数组的函数
const findSpace = filter(haveSpace)

柯里化的好处就是我们可以最大程度的重用我们的函数

封装模拟lodash的curry柯里化转化方法

  • 分析思路:
    1. 传入参数为纯函数func
    2. 返回值为一个柯里化之后的函数 curried
    3. 比较curried的实参个数…args 跟纯函数的形参个数func.length
    4. 如果实参个数大于等于形参个数 返回func的调用结果
    5. 如果实参个数小于形参个数 要再次返回一个新的curried ,并且指定其形参前几位为上一次curried调用时 传入的实参 ,并合并新的实参
function getSum(a, b, c) {
     
    return a + b + c
}

function curry(func) {
     
    return function curried(...args) {
     
        // 函数的length属性获得函数形参个数
        // es6的...args 函数的参数扩展 获取传入的不固定数量的实参
        if (args.length < func.length) {
     
            return function () {
     
                // 第一部分参数在args里面,第二部分参数在arguments里面,要将两个合并并且展开传递
                // concat函数要合并两个数组,arguments为伪数组,所以用Array.from进行转换
                return curried(...args.concat(Array.from(arguments)))
            }
        }
        // 如果实参个数大于等于形参个数 返回func的调用结果
        return func(...args)
    }
}

let s1=curry(getSum)

console.log(s1(1)(2)(3));

柯里化总结

  • 柯里化可以让我们给一个函数传入较少的参数 得到一个已经记住了一部分参数的新函数
  • 这是一种对函数参数的缓存
  • 让函数变得更灵活,颗粒度更小
  • 可以把多元函数编程一元函数,可以组合使用函数产生强大的功能

你可能感兴趣的:(JS,学习回顾,js,javascript,fp,es6)