ng

JavaScript之函数式编程

2021-07-02  本文已影响0人  Young_Jeff

啥是函数式编程?

函数式编程(Functional Programming, FP),是一种编程范式,常用的编程范式还有:面向对象编程,面向过程编程;

对函数式编程的理解

// 非函数式
let n1 = 2
let n2 = 3
let sum = n1 + n2
console.log(sum)

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

为啥要学?


函数式编程的前置知识

  1. 在JavaScript中,函数是一等公民
  2. 高阶函数(用来屏蔽细节,只关心目标),常用的高阶函数有:filter,map,forEach,every等

函数可以存储在变量中,可以当作参数传递,还能当作返回值

// 把函数赋值给变量
let fn = function () {
    console.log("hello")
}
fn()

// 函数作为参数传递,forEach实现
function forEach (array, fn) {
  for (let i = 0; i < array.length; i++) {
      fn(array[i]) 
  } 
}
// test
let arr = [1, 2, 3]
forEach(arr, item => {
  item = item * 2
  console.log(item) // 2 4 6
})

// 当作返回值返回
function fn2(){
  let num = 100;
  return function(){
    console.log(num)
  }
}
// test
const res = fn2()
res()  //100
  1. 闭包(延长作用域链)
    闭包的概念:内部函数可以访问外部函数的变量和参数
    闭包的本质:函数在执行的时候会放在一个执行栈上,当函数执行完毕后会从栈移除,但是,堆上的作用域成员因为还被引用着,得不到释放,因为就可以访问到;
    继续用上面的代码案例
function fn2(){
  let num = 100;
}
// 正常情况下,执行完fn2,里面的变量num会释放掉
function fn2(){
  let num = 100;
  return function(){
    console.log(num)
  }
}
// 在上面函数中,返回了一个函数,而且在函数中还访问了原来函数内部的成员,就可以称为闭包

// test
const res = fn2()
res()
// res为外部函数,当外部函数对内部成员有引用的时候,那么内部的成员num就不能被释放。当调用res时,就可以访问num。

纯函数是啥?

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

类似数学中的函数,y=f(x) 1.jpg
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
const _ = require('lodash')
function getArea(r) {
  console.log(r)
  return Math.PI * r * r
}

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

// 看到输出的4只执行了一次,因为其结果被缓存下来了

下面模拟一个记忆函数

function memoize (f) {
  let cache = {}
  return function () {
    // arguments是一个伪数组,所以要进行字符串的转化
    let key = JSON.stringify(arguments)
    // 如果缓存中有值就把值赋值,没有值就调用f函数并且把参数传递给它
    cache[key] = cache[key] || f.apply(f,arguments)
    return cache[key]
  }
}

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

函数柯里化又是啥?

详情可参考另一篇拆解柯里化:函数柯里化

将多变量函数拆解为单变量的多个函数的依次调用;
就是利用函数执行,可以形成一个不销毁的私有作用域,把预先处理的内容放到不销毁的作用域里面,返回一个函数供以后调用;

// 普通的纯函数
function checkAge (min, age) {
    return age >= min
}
console.log(checkAge(18, 20))  //true
console.log(checkAge(18, 24))  //true
// 经常使用18,这段代码是重复的。为了避免重复改造函数:
function checkAge (min) {
    return function (age) {
        return age >= min
    }
}

let checkAge18 = checkAge(18)

console.log(checkAge18(20)) //true
console.log(checkAge18(24)) //true

lodash中的柯里化-curry

const _ = require('lodash')

// 参数是一个的为一元函数,两个的是二元函数
// 柯里化可以把一个多元函数转化成一元函数
function getSum (a, b, c) {
  return a + b + c
}
// 定义一个柯里化函数
const curried = _.curry(getSum)

// 如果输入了全部的参数,则立即返回结果
console.log(curried(1, 2, 3)) // 6
//如果传入了部分的参数,此时它会返回当前函数,并且等待接收getSum中的剩余参数
console.log(curried(1)(2, 3)) // 6
console.log(curried(1, 2)(3)) // 6

简单实现一个柯里化转换函数
分析:

  1. 调用curry,传递一个纯函数,完成后返回一个柯里化函数
  2. 如果调用curried传递的参数和getSum参数个数相同,就立即执行并返回结果;如果调用curried传递的是部分参数,那么需要返回一个新函数,等待接受getSum其他参数
function curry(func) {
  return function curriedFn(...args) {
    // 判断实参和形参的个数
    console.log('看下args', args);
    if (args.length < func.length) {
      return function () {
        // 等待传递的剩余参数
        // 第一部分参数在args里面,第二部分参数在arguments里面
        console.log('看下arguments', arguments);
        return curriedFn(...args.concat(Array.from(arguments)));
      };
    }
    // 如果实参大于等于形参的个数
    // args是剩余参数
    return func(...args);
  };
}
function getSum(a, b, c) {
  return a + b + c;
}

const curriedTest = curry(getSum)

console.log(curriedTest(1, 2, 3))  // 6
console.log(curriedTest(1)(2, 3))  // 6
console.log(curriedTest(1, 2)(3))  // 6

柯里化优点:


函数组合

纯函数和柯里化很容易写出洋葱代码 h(g(f(x))),函数组合可以避免这种情况;

a --> fn --> b
a-> f3 -> m -> f2 -> n -> f1 -> b
其实中间m、n、是什么我们也不关心 类似于下面的函数

先来看看Lodash中的组合函数用法

//  获取数组的最后一个元素并转化成大写字母
const _ = require('lodash')

const reverse = arr => arr.reverse()
const first = arr => arr[0]
const toUpper = s => s.toUpperCase()

const f = _.flowRight(toUpper, first, reverse)

console.log(f(['one', 'two', 'three'])) // THREE

简单实现一个flowRight函数
分析: 入参不固定,都是函数,出参是一个函数,这个函数要接受一个初始值

function compose(...args) {
  // args代表调用compose传入的要组合的函数数组
  return function (value) {
    // compose返回的函数接受一个初始值value
    // 因为要从右往左执行,所以数组反转一下
    // reduce方法接受两个参数:一个迭代函数,一个初始化值;
    // 其中的迭代函数的前两个参数:total代表上一次调用fn的返回值,fn指当前正在处理值(此处是函数)
    return args.reverse().reduce(function (total, fn) {
      return fn(total);
    }, value);
  };
}

//test
const reverse = (arr) => arr.reverse();
const first = (arr) => arr[0];
const toUpper = (s) => s.toUpperCase();

const fTest = compose(toUpper, first, reverse);
console.log(fTest(['one', 'two', 'three'])); // THREE

上一篇下一篇

猜你喜欢

热点阅读