什么是函数式编程?
# 什么是函数式编程?
函数式编程(Functional Programming, FP),FP 是编程范式之一,我们常听说的编程范式还有面向过程编程、面向对象编程。
面向对象编程的思维方式:把现实世界中的事物抽象成程序世界中的类和对象,通过封装、继承和多态来演示事物事件的联系
函数式编程的思维方式:把现实世界的事物和事物之间的联系抽象到程序世界(对运算过程进行抽象)
程序的本质:根据输入通过某种运算获得相应的输出,程序开发过程中会涉及很多有输入和输出的函数
函数式编程中的函数指的不是程序中的函数(方法),而是数学中的函数即映射关系,函数式编程用来描述数据(函数)之间的映射
纯函数:相同的输入始终要得到相同的输出
// 非函数式【面向过程】
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)
# 为什么要学函数式编程?
- 函数式编程是随着 React 的流行受到越来越多的关注
- Vue 3也开始拥抱函数式编程
- 函数式编程可以抛弃 this
- 打包过程中可以更好的利用 tree shaking 过滤无用代码
- 方便测试、方便并行处理
- 有很多库可以帮助我们进行函数式开发:lodash、underscore、ramda
# 基本认知
# 函数是一等公民
https://developer.mozilla.org/zh-CN/docs/Glossary/First-class_Function
- 函数可以存储在变量中
- 函数可以作为参数
- 函数可以作为返回值
在 JavaScript 中函数就是一个普通的对象 (可以通过 new Function()
),我们可以把函数存储到变量/数组中,它还可以作为另一个函数的参数和返回值,甚至我们可以在程序运行的时候通过 newFunction('alert(1)')
来构造一个新的函数。
把函数赋值给变量
let fn = function () {
console.log('Hello First-class Function')
}
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) }
}
// 优化
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])
}
}
// 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
}
函数作为返回值
function makeFn () {
let msg = 'Hello function'
return function () {
console.log(msg)
}
}
const fn = makeFn()
fn()
makeFn()()
// 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)
# 使用高阶函数的意义
高阶函数是用来抽象通用
的问题
抽象: 可以帮我们屏蔽细节,只需要关注于我们的目标
// 面向过程的方式
let array = [1, 2, 3, 4]
for (let i = 0; i < array.length; i++) {
console.log(array[i])
}
// 高阶高阶函数
let array = [1, 2, 3, 4]
forEach(array, item => {
console.log(item)
})
let r = filter(array, item => {
return item % 2 === 0
})
常用高阶函数
- forEach
- map
- filter
- every
- some
- find/findIndex
- reduce
- sort
- ……
练习实现
forEach
function forEach(array, fn){
for(let i = 0; i < array.length; i++){
fn(array[i])
}
}
map
function map(array, fn){
let result = []
for(let i = 0; i < array.length; i++){
result.push(fn(array[i]))
}
return result
}
filter
function filter(array, fn){
let result = []
for(let i = 0; i < array.length; i++){
if(fn(array[i])){
result.push(fn(array[i]))
}
}
return result
}
every
function every(array, fn){
for(let item of array){
if(!fn(item)){
return false
}
}
return true
}
some
function some(array, fn){
for(let item of array){
if(fn(item)){
return true
}
}
return false
}
find
: 返回数组中满足提供的测试函数的第一个元素的值
function find(array, fn){
for(let item of array){
if(fn(item)){
return item
}
}
return undefined
}
findIndex
: 返回数组中找到的元素的索引,而不是其值
function findIndex(array, fn){
for(let i = 0; i < array.length; i++){
if(fn(array[i])){
return i
}
}
return undefined
}
# 闭包
# 基本概念
闭包 (Closure):函数和其周围的状态(词法环境)的引用捆绑在一起形成闭包。
可以在另一个作用域中调用一个函数的内部函数并访问到该函数的作用域中的成员
函数作为返回值,并引用了外面函数中的变量
function makeFn () {
let msg = 'Hello function' // 正常情况下 msg在函数执行完后会被释放
return function () {
console.log(msg)
}
}
// once
function once (fn) {
let done = false // done 不会被释放
return function () {
if (!done) {
done = true
return fn.apply(this, arguments)
}
}
}
闭包的本质:函数在执行的时候会放到一个执行栈上,当函数执行完毕之后会从执行栈上移除, 但是堆上的作用域成员因为被外部引用不能释放,因此内部函数依然可以访问外部函数的成员
# 闭包案例
// 生成计算数字的多少次幂的函数
function makePower (power) {
return function (number) {
return Math.pow(number, power)
}
}
let power2 = makePower(2)
let power3 = makePower(3)
console.log(power2(4))
// 第一个数是基本工资,第二个数是绩效工资
function makeSalary (base) {
return function (y) {
return base + y
}
}
let salaryLevel1 = makeSalary(1500)
let salaryLevel2 = makeSalary(2500)
console.log(salaryLevel1(2000))
console.log(salaryLevel2(3000))