• 为什么要学函数式编程?
  • 什么是函数式编程?

    • 函数式编程和面向对象编程的不同
    • 对于函数式编程思维形式的了解:
  • 函数式编程的前置常识

    • 函数是一等公民
    • 高阶函数

      • 什么是高阶函数?
      • 应用高阶函数的意义
      • 罕用的高阶函数
    • 闭包

      • 闭包的概念
      • 闭包的核心作用
      • 闭包的实质
      • 闭包的案例

        • 案例一
        • 案例二

为什么要学函数式编程?

函数式编程是一个十分古老的概念。

  • 函数式表成是随着React的风行收到越来越多的关注(React的高阶组件应用了高阶函数来实现,高阶函数就是函数式编程的一个个性。Redux也应用了函数式编程的思维。)
  • Vue3也开始拥抱函数式编程
  • 函数式编程能够摈弃this
  • 打包过程中能够更好的利用tree shaking过滤无用代码
  • 不便测试、不便并行处理
  • 有很多库能够帮忙咱们进行函数式开发:lodash、underscore、ramda

什么是函数式编程?

函数式编程,缩写FP,是一种编程范式,也是一种编程格调,和面向对象是并列的关系。函数式编程咱们能够认为是一种思维模式,加上实现办法。其思维形式就是把事实世界事物和事物之间的分割形象到程序世界(是对运算过程进行形象

常据说的编程范式还有面向过程编程(依照步骤来实现)、面向对象编程(把事实中的事物形象成类和对象,通过封装、继承和多态来演示不同事物之间的分割)

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

从思维形式上来说 面向对象编程是对事物的形象,而函数式编程是对运算过程的形象

对于函数式编程思维形式的了解:

  • 程序的实质:依据输出通过某种运算取得相应的输入,程序开发过程中会波及很多输出和输入的函数。
  • 函数式编程中的函数指的不是程序中的函数Function,而是数学中的函数即映射关系,例如:y=sin(x),是这种x和y的关系
  • 雷同的输出时钟要失去雷同的输入(纯函数)
  • 函数式编程用形容数据(函数)之间的映射
// 非函数式let num1 = 2let num2 = 3let sum = num1 + num2console.log(sum)// 函数式function add(n1, n2) {    return n1 + n2}let sum = add(2, 3)console.log(sum)

函数式编程的前置常识

函数是一等公民

在JS中函数就是一个一般的对象,咱们能够把函数存储到变量/数组中,它还能够作为另一个函数的参数和返回值,甚至咱们能够在程序运行的时候通过new Function('alert(1)')来结构一个新的函数。

  • 函数能够存储在变量中
// 把函数赋值给变量let fn = function () {    console.log("hi")}fn()// 一个示例const BlogController = {    index (posts) { return Views.index(posts) },    show (post) { return Views.show(post) },    create (attrs) { return Db.create(attrs) },    update (post, attrs) { return Db.update(post, attrs) },    destroy (post) { return Db.destroy(post) }}// 优化 赋值的是Views的index办法,不是办法的调用const BlogController = {    index: Views.index,    show: Views.show,    create: Db.create,    update: Db.update,    destroy: Db.destroy}    

上面两个个性在高阶函数中会有具体阐明

  • 函数能够作为参数
  • 函数能够作为返回值

高阶函数

什么是高阶函数?

高阶函数(Higher-order function)

  • 函数能够作为参数
// forEach// 定义一个遍历数组的并对每一项做解决的函数,第一个函数是一个数组,第二个参数是一个函数。function forEach (array, fn) {    for (let i = 0; i < array.length; i++) {        fn(array[i])     } }// testlet arr = [1, 2, 3]forEach(arr, item => {    item = item * 2    console.log(item) // 2 4 6})
// filter// 遍历数组,并把满足条件的元素存储成数组,再进行返回function filter(array, fn) {    let results = []    for (let i = 0; i < array.length; i++) {         //如果满足条件          if (fn(array[i])) {             results.push(array[i])         }        }    return results}// testlet arr = [1, 3, 4, 7, 8]let result = filter(arr, item => item % 2 === 0)console.log(result) // [4, 8]
  • 函数作为返回值
// 一个函数返回另一个函数function makeFn () {    let msg = 'Hello function'     return function () {         console.log(msg)     } }// test// 第一种调用形式const fn = makeFn() fn() //Hello function// 第二种调用形式makeFn()()///Hello function
// once// 让函数只执行一次function once(fn) {    let done = false    return function() {        // 判断值有没有被执行,如果是false示意没有执行,如果是true示意曾经执行过了,不用再执行        if(!done) {            done = true            // 调用fn,以后this间接传递过去,第二个参数是把fn的参数传递给return的函数            return fn.apply(this, arguments)        }    }}// testlet pay = once(function (money) {    console.log(`领取:${money} RMB`)})pay(5) //领取:5 RMBpay(5)pay(5)pay(5)pay(5)

应用高阶函数的意义

  • 形象能够帮咱们屏蔽细节,咱们只须要晓得咱们的指标和解决这类问题的函数,咱们不须要关怀实现的细节
  • 高阶函数是用来形象通用的问题

罕用的高阶函数

有一个通用的特点,须要一个函数作为参数。

  • forEach
  • map 对数组中的每个元素进行遍历,并解决,解决的后果放在一个新数组中返回
const map = (array, fn) => {     let results = []     for (const value of array) {         results.push(fn(value))     }    return results }// testlet arr = [1, 2, 3, 4]arr = map(arr, v => v * v)console.log(arr)//
  • filter
  • every 数组中的每一个元素是否都匹配咱们指定的一个条件,如果都满足返回true,如果不满足返回false
const every = (array, fn) => {     let result = true     for (const value of array) {        result = fn(value)         // 如果有一个元素不满足就间接跳出循环        if (!result) {             break         }    }    return result}// testlet arr = [11, 12, 14]let r = every(arr, v => v > 10)console.log(r) // falser = every(arr, v => v > 12)console.log(r) // false
  • some 判断数组中是否有一个元素满足咱们指定的条件,满足是true,都不满足为false
const some = (array, fn) => {     let result = false     for (const value of array) {        result = fn(value)         // 如果有一个元素不满足就间接跳出循环        if (result) {             break         }    }    return result}// testlet arr = [1, 3, 4, 9]let arr1 = [1, 3, 5, 9]let r = some(arr, v => v % 2 === 0)console.log(r) // truer = some(arr1, v => v % 2 === 0)console.log(r) // false
  • find/findIndex
  • reduce
  • sort

闭包

闭包的概念

闭包:函数和其四周的状态(词法环境)的援用捆绑在一起造成闭包

  • 艰深的讲:能够在另一个作用域中调用一个函数的外部函数并拜访到该函数作用域中的成员

在下面函数作为返回值的过程中,其实咱们就用到了闭包,上面进行语法演示:

function makeFn () {    let msg = 'Hello function'}// 失常状况下,执行完makeFn,外面的变量msg会开释掉// 然而上面的状况function makeFn () {    let msg = 'Hello function'    return function () {         console.log(msg)    } }// 在下面函数中,返回了一个函数,而且在函数中还拜访了原来函数外部的成员,就能够称为闭包const fn = makeFn()fn()// fn为内部函数,当内部函数对外部成员有援用的时候,那么外部的成员msg就不能被开释。当咱们调用fn的时候,咱们就会拜访到msg。//留神的点://1、咱们能够在另一个作用域调用makeFn的外部函数//2、当咱们调用外部函数的时候咱们能够拜访到外部成员

闭包的核心作用

把函数外部成员的作用范畴缩短

闭包的实质

函数在执行的时候会放到一个执行栈上,当函数执行结束之后会从执行栈上移除。然而堆上的作用域成员因为被内部援用不能开释,因而外部函数仍然能够拜访内部函数的成员。

/解读:函数执行的时候在执行栈上,执行结束之后从执行栈上移除,外部成员的内存被开释。然而在函数执行结束移除之后,开释内存的时候,如果内部有援用,则外部成员的内存不能被开释。/

闭包的案例

案例一:计算一个数平方和立方的运算
Math.pow(4, 2)Math.pow(5, 2)// 前面的二次方三次方很多次重复,上面要写一个二次方三次方的函数function makePower (power) {  return function (number) {    return Math.pow(number, power)  }}// 求平方let power2 = makePower(2)let power3 = makePower(3)console.log(power2(4)) // 16console.log(power2(5)) // 25console.log(power3(4)) // 64

调试台的案例演示

案例二:计算不同级别的员工工资
// 假如计算员工工资的函数第一个函数传基本工资,第二个参数传绩效工资// getSalary(12000, 2000)// getSalary(15000, 3000)// getSalary(15000, 4000)// 不同级别的员工基本工资是一样的,所以咱们将基本工资提取进去,之后只须要加上绩效工资function makeSalary (base) {     return function (performance) {         return base + performance     }}let salaryLevel1 = makeSalary(12000)let salaryLevel2 = makeSalary(15000)console.log(salaryLevel1(2000)) //14000console.log(salaryLevel2(3000)) //18000console.log(salaryLevel2(4000)) //19000