函数式编程

152 阅读4分钟

历史:为什么要学习函数式编程

  • 函数式编程是随着React的流程收到越来越多的关注
  • Vue3也开始拥抱函数式编程
  • 函数式编程可以抛弃this
  • 打包过程中可以更好的利用tree shaking过滤无用代码
  • 方便测试、方便并行处理
  • 有很多库可以帮助我们进行函数式开发:loadsh、undercore、ramda

什么是函数式编程

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

  • 面向对象编程的思维方式:把现实世界中的事物抽象成程序世界中的类和对象,通过封装、继承和多态来演示事物事件的联系
  • 函数式编程的思维方式:把现实世界的事物和事物之间的联系抽象到程序世界(对运算过程进行抽象)
  1. 程序的本质:根据输入通过某种运算获得相应的输出,程序开发过程中会涉及很多的输入和输出的函数
  2. x->f(联系、映射)->y,y=f(x)
  3. 函数式编程中的函数指的不是程序中的函数(方法),而是数学中的函数即映射关系
  4. 相同的输入始终得到相同的输出(纯函数)
  5. 函数式编程用来描述数据(函数)之间的映射
// 非函数式
let num1 = 2;
let num2 = 3;
let sum = num1 + num2;

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

函数是一等公民

  • 函数可以存储在变量中
  • 函数作为参数
  • 函数作为返回值 在JavaScript中函数就是一个普通的对象(可以通过new Function()),我们可以把函数存储到变量/数组中,它还可以作为另一个函数的参数和返回值,甚至我们可以再程序运行的时候通过new Function('alert(1)')来构造一个新的函数
// 把函数存储变量中
let fn = function(){
    console.log('Hello First-class Function')
}
fn()

// 一个示例
const BlogController = {
    index(posts) { return Views.index(posts) },
    show(post) { return Views.index(post) },
    create(attrs) { return Db.create(attrs) }, 
    update(post, attrs) { return Db.udpate(post, attrs) },
    destroy(post) { return Db.destroy(post) }
}

// 优化
const BlogController = {
    index: Views.index,
    show: Views.show,
    create: Db.create, 
    update: Db.udpate,
    destroy: Db.destroy
}

高阶函数

高阶函数(Higher-order function)

  • 可以把函数作为参数传递给另一个函数
// forEach
function forEach(array, fn) {
    for(let i = 0; i < array.length; i++) {
        fn(array[i])
    }
}

// filter
function filter(array, fn) {
    let results = []
    for (let i = 0; i < array.length; i++) {
        if (fn(array[i])) {
            result.push(array[i])
        }
    }
    return results
}
  • 可以把函数作为另一个函数的返回结果
// 函数作为返回值
function makeFn() {
    let msg = 'Hello Function';
    return function() {
        console.log(msg)
    }
}

// 调用
makeFn()()

// loadsh中的once函数 对函数只执行一次
function once(fn) {
    let done = false
    return function() {
        if (!done) {
            done = true;
            return fn.apply(this, arguments)
        }
    }
}
// 调用
let pay = once(function(money) {
    console.log(`支付: ${money} RMB`)
})
pay(5)
pay(5)
pay(5)
pay(5)
pay(5)
pay(5)
pay(5)

使用高阶函数的意义

  • 抽象可以帮我们屏蔽细节,只需要关注与我们的模板
  • 高阶函数是用来抽象通用问题
  • 使用更加灵活
  • 代码更加简洁
// 面向过程的方式
let array = [1, 2, 3, 4, 5]
for (let i = 0; i < array.length; i++) {
    console.log(array[i])
}

// 高阶函数
let array = [1, 2, 3, 4, 5]
forEach(array, item => {
    console.log(item)
})


let r = filter(array, item => {
    return item % 2 === 0
})

常用高阶函数

  • forEach
  • map
  • filter
  • every
  • some
  • find/findIndex
  • reduce
  • sort
  • ......
// 手动实现常用高阶函数
// map 作用:对数组的每一个元素进行遍历处理并返回
const map = (array,fn) => {
    let results = [];
    for (let value of array) {
        results.push(fn(value))
    }
    return results
}
// 调用
let array = [1, 2, 3, 4, 5]
arr = map(array, v => v * v)

// every 作用就是遍历数组中的每一个元素,看是否符合条件
const every = (array, fn) => {
    let result = true;
    for(let value of array) {
        result = fn(value)
        if (!result) {
            break;
        }
    }
    return result
}
// 调用
let array = [1, 2, 3, 4, 5]
arr = every(array, v => v > 10)

闭包(Closure)

概念

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

  • 可以在另一个作用域中调用一个函数的内部函数并访问到该函数的作用域中的成员

闭包的本质:函数在执行的时候会放到一个执行栈上当函数执行完毕之后会从执行栈上移除,但是堆上的作用域成员因为被外部引用不能释放,因此内部函数依然可以访问外部函数的成员

// 函数作为返回值
function makeFn() {
    let msg = 'Hello Function';
    return function() {
        console.log(msg)
    }
}

// 调用
makeFn()()

// loadsh中的once函数 对函数只执行一次
function once(fn) {
    let done = false
    return function() {
        if (!done) {
            done = true;
            return fn.apply(this, arguments)
        }
    }
}

案例


// Math.pow(4, 2)

function makePower(power) {
    return function (number) {
        Math.pow(number, power)
    }
}

纯函数

概念

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

  • 纯函数就是类似数学中的函数(用来描述输入和输出之间的关系),y = f(x)

image.png

  • loadsh是一个纯函数的功能库,提供了对数组、数字、对象、字符串、函数等操作的一些方法

  • 数组中的slice 和 splice分别是:纯函数和不纯的函数

  • slice返回数组中的指定部分,不会改变原数组

  • splice对数组进行操作返回该数组,会改变原数组

函子

  • 函数式编程的运算不能直接操作,而是由函子完成
  • 函子就是一个实现了map契约的对象
  • 我们可以把函子想象成一个盒子,这个盒子封装了一个值
  • 最终map方法返回一个包含新值的盒子( 函子 )