- 为什么要学函数式编程?
-
什么是函数式编程?
- 函数式编程和面向对象编程的不同
- 对于函数式编程思维形式的了解:
-
函数式编程的前置常识
- 函数是一等公民
-
高阶函数
- 什么是高阶函数?
- 应用高阶函数的意义
- 罕用的高阶函数
-
闭包
- 闭包的概念
- 闭包的核心作用
- 闭包的实质
-
闭包的案例
- 案例一
- 案例二
为什么要学函数式编程?
函数式编程 是一个十分古老的概念。
- 函数式表成是随着 React 的风行收到越来越多的关注(React 的高阶组件应用了高阶函数来实现,高阶函数就是函数式编程的一个个性。Redux 也应用了函数式编程的思维。)
- Vue3 也开始拥抱函数式编程
- 函数式编程能够摈弃 this
- 打包过程中能够更好的利用 tree shaking 过滤无用代码
- 不便测试、不便并行处理
- 有很多库能够帮忙咱们进行函数式开发:lodash、underscore、ramda
什么是函数式编程?
函数式编程,缩写 FP,是一种编程范式,也是一种编程格调,和面向对象是并列的关系。函数式编程咱们能够认为是一种思维模式,加上实现办法。其思维形式就是把事实世界事物和事物之间的分割形象到程序世界(是对运算过程进行形象
)
常据说的编程范式还有 面向过程编程(依照步骤来实现)、面向对象编程(把事实中的事物形象成类和对象,通过封装、继承和多态来演示不同事物之间的分割)。
函数式编程和面向对象编程的不同
从思维形式上来说 面向对象编程是对事物的形象,而函数式编程是对运算过程的形象
对于函数式编程思维形式的了解:
- 程序的实质:依据输出通过某种运算取得相应的输入,程序开发过程中会波及很多输出和输入的函数。
- 函数式编程中的函数指的不是程序中的函数 Function,而是 数学中的函数即映射关系,例如:y=sin(x),是这种 x 和 y 的关系
- 雷同的输出时钟要失去雷同的输入(纯函数)
- 函数式编程用形容数据(函数)之间的映射
// 非函数式
let num1 = 2
let num2 = 3
let sum = num1 + num2
console.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])
}
}
// test
let 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
}
// test
let 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)
}
}
}
// test
let pay = once(function (money) {console.log(` 领取:${money} RMB`)
})
pay(5) // 领取:5 RMB
pay(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
}
// test
let 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
}
// test
let arr = [11, 12, 14]
let r = every(arr, v => v > 10)
console.log(r) // false
r = 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
}
// test
let arr = [1, 3, 4, 9]
let arr1 = [1, 3, 5, 9]
let r = some(arr, v => v % 2 === 0)
console.log(r) // true
r = 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)) // 16
console.log(power2(5)) // 25
console.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)) //14000
console.log(salaryLevel2(3000)) //18000
console.log(salaryLevel2(4000)) //19000