为什么要学习函数式编程

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函数式编程内容请搜索Devmax以前的文章或继续浏览下面的相关文章希望大家以后多多支持Devmax!

JavaScript函数式编程实现介绍的更多相关文章

  1. 基于JavaScript编写一个图片转PDF转换器

    本文为大家介绍了一个简单的 JavaScript 项目,可以将图片转换为 PDF 文件。你可以从本地选择任何一张图片,只需点击一下即可将其转换为 PDF 文件,感兴趣的可以动手尝试一下

  2. HTML5数字输入仅接受整数的实现代码

    这篇文章主要介绍了HTML5数字输入仅接受整数的实现代码,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  3. amaze ui 的使用详细教程

    这篇文章主要介绍了amaze ui 的使用详细教程,本文通过多种方法给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  4. html5简介_动力节点Java学院整理

    这篇文章主要介绍了html5简介,用于指定构建网页的元素,这些元素中的大多数都用于描述网页内容,有兴趣的可以了解一下

  5. ios 8 Homescreen webapp,关闭和打开iPad停止javascript

    我有一个适用于iPad的全屏HTML5网络应用程序,并且刚刚安装了IOS8来试用它,它一切正常,直到你关闭并重新启动iPad.一旦web应用程序重新启动javascript就会停止并加载新页面不会重新启动它.在iPad上的Safari中打开同一页面时,关闭和打开iPad会继续按预期工作.其他人注意到了这个或想出了一个解决方案吗?解决方法这似乎是我在iOS8.1.1更新中解决的.

  6. iOS 6 javascript与object.defineProperty的间歇性问题

    当访问使用较新的Object.defineProperty语法定义属性的对象的属性时,有没有其他人注意到新iOS6javascript引擎中的间歇性错误/问题?https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/defineProperty我正在看到javascript失败的情况,说

  7. ios – 如何使用JSExport导出内部类的方法

    解决方法似乎没有办法将内部类函数导出到javascript.我将内部类移出并创建了独立的类,它起作用了.

  8. 静音iOS推送通知与React Native应用程序在后台

    我有一个ReactNative应用程序,我试图获得一个发送到JavaScript处理程序的静默iOS推送通知.我看到的行为是AppDelegate中的didReceiveRemoteNotification函数被调用,但是我的JavaScript中的处理程序不会被调用,除非应用程序在前台,或者最近才被关闭.我很困惑的事情显然是应用程序正在被唤醒,并且它的didReceiveRemoteNotifi

  9. ios – 内存泄漏与UIWebView和Javascript

    清楚地包含一个Javascript文件到我的HTML是使UIWebView泄漏内存.当我重复使用相同的UIWebView对象时,或者每当我有内容实例化一个新的漏洞时,会出现泄漏的事实,导致我认为必须有一些JavaScript文件被loadHTMLString处理,导致泄漏.有人知道如何解决这个问题吗?

  10. iOS应用程序的UI自动化测试如何与乐器和Javascript

    从WWDC2010视频会议中了解iOS应用程序的自动化UI测试,但没有实践.从代码项目project,我们可以有一个例子.这个问题在这里听到有涉及这个的人.任何限制?解决方法我建议从AlexWollmer开始使用thisblogpost.他创建了一个非常有用的JavaScript库:tuneup_jswithtest()函数,它允许测试分离和有用的帮助者以及为自动化仪器编写测试的断言.

随机推荐

  1. js中‘!.’是什么意思

  2. Vue如何指定不编译的文件夹和favicon.ico

    这篇文章主要介绍了Vue如何指定不编译的文件夹和favicon.ico,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

  3. 基于JavaScript编写一个图片转PDF转换器

    本文为大家介绍了一个简单的 JavaScript 项目,可以将图片转换为 PDF 文件。你可以从本地选择任何一张图片,只需点击一下即可将其转换为 PDF 文件,感兴趣的可以动手尝试一下

  4. jquery点赞功能实现代码 点个赞吧!

    点赞功能很多地方都会出现,如何实现爱心点赞功能,这篇文章主要为大家详细介绍了jquery点赞功能实现代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  5. AngularJs上传前预览图片的实例代码

    使用AngularJs进行开发,在项目中,经常会遇到上传图片后,需在一旁预览图片内容,怎么实现这样的功能呢?今天小编给大家分享AugularJs上传前预览图片的实现代码,需要的朋友参考下吧

  6. JavaScript面向对象编程入门教程

    这篇文章主要介绍了JavaScript面向对象编程的相关概念,例如类、对象、属性、方法等面向对象的术语,并以实例讲解各种术语的使用,非常好的一篇面向对象入门教程,其它语言也可以参考哦

  7. jQuery中的通配符选择器使用总结

    通配符在控制input标签时相当好用,这里简单进行了jQuery中的通配符选择器使用总结,需要的朋友可以参考下

  8. javascript 动态调整图片尺寸实现代码

    在自己的网站上更新文章时一个比较常见的问题是:文章插图太宽,使整个网页都变形了。如果对每个插图都先进行缩放再插入的话,太麻烦了。

  9. jquery ajaxfileupload异步上传插件

    这篇文章主要为大家详细介绍了jquery ajaxfileupload异步上传插件,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. React学习之受控组件与数据共享实例分析

    这篇文章主要介绍了React学习之受控组件与数据共享,结合实例形式分析了React受控组件与组件间数据共享相关原理与使用技巧,需要的朋友可以参考下

返回
顶部