阅读 87

JavaScript中的函数式编程--函数

为什么学习函数式编程

  • 函数式编程是随着React的流行收到越来越多的关注,React中的高阶组件使用高阶函数老实现,高阶函数就是函数式编程的一个特性;再就是React中的redux也是使用了函数式编程的思想
  • iOS中的自动布局框架Masonry也是使用函数式编程实现的
  • 在新发布的vue3中也是用了大量了高阶函数,由此可见在流行的框架中(React、vue、Masonry)都趋向于函数式编程,也可以说你可以补血新框架,但是不能不学函数式编程
  • 使用函数式编程时可以最大限度的抛弃烦人的this
  • 使用函数式编程在打包过程中可以更好的利用tree shaking过滤掉无用代码
  • 使用函数式编程还能方便测试、并行处理等操作
  • 很多流行库能帮助我们使用函数式编程,如:lodash、underscore、ramda等

什么是函数式编程

函数式编程(Functional Programming,FP),FP是编程的一种范式,我们常见的编程范式还有面向对象、面向过程。

  • 面向对象编程:就是把现实世界中的事物抽象成编程世界中的类和对象,通过封装、集成和多态来演示事物事件中间的联系
  • 函数式编程:就是把现实世界中的事物和事物之间的联系抽象到程序世界(对运算过程进行抽象)
    • 程序的本质:根据输入通过某种运算获得相应的输出,程序开发过程中会涉及到很多输入输出函数
    • 使用数学中的映射表示:x -> f(联系/映射) -> y,即:y=f(x);再比如:y=sin(x)
    • 特点:相同的输入始终得到相同的输出(即纯函数)
    • 一句话描述函数式编程:用来描述数据(函数)之间的映射,是对运算过程的抽象

代码示例:计算两个数的和

    // 非函数式(按步骤,面向过程)
    let num1 = 1
    let num2 = 2
    let sum = num1 + num2
    
    // 函数式(把过程抽象成函数)
    function add(n1, n2) {
        return n1 + n2
    }
    let sum = add(1, 2)
复制代码

函数是一等公民

一等公民,MDN解释:First-class Function 在JavaScript中函数就是一个普通的对象,既然是对象,那我们就可以把函数存储到变量/数组/对象中,它还可以作为其他函数的参数和返回值使用,也可以再程序运行过程中通过 new Function('alert("xx")')来构造一个新的函数

函数可以存储在变量中

直接代码演示

    // 函数赋值给变量
    let fn = function() {
        console.log('first-class Function')
    }
    fn()
    
    // 函数存储示例
    const BlogController = {
        index(posts) { return Views.index(posts) },
        show(posts) { return Views.show(posts) },
        create(attrs) { return Db.create(attrs) },
        update(posts, attrs) { return Db.update(posts, attrs) },
        destory(posts) { return Db.destory(posts) }
    }
    // 示例优化(通过将一个函数/方法赋值给另一个变量)
    const BlogController = {
        index: Views.index,
        show: Views.show,
        create: Db.create,
        update: Db.update,
        destory: Db.destory
    }
复制代码

函数作为参数使用

高阶函数

高阶函数: Highter-order function

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

示例:高阶函数-函数作为参数

    // 模拟forEach
    function forEach(array, fn) {
        if (Object.prototype.toString.call(array) !== '[object Array]' || typeof fn !== "function") return
        for (let i = 0; i < array.length; i++) {
            fn(array[i])
        }
    }
    const forEachArr = [1, 3, 6, 8, 9];
    forEach(forEachArr, function(item) {
        console.log(item)
    })
复制代码
    // 模拟过滤函数filter
    function filter(array, fn) {
        if (Object.prototype.toString.call(array) !== '[object Array]' || typeof fn !== "function") return
        let res = [] // 存储满足条件的数据
        for (let i = 0; i < array.length; i++) {
            let item = array[i]
            if (fn(item)) {
                res.push(item)
            }
        }
        return res
    }
    const filterArr = [1, 3, 6, 8 ,9]
    // 过滤数组中的偶数
    const evenNumbers = filter(filterArr, function(item) {
        return item % 2 === 0
    })
