javascript 函数式编程头脑

最最先打仗函数式编程的时刻是在小米事情的时刻,那个时刻看老大之前写的代码种种 compose,然后一些 ramda 的一些东西函数,看着很费劲,然后尽力吐槽函数式编程,如今回想起来,那个时刻的本身真的是见地短浅,只想说,’真香’。

近来在研讨函数式编程,真的是在进修的历程当中以为本身的头脑提升了很多,笼统才能大大的进步了,让我深深的感觉到了函数式编程的魅力。所以我盘算背面用 5 到 8 篇的篇幅,细致的引见一下函数式编程的头脑,基础、怎样设想、测试等。

本日这篇文章主要引见函数式编程的头脑。

  • 函数式编程有用吗?
  • 什么是函数式编程?
  • 函数式编程的长处。

面向对象编程(OOP)经由历程封装变化使得代码更轻易邃晓。

函数式编程(FP)经由历程最小化变化使得代码更轻易邃晓。

— Michacel Feathers(Twitter)

总所周知 JavaScript 是一种具有很多同享状况的动态言语,逐步的,代码就会积聚充足的复杂性,变得愚笨难以保护。面向对象设想能帮我们在肯定水平上处置惩罚这个题目,然则还不够。

因为有很多的状况,所以处置惩罚数据流和变化的通报显得尤为主要,不晓得你们晓得相应式编程与否,这类编程范式有助于处置惩罚 JavaScript 的异步或许事宜相应。总之,当我们在设想应用递次的时刻,我们应当斟酌是不是恪守了以下的设想准绳。

  • 可扩展性–我是不是须要不断地重构代码来支撑分外的功用?
  • 易模块化–假如我变动了一个文件,另一个文件是不是会受到影响?
  • 可重用性–是不是有很多反复的代码?
  • 可测性–给这些函数增加单元测试是不是让我纠结?
  • 易推理性–我写的代码是不黑白构造化严峻并难以推理?

我这能这么跟你说,一旦你学会了函数式编程,这些题目水到渠成,原本函数式编程就是这个头脑,一旦你掌握了函数式,然后你再进修相应式编程那就比较轻易懂了,这是我亲自体味的。我之前在学 Rxjs 的时刻是真的痛楚,说实话,Rxjs 是我学过最难的库了,没有之一。在经历过痛楚的一两个月以后,有些东西照样不能举一反三,晓得我近来研讨函数式编程,才以为是天经地义。毫无夸大,我也只管在背面的文章中给人人引见一下 Rxjs,这个话题我也在公司分享过。

什么是函数式编程?

简朴来讲,函数式编程是一种强调以函数运用为主的软件开辟作风。看到这句我想你照样一脸懵逼,不晓得函数式编程是啥,不要焦急,看到末了我相信你会邃晓的。

另有一点你要记着,函数式编程的目标是运用函数来笼统作用在数据之上的掌握流和操纵,从而在体系中消弭副作用削减对状况的转变。

下面我们经由历程例子来简朴的演示一下函数式编程的魅力。

如今的需求就是输出在网页上输出 “Hello World”

能够初学者会这么写。

document.querySelector('#msg').innerHTML = '<h1>Hello World</h1>'

这个递次很简朴,然则一切代码都是死的,不能重用,假如想转变音讯的花样、内容等就须要重写全部表达式,所以能够有履历的前端开辟者会这么写。

function printMessage(elementId, format, message) {
    document.querySelector(elementId).innerHTML = `<${format}>${message}</${format}>`
}

printMessage('msg', 'h1', 'Hello World')

如许确切有所改进,然则任然不是一段可重用的代码,假如是要将文本写入文件,不黑白 HTML,或许我想反复的显现 Hello World

那末作为一个函数式开辟者会怎样写这段代码呢?

const printMessage = compose(addToDom('msg', h1, echo))

printMessage('Hello World')

解释一下这段代码,个中的 h1echo 都是函数,addToDom 很明显也能看出它是函数,那末我们为何要写成如许呢?看起来多了很多函数一样。

实在我们是讲递次分解为一些更可重用、更牢靠且更轻易于邃晓的部份,然后再将他们组合起来,构成一个更轻易推理的递次团体,这是我们前面谈到的基础准绳。

compose 简朴解释一下,他会让函数从末了一个参数递次实行到第一个参数,compose 的每一个参数都是函数,不邃晓的能够查一下,在 redux 的中间件部份这个函数式英华。

能够看到我们是将一个使命拆分红多个最小颗粒的函数,然后经由历程组合的体式格局来完成我们的使命,这跟我们组件化的头脑很相似,将全部页面拆分红若干个组件,然后拼装起来完成我们的全部页面。在函数式编程内里,组合是一个非常非常非常主要的头脑。

好,我们如今再转变一下需求,如今我们须要将文本反复三遍,打印到掌握台。

var printMessaage = compose(console.log, repeat(3), echo)

printMessage(‘Hello World’)

能够看到我们变动了需求并没有去修正内部逻辑,只是重组了一下函数罢了。

能够看到函数式编程在开辟中具有声明形式。为了充足邃晓函数式编程,我们先来看下几个基础观点。

  • 声明式编程
  • 纯函数
  • 援用通明
  • 不可变性

声明式编程

函数式编程属于声明是编程范式:这类范式会形貌一系列的操纵,但并不会暴露它们是怎样完成的或是数据流怎样传过它们

我们所熟知的 SQL 语句就是一种很典范的声明式编程,它由一个个形貌查询效果应当是什么样的断言构成,对数据检索的内部机制举行了笼统

我们再来看一组代码再来对照一下敕令式编程和声明式编程。

// 敕令式体式格局
var array = [0, 1, 2, 3]
for(let i = 0; i < array.length; i++) {
    array[i] = Math.pow(array[i], 2)
}

