函数式编程(二)

98 阅读3分钟

闭包

闭包 (Closure): 函数和其周围的状态(词法环境)的引用捆绑在一起(或者说函数被引用包围)形成闭包。

  • 一个内层函数中访问到其外层函数的作用域

本质

  • 函数在执行的时候会放到一个执行栈上当函数执行完毕之后会从执行栈上移除,但是栈上的作用域成员因为被外部引用不能释放,因此内部函数依然可以访问外部函数的成员。
// 函数作为返回值
function makeFn () {
    let msg = 'Hello function'
    return function () {
        console.log(msg)
    }
}
const fn = makeFn()
fn()

用途

  • 可以读取函数内部的变量。
  • 让这些变量的值始终保持在内存中。

注意点

  • 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。

解决方法 : 在退出函数之前,将不使用的局部变量全部删除。

  • 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

function makeFunc() {
    var name = "Mozilla";
    function displayName() {
        alert(name);
    }
    return displayName;
}

var myFunc = makeFunc();
myFunc();

柯里化

优点

  • 我们可以使用柯里化,通过给一个函数传递较少的参数得到一个已经记住某些固定参数的新函数
  • 可以对函数的参数进行 "缓存"
  • 让函数变得更灵活,让函数的粒度更小
  • 可以把多元函数转换成一元函数,可以组合使用函数产生强大的功能。
// 普通纯函数
function checkAge (min, age) {
  return age >= min
}
checkAge(18, 24) 
checkAge(20, 30)
// 柯里化
function checkAge (min) {
  return function (age) {
    return age >= min
  }
}
// ES6 写法
let checkAge = min => (age => age >= min)

let checkAge18 = checkAge(18)
let checkAge20 = checkAge(20)
checkAge18(24) 
checkAge20(16)

函数组合

管道

给fn函数输入参数a,返回结果b ==> 数据a通过一个管道得到了数据b, 函数fn即可当作管道。

image.png

当函数fn比较复杂的时候我们可以把函数fn拆分为多个函数,同时会产生多个数据结果。即 函数fn ==> 函数f3、数据m、函数f2、数据n、函数f3.

image.png

函数组合

函数组合 (compose): 如果一个函数要经过多个函数处理才能得到最终值,这个时候可以把中间过程的函数合并成一个函数。

  • 函数就像是数据的管道,函数组合就是把这些管道连接起来,使数据通过多个管道最终得到结果。
  • 函数组合默认式从右到左执行。
  • lodash的组合函数为: flow() / flowRight(), 可组合多个函数。
    • flow():从左到右执行。
    • flowRight():从右到左执行(常用)。
const _ = require('lodash')
const toUpper = val => val.toUpperCase()
const reverse = arr => arr.reverse()
const first = arr => arr[0]
const f = _.flowRight(toUpper, first, reverse)
const value = ['Jane', 'Alice', 'Tony', 'Jone']
console.log(f(value))
  • 函数组合要满足 结合律
const _ = require('lodash')
// const f = _.flowRight(_.toUpper, _.first, _.reverse)
// const f = _.flowRight(_.flowRight(_.toUpper, _.first), _.reverse)
const f = _.flowRight(_.toUpper, _.flowRight(_.first, _.reverse))
const value = ['Jane', 'Alice', 'Tony', 'Jone']
console.log(f(value))

lodash/fp

  • lodash 的 fp 模块提供了实用的对函数式编程友好的方法
  • 提供了不可变 auto-curried(已经被柯里化)、iteratee-first(函数优先) data-last(数据滞后) 的方法。
// lodash 模块
const _ = require('lodash')
_.map(['a', 'b', 'c'])                  // => ['a', 'b', 'c']
_.map(['a', 'b', 'c'], _.toUpper)       // => ['A', 'B', 'C']
_.split('Hello World', ' ')             // => [ 'Hello', 'World' ]

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

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

Point Free

Point Free: 我们可以把数据处理的过程定义成与数据无关的合成运算,不需要用到代表数据的那个参数,只要把简单的运算步骤合成到一起。

  • 不使用所要处理的值,只合成运算过程。
// 非 Point Free 模式 Hello World ==> hello_world
function f (word) {
 return word.toLowerCase().replace(/\s+/g, '_')
}

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