我的编程空间,编程开发者的网络收藏夹
学习永远不晚

JavaScript函数式编程实现介绍

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

JavaScript函数式编程实现介绍

为什么要学习函数式编程

Vue进入3.*(One Piece 海贼王)世代后,引入的setup语法,颇有向老大哥React看齐的意思,说不定前端以后还真是一个框架的天下。话归正传,框架的趋势确实是对开发者的js功底要求更为严格了,无论是hooks、setup,都离不开函数式编程,抽离代码可复用逻辑,更好地组织及复用代码,有一点我感到很高兴的是,终于可以抛弃烦人的this了,当然,这也不是我为偷懒而生出这样的感想,人家道格拉斯老爷子可是在他的新书《JavaScript悟道》里极力吐槽了一下this,所以,也算是像js大佬看齐了。所以,要想不被前端日新月异的新技术给冲昏头脑,还是适时回来重学一下JavaScript吧。

什么是函数式编程

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

面向对象编程:面向对象有三大特性,通过封装、继承和多态来演示事物之间的联系,如果更宽泛来说,抽象也应该算进去,但是由于面向对象的本质就是抽象,其不算是三大特性也不为过。

函数式编程:函数式编程的思想主要就是对运算过程进行抽象,它更像一个黑盒,你给入特定的输出,进过黑盒运算后再返回运算结果。你可以将其理解为数学中的y = f(x)。

  • 程序的本质:根据输入进行某种运算得到相应的输出。
  • x -> f(联系、映射) -> y, y = f(x)
  • 函数式编程中的函数其实对应数学中的函数,即映射关系。
  • 相同的输入始终要得到相同的输出(纯函数)
  • 可复用

前置知识

函数是一等公民

作为一名有一定经验的前端开发者,你一定对JavaScript中“函数是一等公民”这一说法不陌生。

这里给出权威文档MDN的定义:当一门编程语言的函数可以被当作变量一样用时,则称这门语言拥有头等函数。例如,在这门语言中,函数可以被当作参数传递给其他函数,可以作为另一个函数的返回值,还可以被赋值给一个变量。

函数可以储存在变量中

let fn = function() {
  console.log('Hello First-class Function')
}
fn()

函数作为参数

function foo(arr, fun) {
  for (let i = 0; i < arr.length; i++) {
    fun(arr[i])
  }
}
const array = [1, 2, 3, 4]
foo(array, function(a) { console.log(a) })

函数作为返回值

function fun() {
  return function () {
    consoel.log('哈哈哈')
  }
}
const fn = fun()
fn()

高阶函数

什么是高阶函数

高阶函数

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

函数作为参数(为了避免文章篇幅过长,后面的演示代码就不给出测试代码了,读者可自行复制文章代码在本地编辑器上调试)

function filter(array, fn) {
    let results = []
    for (let i = 0; i < array.length; i++) {
        if (fn(array[i])) {
            results.push(array[i])
        }
    }
    return results
}
// 测试
let arr = [1, 3, 4, 7, 8]
const results = filter(arr, function(num) {
    return num > 7
})
console.log(results) // [8]

函数作为返回值

// 考虑一个场景,在网络延迟情况下,用户点击支付,你一定不想要用户点完支付没反应后点击下一次支付再重新支付一次,不然,你的公司就离倒闭不远了。
// 所以考虑一下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)
// 5

使用高阶函数的意义

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

常用高阶函数

  • forEach(已实现)
  • map
const map = (array, fn) => {
  let results = []
  for (let value of array) {
    results.push(fn(value))
  }
  return results
}
  • filter
  • every
const every = (array, fn) => {
  let result = true
  for (let value of array) {
    result = fn(value)
    if (!result) {
      break
    }
  }
  return result
}
  • some
const some = (array, fn) => {
  let result = false
  for (let value of array) {
    result = fn(value)
    if (result) {
      break
    }
  }
  return result
}
  • find/findIndex
  • reduce
  • sort

闭包

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

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