复制代码

以上两个例子我们可以看出:当函数作为参数时调用会更灵活,在使用某个功能时不需要考虑其内部是如何去实现,也达到了功能封装的效果

函数作为返回值使用

函数作为返回值,即当我们去调用一个函数的时候,该函数会给我们返回一个Function类型的数据 示例:高阶函数-函数作为返回值

    function makeFn() {
        if (Object.prototype.toString.call(array) !== '[object Array]' || typeof fn !== "function") return
        let msg = 'Hello function'
        return function() {
            console.log(msg)
        }
    }
    // 调用makeFn得到return返回的函数
    const fn = makeFn()
    // 执行fn
    fn()
    // 也可以使用连续执行
    makeFn()()
复制代码
    // 模拟once函数
    function once(fn) {
        if (Object.prototype.toString.call(array) !== '[object Array]' || typeof fn !== "function") return
        let done = false // 标记当前函数是否被执行
        return function() {
            if (!done) {
                console.log('>>', arguments)
                done = true
                // 返回函数执行结果
                return fn.apply(this, arguments)
            }
        }
    }
    // 模拟支付场景
    const pay = once(function(money) {
        console.log('支付:${money}')
    })
    pay(10) // 执行
    // 之后的都不会执行
    pay(10)
    pay(10)
复制代码

为什么要使用高阶函数(使用高阶函数的意义)

  • 高阶函数是用来抽象通用问题的
  • 抽象可以帮我们屏蔽细节,只需要关注我们的目标
  • 使代码更简洁

举例:比如我们现在需要去遍历一个数组,按照面向过程的方式时我们需要使用一个for循环,定义一个循环变量,判断循环条件等操作;如果此时我们使用高阶函数对遍历这个步骤进行抽象,如上边实现的forEach函数,我们此时只需要知道forEach内部帮我们实现了循环,然后传递数据给forEach。(前边的filter函数也是如此)

常用的高阶函数

  • forEach
  • map
  • filter
  • every
  • some
  • find/findIndex
  • reduce
  • sort
  • ......

map实现

    // 此处我们使用ES6中的箭头函数来实现
    const map = (array, fn) => {
        if (Object.prototype.toString.call(array) !== '[object Array]' || typeof fn !== "function") return
        let res = []
        // 此处我们使用for循环的替代for of来实现
        for (let value of array) {
            res.push(fn(value))
        }
        return res
    }
    
    const mapArr = [1, 2, 3, 4]
    // 此处我们来对数组中的元素做平方运算
    const squareArr = map(mapArr, value => value * value)
    console.log(squareArr)
复制代码

some实现(检测数组中的元素是否至少有一个满足某个条件)

    const some = (array, fn) => {
        if (Object.prototype.toString.call(array) !== '[object Array]' || typeof fn !== "function") return
        let res = false
        for (let value of array) {
            res = fn(value)
            if (res) {
                break
            }
        }
        return res
    }
    const someArr = [1, 3, 4, 5]
    // 判断数组中是否有偶数
    const hasEvenNumber = some(someArr, value => value % 2 == 0)
    console.log(hasEvenNumber)
复制代码

every实现(判断数组中的每一个元素是否都匹配某个条件)

    const every = (array, fn) => {
        if (Object.prototype.toString.call(array) !== '[object Array]' || typeof fn !== "function") return
        let res = true // 用于记录数组中的元素是否匹配
        for (let value of array) {
            res = fn(value)
            if (!res) break
        }
        return res
    }
    const everyArr = null
    // 判断数组中每一个元素是否大于10
    const moreThenTen = every(everyArr, value => value > 10)
    console.log(moreThenTen)
复制代码