美文网首页
【JavaScript ES6 函数式编程入门经典】读书笔记(第

【JavaScript ES6 函数式编程入门经典】读书笔记(第

作者: 八宝君 | 来源:发表于2019-08-02 17:12 被阅读0次
    就是这本书

    写在前面的话

    之前想看函数式编程时找了许多资料,许多对这个只是大概的描述了一段,然后看到有人推荐这本书,所以写下这本读书笔记,给有需要的人参考。

    第一章 函数式编程简介

    函数的第一条原则是要小。函数的第二条原则是要更小。 ——ROBERT C.MARTIN

    1.1 什么是函数式编程?

    f(x) = Y
    
    • 函数必须总是接受一个参数
    • 函数必须总是返回一个值
    • 函数应该依据接收到的参数(X)而不是外部环境运行
    • 对于一个给定的X,只会输出唯一的一个Y

    函数式编程技术主要基于数学函数它的思想。<br />
    看个栗子:

    var percentValue = 5;
    var caculateTax = (value) => {
      return value/100 * (100 + percentValue)
    }
    

    这个caculateTax依赖于全局变量percentValue, 因此在数学意义上不能被称作真正的函数。修改成:

    var caculteTax = (value, percentValue) => {
      return value/100 * (100 + percentValue)
    }
    

    现在caculateTax算得上一个真正的函数了,通过这个简单的栗子,可以简单定义一下函数式编程:函数式编程是一种范式,我们能够一次创建仅依赖输入就可以完成自身逻辑的函数。这保证了当函数被多次调用时仍然返回相同的结果,不会改变任何外部环境的变量,这将产生可缓存、可测试的代码库。

    函数与javascript方法
    函数是一段可以通过其名称被调用的代码。它可以传递参数并返回值。
    var simple = (a) => { return a } // 一个简单的函数
    simple(5) // 用其名称调用
    然而方法是一段必须通过其名称及其关联对象的名称被调用的代码。
    var obj = { simple : (a) =>{ return a } }
    obj.simple(5) // 用其名称及其关联对象调用

    1.2 引用透明性

    所有的函数对于相同的输入都将返回相同的值,这一特性被称为引用透明性。

    一个简单的函数

    var identity = (i) => { return i }
    

    该函数满足引用透明性,现在它被用于其它函数调用之间。

    sum(4, 5) + identity(1)
    

    根据引用透明性,上面的语句可以转换成

    sum(4, 5) + 1
    

    这个过程被称为替换模型, 因为可以直接替换函数的结果(主要是因为函数的逻辑不依赖其它全局变量),这与它的值是一样的。这也使得并发代码缓存成为可能。

    遵循引用透明性的函数只依赖来自参数的输入, 不依赖全局数据。因此线程可以自由地运行,没有任何锁机制。

    引用透明性是一种哲学
    “引用透明性”一词来自分析哲学。该哲学分支研究自然语言的语义及其含义。单词“Referential”或“Referent”意指表达式引用的事物。句子中的上下文是“引用透明的”,如果用另一个引用相同实体的词语替换上下文中的一个词语,并不会改变句子的含义。
    替换函数的值并不影响上下文,这就是函数式编程的哲学!

    1.3 命令式、声明式与抽象

    函数式编程主张声明式编程和编写抽象的代码。

    来看个栗子:

    // 打印数组的每个元素
    var array = [1, 2, 3]
    for (i = 0; i < array.length; i++)
      console.log(array[i]) // 打印 1, 2, 3
    

    用数组长度的索引计算结果编写了一个隐式的for循环并打印出数组项,"打印数组的元素",但是看起来像是在告诉编译器该做什么,在上面栗子中,告诉编译器“要获得数组长度、循环数组、用索引获取每一个数组元素,等等”我们将之称为“命令式”解决方案。命令式编程主张告诉编译器“如何”做。

    现在来看另外一方面,声明式编程。在声明式编程中,我们要告诉编译器做“什么”,而不是“如何”做。“如何”做的部分将被抽象到普通函数中(这些函数被称为高阶函数),更改如下:

    var array = [1, 2, 3]
    array.forEach((element) => {
      console.log(element) // 打印 1, 2, 3
    })
    

    这个代码片段移除了如何做的部分,只关心做什么的部分。

    函数式编程主张以抽象的方式创建函数,这些函数能够在代码的其他部分被重用。

    1.4 纯函数

    大多数函数式编程的好处来自于编写纯函数,那么什么是纯函数?

    纯函数:对给定的输入返回相同的输出的函数。

    var double = (value) => value * 2
    

    这个"double"函数就是一个纯函数,遵循引用透明性。

    • 纯函数产生可测试的代码
      不纯的函数具有副作用,例如之前的这个函数
    var percentValue = 5;
    var caculateTax = (value) => {
      return value/100 * (100 + percentValue)
    }
    

    caculateTax不是纯函数,因为它依赖外部环境计算其逻辑。假设在运行相同的测试用例时,外部环境也在改变变量percentValue的值,那么之前给的定输入对应的就不是唯一的值了。

    纯函数有一个重要属性:纯函数不应改变任何外部环境的变量。换而言之,纯函数不应依赖任何外部变量也不应改变任何外部变量。

    // 举个反例
    bar global = 'globalValue'
    var badFunction = (value) => {
      global = 'changed'
      return value * 2
    }
    

    看看这个反例,当badFunction被调用时,它将全局变量global的值变更了,假设另一个函数的逻辑依赖global变量的话,调用badFunction就影响了其他函数的行为。具有这种性质的函数会使得代码库变得难以测试。

    • 合理的代码
      通过创建和使用纯函数,能够让我们非常简单的推理代码或函数。函数(无论它是否为纯函数)必须总是具有一个有意义的名称。例如double加倍函数就不能命名成dd

    用一个内置的Math.max函数来测试一下推理能力。
    给定函数调用:
    Math.max(3, 4, 5, 6)
    为了给出结果,是不是没有看max的实现?为什么?因为Math.max是纯函数。

    1.5 并发代码

    纯函数总是允许我们并发地执行代码,因为纯函数不会改变它的环境。当然,js并没有真正的多线程来并发地执行函数,但如果你的项目使用了webworker来模拟多线程并行执行任务,这种时候就需要用纯函数来代替非纯函数。
    举个栗子:

    let global = "something"
    let function1 = (input) => {
      // 处理input
      // 改变global
      global = "somethingElse"
    }
    let function2 = () => {
      if (global === "something") {
        // 业务逻辑
      }
    }
    

    如果我们需要并发的执行function1function2,假设线程T-1选择function1执行,线程T-2选择function2执行,如果T-1在T-2之前执行,那么并发执行这些函数就会引起不良反应,现在把它们改为纯函数。

    let function1 = (input, global) => {
      // 处理input
      // 改变global
      global = "somethingElse"
    }
    let function2 = (global) => {
      if (global === "somethins") {
        // 业务逻辑
      }
    }
    

    移动了global变量,把它作为两个函数的参数,使它们变成纯函数。由于函数并不依赖外部环境(global 变量),因此不必像之前那样担心线程的执行顺序。

    1.6 可缓存

    纯函数总是为给定的输入返回相同的输出,所以缓存纯函数的输出也是可能的。

    举个栗子,有这么一个做耗时计算的函数。

    var longRunningFunction = (ip) => {
      // do long running tasks and return
    }
    

    然后我们有一个记账对象,它存储了longRunningFunction函数的所有调用结果。

    var longRunningFnBookKeeper = {
      2: 3,
      4: 5,
      ...
    }
    

    使用纯函数的定义,在调用longRunningFunction之前检查key是否存在longRunningFnBookKeeper中,比如说:

    // 检查key是否在longRunningFnBookKeeper中
    // 如果在,则返回结果,否则更新记账对象
    longRunningFnBookKeeper.hasOwnProperty(ip) ?
      longRunningFnBookKeeper[ip] :
      longRunningFnBookKeeper[ip] = longRunningFunction(ip)
    

    这样就是缓存了纯函数的调用结果。

    1.7 管道与组合

    使用纯函数,我们只需要在函数中做一件事。纯函数应该被设计为只做一件事。只做一件事并把它做到完美是UNIX的哲学,我们在实现纯函数时也将遵循这一原则。UNIX和LINUX平台有很多用户日常任务的命令,例如:cat用于打印文件内容,grep用于搜索文件,wc用于计算行数。这些命令的确一次只解决一个问题,但是可以通过组合或者管道来完成复杂的任务。假如我们要在一个文件中找到一个特定的名称并统计它出现次数,在命令提示符中命令如下:

    cat jsBook | grep -i "composing" | wc

    组合不是UNIX/LINUX命令行独有的,但它们是函数式编程范式的核心。称为函数式组合。

    javascript不支持组合函数函数的操作符"|",但是我们可以创建自己的支持组合的函数。

    1.8 纯函数是数学函数

    先见1.6 “可缓存”中的那段longRunningFunctionlongRunningFnBookKeeper代码。
    假设通过多次调用,longRunningFnBookKeeper增长为如下的对象:

    longRunningFnBookKeeper = {
      1: 32,
      2: 4,
      3: 5,
      5: 6,
      11: 44
    }
    

    分析一下该对象,可见longRunningFnBookKeeper接受一个输入并为给定的范围映射输出,此处的关键是,输入具有强制的、相应的输出。在key中也不存在映射两个输出的输入。

    比对数学函数的定义(维基百科看不了),基本上与纯函数一致,从上述例子也能看到数学函数的思想被借鉴到函数式范式的世界。

    1.9 javascript是函数式编程语言吗

    javascript是函数式编程语言,函数式编程主张函数必须接受至少一个参数并返回一个值。坦率的说,可以创建一个不接收参数并且实际上什么也不返回的函数,例如这个:var useless = () => {},这串代码执行并不会报错,原因是javascript不是一种纯函数语言(比如说Haskell),而更像是一种多范式语言。

    javascript支持将函数作为参数,以及将函数传递给另一函数等特性——主要原因是javascript将函数视为一等公民,由于函数定义的约束,开发者需要在创建javascript函数时将其考虑在内。

    相关文章

      网友评论

          本文标题:【JavaScript ES6 函数式编程入门经典】读书笔记(第

          本文链接:https://www.haomeiwen.com/subject/ebdbrctx.html