array; // [0, 1, 4, 9]

// 声明式体式格局
[0, 1, 2, 3].map(num => Math.pow(num, 2))

能够看到敕令式很详细的通知盘算机怎样实行某个使命。

而声明式是将递次的形貌与求值星散开来。它关注怎样用种种表达式来形貌递次逻辑,而不肯定要指明其掌握流或状况关联的变化。

为何我们要去掉代码轮回呢?轮回是一种主要的敕令掌握构造,但很难重用,而且很难插进去其他操纵中。而函数式编程旨在只管的进步代码的无状况性和不变性。要做到这一点,就要学会运用无副作用的函数–也称纯函数

纯函数

纯函数指没有副作用的函数。雷同的输入有雷同的输出,就跟我们上学学的函数一样,经常这些状况会发生副作用。

  • 转变一个全局的变量、属性或数据构造
  • 转变一个函数参数的原始值
  • 处置惩罚用户输入
  • 抛出一个非常
  • 屏幕打印或纪录日记
  • 查询 HTML 文档,浏览器的 Cookie 或接见数据库

举一个简朴的例子

var counter = 0
function increment() {
    return ++counter;
}

这个函数就是不纯的,它读取了外部的变量,能够会以为这段代码没有什么题目,然则我们要晓得这类依靠外部变量来举行的盘算,盘算效果很难展望,你也有能够在其他处所修正了 counter 的值,致使你 increment 出来的值不是你预期的。

关于纯函数有以下性子:

  • 仅取决于供应的输入,而不依靠于任安在函数求值或挪用距离时能够变化的隐蔽状况和外部状况。
  • 不会形成超越作用域的变化,比方修正全局变量或援用通报的参数。

然则在我们日常平凡的开辟中,有一些副作用是难以避免的,与外部的存储体系或 DOM 交互等,然则我们能够经由历程将其从主逻辑中星散出来,使他们易于治理。

如今我们有一个小需求:经由历程 id 找到门生的纪录并渲染在浏览器(在写递次的时刻要想到能够也会写到掌握台,数据库或许文件,所以要想怎样让本身的代码能重用)中。

// 敕令式代码

function showStudent(id) {
    // 这里假如是同步查询
    var student = db.get(id)
    if(student !== null) {
          // 读取外部的 elementId
          document.querySelector(`${elementId}`).innerHTML = `${student.id},${student.name},${student.lastname}`
    } else {
        throw new Error('not found')
    }
}

showStudent('666')

// 函数式代码

// 经由历程 find 函数找到门生
var find = curry(function(db, id) {
    var obj = db.get(id)
    if(obj === null) {
        throw new Error('not fount')
    }
    
    return obj
})

// 将门生对象 format
var csv = (student) => `${student.id},${student.name},${student.lastname}`

// 在屏幕上显现
var append = curry(function(elementId, info) {
    document.querySelector(elementId).innerHTML = info
})

var showStudent = compose(append('#student-info'), csv, find(db))

showStudent('666')

假如看不懂 curry (柯里化)的先不焦急,这是一个关于新手来讲比较难邃晓的一个观点,在函数式编程内里起着至关主要的作用。

能够看到函数式代码经由历程较少这些函数的长度,将 showStudent 编写为小函数的组合。这个递次还不够圆满,然则已能够展现出比拟于敕令式的很多上风了。

  • 天真。有三个可重用的组件
  • 声明式的作风,给高阶步骤供应了一个清楚视图,增强了代码的可读性
  • 别的是将纯函数与不纯的行动星散出来。

我们看到纯函数的输出效果是一致的,可展望的,雷同的输入会有雷同的返回值,这个实在也被称为援用通明

援用通明

援用通明是定义一个纯函数较为准确的要领。纯度在这个意义上外表一个函数的参数和返回值之间映照的纯的关联。假如一个函数关于雷同的输入一直发生雷同的效果,那末我们就说它是援用通明

这个观点很轻易邃晓,简朴的举两个例子就好了。

// 非援用通明
var counter = 0

function increment() {
    return ++counter
}

// 援用通明
var increment = (counter) => counter + 1

实在关于箭头函数在函数式编程内里有一个嵬峨上的名字,叫 lambda 表达式,关于这类匿名函数在学术上就是叫 lambda 表达式,如今在 Java 内里也是支撑的。

不可变数据

不可变数据是指那些建立后不能变动的数据。与很多其他言语一样,JavaScript 里有一些基础范例(String,Number 等)从本质上是不可变的,然则对象就是在恣意的处所可变。

斟酌一个简朴的数组排序代码:

var sortDesc = function(arr) {
    return arr.sort(function(a, b) {
        return b - a
    })
}

var arr = [1, 3, 2]
sortDesc(arr) // [1, 2, 3]
arr // [1, 2, 3]

这段代码看似没什么题目,然则会致使在排序的历程当中会发生副作用,修正了原始援用,能够看到原始的 arr 变成了 [1, 2, 3]。这是一个言语缺点,背面会引见怎样战胜。

总结

  • 运用纯函数的代码绝不会变动或损坏全局状况,有助于进步代码的可测试性和可保护性
  • 函数式编程采纳声明式的作风,易于推理,进步代码的可读性。
  • 函数式编程将函数视为积木,经由历程一等高阶函数来进步代码的模块化和可重用性。
  • 能够应用相应式编程组合各个函数来下降事宜驱动递次的复杂性(这点背面能够会零丁拿一篇来举行解说)。

文章内容来至于《JavaScript函数式编程指南》

迎接关注个人民众号【前端桃园】

《javascript 函数式编程头脑》

    原文作者:leexiaoran
    原文地址: https://segmentfault.com/a/1190000017434776
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