function makePower(power) {
    return function (num) {
        return Math.pow(num, power)
    }
}
// 求平方及立方
let power2 = makePower(2)
let power3 = makePower(3)
console.log(power2(4)) // 16
console.log(power2(5)) // 25
console.log(power3(4)) // 64
function maekSalary(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

其实上面这两个函数都是差不多的,都是通过维持对原函数内部成员的引用。具体可以通过浏览器调试工具自行了解。

纯函数

纯函数概念

纯函数:相同的输入永远会得到相同的输出

lodash 是一个纯函数的功能库,提供了对数组、数字、对象、字符串、函数等操作的一些方法。有人可能会有这样的疑惑,随着ECMAScript的演进,lodash中很多方法都已经在ES6+中逐步实现了,那么学习其还有必要吗?其实不然,lodash中还是有很多很好用的工具函数的,比如说,防抖节流是前端工作中经常用到的,你可不想每次都手写一个函数吧?更何况没有一点js功底还写不出来呢。

话归正传,来看看数组的两个方法:slice和splice。

  • slice 返回数组中的指定部分,不会改变原数组
  • splice 对数组进行操作返回该数组,会改变原数组
let array = [1, 2, 3, 4, 5]
// 纯函数
console.log(array.slice(0, 3))
console.log(array.slice(0, 3))
console.log(array.slice(0, 3))
// 不纯的函数
console.log(array.splice(0, 3))
console.log(array.splice(0, 3))
console.log(array.splice(0, 3))

纯函数的好处

可缓存

因为纯函数对相同的输入始终有相同的结果,所以可以把纯函数的结果缓存起来

function getArea(r) {
    console.log(r)
    return Math.PI * r * r
}
function memoize(f) {
    let cache = {}
    return function() {
        let key = JSON.stringify(arguments)
        cache[key] = cache[key] || f.apply(f, arguments)
        return cache[key]
    }
}
let getAreaWithMemory = memoize(getArea)
console.log(getAreaWithMemory(4))
console.log(getAreaWithMemory(4))
console.log(getAreaWithMemory(4))
// 4
// 50.26548245743669
// 50.26548245743669
// 50.26548245743669

可测试

纯函数让测试更方便

并行处理

在多线程环境下并行操作共享的内存数据很可能会出现意外情况

纯函数不需要访问共享的内存数据,所以在并行环境下可以任意运行纯函数 (Web Worker)

副作用

// 不纯的
let mini = 18
function checkAge (age) {
  return age >= mini
}
// 纯的(有硬编码,后续可以通过柯里化解决)
function checkAge (age) {
  let mini = 18
  return age >= mini
}

副作用让一个函数变的不纯(如上例),纯函数的根据相同的输入返回相同的输出,如果函数依赖于外部的状态就无法保证输出相同,就会带来副作用。

柯里化

柯里化的概念:当一个函数有多个参数的时候先传递一部分参数调用它(这部分参数以后永远不变),然后返回一个新的函数接收剩余的参数,返回结果。

柯里化就可以解决上面代码中的硬编码问题

// 普通的纯函数
function checkAge(min, age) {
    return age >= min
}
// 函数的柯里化
function checkAge(min) {
    return function(age) {
        return age >= min
    }
}
// 当然,上面的代码也可以用ES6中的箭头函数来改造
const checkAge = (min) => (age => age >= min)

下面来手写一个curry函数

function curry(func) {
  return function curriedFn(...args) {
    if (args.length < func.length) {
      return function() {
        return curriedFn(...args.concat(Array.from(arguments)))
      }
    }
    return func(...args)
  }
}

函数组合

看了这么多代码,你肯定会觉得函数里面有很多return看起来不是很好看,事实也确是如此,所以这就要引出函数组合这个概念。

纯函数和柯里化很容易写出洋葱代码 h(g(f(x)))

获取数组的最后一个元素再转换成大写字母, .toUpper(.first(_.reverse(array))) (这些都是lodash中的方法)

函数组合可以让我们把细粒度的函数重新组合生成一个新的函数

你可以把其想象成一根管道,你将fn管道拆分成fn1、fn2、fn3三个管道,即将不同处理逻辑封装在不同的函数中,然后通过一个compose函数进行整合,将其变为一个函数。

fn = compose(f1, f2, f3)
b = fn(a)

Functor(函子)

什么是Functor

  • 容器:包含值和值的变形关系(这个变形关系就是函数)
  • 函子:是一个特殊的容器,通过一个普通的对象来实现,该对象具有 map 方法,map 行一个函数对值进行处理(变形关系)
// Functor 函子 一个容器,包裹一个值
class Container {
    constructor(value) {
        this._value = value
    }
    // map 方法,传入变形关系,将容器里的每一个值映射到另一个容器
    map(fn) {
        return new Container(fn(this._value))
    }
}
let r = new Container(5)
    .map(x => x + 1)
    .map(x => x * x)

console.log(r)  // 36

总结

  • 函数式编程的运算不直接操作值,而是由函子完成
  • 函子就是一个实现了 map 契约的对象
  • 我们可以把函子想象成一个盒子,这个盒子里封装了一个值
  • 想要处理盒子中的值,我们需要给盒子的 map 方法传递一个处理值的函数(纯函数),由这个函数来对值进行处理
  • 最终 map 方法返回一个包含新值的盒子(函子)

可能你不习惯在代码中看到new关键字,所以可以在容器中实现一个of方法。

class Container {
    static of (value) {
        return new Container(value)
    }
    constructor(value) {
        this._value = value
    }
    map(fn) {
        return Container.of(fn(this._value))
    }
}

MayBe 函子

上面的代码中如果传入一个null 或 undefined的话,代码就会抛出错误,所以需要再实现一个方法

class MayBe {
    static of(value) {
        return new MayBe(value)
    }
    constructor(value) {
        this._value = value
    }
    map(fn) {
        return this.isNothing() ? MayBe.of(null) : MayBe.of(fn(this._value))
    }
    isNothing() {
        return this._value == null // 此处双等号等价于this._value === null || this._value === undefined
    }
}

你看下上面的代码,是不是健壮性就好一点了呢?

Either函子

在MayBe函子中,很难确认哪一步产生的空值问题。所以就有了Either

class Left {
    static of(value) {
        return new Left(value)
    }
    constructor(value) {
        this._value = value
    }
    map(fn) {
        return this
    }
}
class Right {
    static of(value) {
        return new Right(value)
    }
    constructor(value) {
        this._value = value
    }
    map(fn) {
        return Right.of(fn(this._value))
    }
}
function parseJSON(str) {
    try {
        return Right.of(JSON.parse(str))
    } catch (e) {
        return Left.of({ error: e.message })
    }
}

到此这篇关于JavaScript函数式编程实现介绍的文章就介绍到这了,更多相关JS函数式编程内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

JavaScript函数式编程实现介绍

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

python函数式编程实例介绍

这篇文章主要讲解了“python函数式编程实例介绍”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“python函数式编程实例介绍”吧!  函数式编程:是指代码中每一块都是不可变的,都由纯函数的
2023-06-02

Python-函数式编程介绍 (上)

1、代码的组织结构不清晰,可读性差 2、实现重复的功能时,只能重复编写实现功能的代码,代码繁多,耗费时间精力 3、假如需要部分功能的扩展或者更新时,需要找出所有实现此功能的地方,一一修改,无法统一管理,加大了维护
2023-01-31

Golang函数式编程库与框架的介绍

go语言提供了丰富的函数式编程库和框架,包括数据处理(filter)、压缩(snappy)、数据分析(gota),以及函数式工具(functional)、依赖注入(fx)和分布式跟踪(opentracing)。通过使用这些库和框架,我们可以
Golang函数式编程库与框架的介绍
2024-04-14

Linux编程基础教程:exit函数介绍

在Linux编程中,exit()函数是一个用于结束程序的系统调用exit()函数的原型如下:#include void exit(int status);其中,status参数表示程序的退出状态。通常情况下,0表示
Linux编程基础教程:exit函数介绍
2024-09-09

linux内核编程container of()函数介绍

前言 在linux 内核编程中,会经常见到一个宏函数container_of(ptr,type,member), 但是当你通过追踪源码时,像我们这样的一般人就会绝望了(这一堆都是什么呀? 函数还可以这样定义??? 怎么还有0呢??? 哎,
2022-06-03

JavaScript中的函数式编程实例分析

这篇文章主要介绍“JavaScript中的函数式编程实例分析”,在日常操作中,相信很多人在JavaScript中的函数式编程实例分析问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”JavaScript中的函数式
2023-07-05

javascript函数式编程的用法

本篇内容介绍了“javascript函数式编程的用法”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!说明1、函数式编程是一种编程范式,是一种软
2023-06-20

Python编程之Re模块下的函数介绍

re模块下的函数compile(pattern):创建模式对象import re pat=re.compile('A') m=pat.search('CBA') #等价于 re.search('A','CBA') prin
2022-06-05

JavaScript函数式编程示例分析

函数式编程是一种编程范式,将整个程序都由函数调用以及函数组合构成。可以看成一条流水线,数据可以不断地从一个函数的输出流入另一个函数的输入,最后输出结果
2022-11-13

JavaScript自定义Webpack配置实现流程介绍

本系列主要整理前端面试中需要掌握的知识点。本节介绍webpack如何优化前端性能,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
2022-11-13

编程热搜

目录