关于javascript:JS与函数式编程

41次阅读

共计 4658 个字符,预计需要花费 12 分钟才能阅读完成。

函数式编程是什么

函数式编程是一种 编程范式,次要是利用函数把运算过程封装起来,通过组合各种函数来计算结果。

函数式编程的特点

1、函数是“一等公民”

所谓 ” 第一等公民 ”,指的是函数与其余数据类型一样,处于平等位置,能够赋值给其余变量,也能够 作为参数,传入另一个函数,或者作为别的函数的返回值。

在 JavaScript 外面,函数也是对象,第一等公民的位置妥妥的。

比方最常见的 setTimeout 函数,其第一个参数就是一个函数:

setTimeout(() => {console.log('hello')
}, 1000)

2、只用“表达式”,不必“语句”

“ 表达式 ”(expression)是一个单纯的运算过程,总是有返回值;” 语句 ”(statement)是执行某种操作,没有返回值。函数式编程要求,只应用表达式,不应用语句。也就是说,每一步都是单纯的运算,而且都有返回值。

3、没有“副作用”

所谓 ” 副作用 ”,指的是 函数外部与内部互动,产生运算以外的其余后果。比方:发送 HTTP 申请、批改全局变量等。

纯正的函数式编程语言编写的函数 没有变量 ,因而,任意一个函数,只有输出是确定的,输入就是确定的,这种 纯函数 称之为没有副作用的。而因为 Python、JavaScript 等语言容许应用变量,所以它们不是 纯函数式编程语言 ,只是对函数式编程提供 局部反对

函数式编程强调没有 ” 副作用 ”,意味着函数要放弃独立,返回一个 新的值,没有其余行为,尤其是不得批改内部变量的值。

在其余类型的语言中,变量往往用来保留 ” 状态 ”(state)。不批改变量,意味着状态不能保留在变量中。函数式编程应用参数保留状态,最好的例子就是递归。

因为递归调用是十分耗费内存的,尤其是递归深度很深时,容易产生栈溢出。能够通过 尾递归 进行优化。

4、惰性求值

惰性求值(lazy evaluation,也称作 call-by-need)是这样一种技术:是在将表达式赋值给变量(或称作绑定)时并不计算表达式的值,而在变量第一次被应用时才进行计算。这样就能够通过防止不必要的求值晋升性能。

最常见的例子就是 Vue 中路由的懒加载:

const List = () => import('@/components/list.vue') // 只是定义了一个函数,没有执行 import 的动作
const router = new VueRouter({
  routes: [{ path: '/list', component: List}
  ]
})

5、援用通明

援用通明(Referential transparency),指的是函数的运行不依赖于内部变量或 ” 状态 ”,只依赖于输出的参数,任何时候只有参数雷同,援用函数所失去的返回值总是雷同的。

其余类型的语言,函数的返回值往往与零碎状态无关,不同的状态之下,返回值是不一样的。这就叫 ” 援用不通明 ”,很不利于察看和了解程序的行为。

6、无锁并发

函数式编程不须要思考 ” 死锁 ”(deadlock),因为它不批改变量,所以基本不存在 ” 锁 ” 线程的问题。不用放心一个线程的数据,被另一个线程批改,所以能够很释怀地把工作摊派到多个线程,部署 ” 并发编程 ”(concurrency)。

看上面的代码:

let s1 = Op1()
let s2 = Op2()
let s3 = concat(s1, s2)

因为 s1 和 s2 互不烦扰,不会批改变量,谁先执行是无所谓的,所以能够释怀地减少线程,把它们调配在两个线程上实现。其余类型的语言就做不到这一点,因为 s1 可能会批改零碎状态,而 s2 可能会用到这些状态,所以必须保障 s2 在 s1 之后运行,天然也就不能部署到其余线程上了。

函数式编程与编程范式

文章一开始提到:函数式编程是一种编程范式。那么还有哪些支流的编程范式?

常见的三种编程范式:命令式编程、面向对象编程和函数式编程。

命令式编程

命令式编程关怀解决问题的 步骤 ,而函数式编程关怀 数据的映射

举例来说,当初有这样一个数学表达式:

(1 + 2) * 3 - 4

命令式编程可能这样写:

let a = 1 + 2;
let b = a * 3;
let c = b - 4;

函数式编程要求应用函数,咱们能够把运算过程定义为不同的函数,而后写成上面这样:

let result = subtract(multiply(add(1,2), 3), 4)

面向对象编程

怎么为一个模糊不清的问题找到一个最失当的形容?形象(Abstraction)通常是咱们用来简化简单的事实问题的办法。

在面向对象程序编程里,计算机程序会被设计成彼此相干的 对象 。对象则指的是 类的实例 。它将对象作为程序的根本单元,将程序和数据封装其中,以进步软件的 重用性、灵活性和扩展性,对象里的程序能够拜访及常常批改对象相干连的数据。对象蕴含数据(字段、属性)与办法。

三种编程范式的比拟

这三种编程范式各自的特点:

  • 命令式编程的外围在于 模块化,在实现过程中应用了状态,依赖了内部变量,导致很容易影响左近的代码,可读性较差,前期的保护老本也较高;
  • 函数式编程的外围在于 防止副作用,不扭转也不依赖以后函数外的数据。联合不可变数据、函数是第一等公民等个性,使函数带有自描述性,可读性较高;
  • 面向对象编程的外围在于 形象,提供清晰的对象边界。联合封装、集成、多态个性,升高了代码的耦合度,晋升了零碎的可维护性;

JS 中的函数式编程

之前提到过,因为 JavaScript 容许应用变量,所以它不是纯函数式编程语言。然而毫无疑问,JS 中有很多函数式编程的利用,比方在 ES5/ES6 规范中的箭头函数、迭代器、map、filter、reduce 等。在前端框架中,Redux 的纯函数,React16.8 推出的 hooks,Vue3.0 的 composition Api 等,也都是函数式编程的利用。

函数柯里化

函数柯里化指的是一种将一个 多元函数 ,转换成一个顺次调用的 单元函数

举个例子,实现一个求和函数:

add(1,2,3) // 一次性承受 3 个参数

通过函数柯里化的解决,变成这样:

addCurry(1)(2)(3) // 每次接管 1 个参数

通常,咱们在实践中应用柯里化都是为了把某个函数变得 单值化 ,这样能够 减少函数的多样性,使得其适用性更强:

const replace = curry((a, b, str) => str.replace(a, b)) // curry 是实现柯里化的函数,参数是一个函数
const replaceSpaceWith = replace(/\s*/)
const replaceSpaceWithComma = replaceSpaceWith(',')
const replaceSpaceWithDash = replaceSpaceWith('-')

通过下面这种形式,咱们从一个 replace 函数中产生很多新函数,能够在各种场合进行应用。

手动实现函数柯里化:

function curry(fn, ...args) {
    return args.length < fn.length
        // 第一个参数传 null 不扭转 this 指向,而且能够在后续的调用中去传入参数
        ? curry.bind(null, fn, ...args) // 通过 bind 实现保留每次输出的 args
        : fn(...args)
}

函数组合

函数组合就是将不同性能的函数组合在一起,顺次对传入的参数做解决,就像一个流水线一样。

比方有三个函数:

const f = x => x + 1
const g = x => x * 2
const t = (x, y) => x + y

如果传入的参数是 1、2,解决的程序是:f => g => t,后果就是3 => 6 => 7

如果有一个函数 compose 能实现函数组合,那么:

let fgt = compose(f, g, t)
fgt(1, 2) // 3 -> 6 -> 7

能够手动实现一下 compose 函数:

const compose = (...fns) => (...args) => fns.reduceRight((val, fn) => fn.apply(null, [].concat(val)), args)

再举个函数组合的利用,比方要将数组最初一个元素大写,假如 log, headreversetoUpperCase 函数存在。

命令式的写法:

log(toUpperCase(head(reverse(arr))))

面向对象的写法:

arr.reverse()
  .head()
  .toUpperCase()
  .log()

当初通过组合,如何实现之前的性能:

const upperLastItem = compose(log, toUpperCase, head, reverse)

高阶函数

高阶函数,通常是指一个函数同时具备:

  • 将一个或多个函数作为参数,或
  • 返回一个函数作为后果

而在 JavaScript 中,最罕用到的 高阶函数就是:filtermapreduce

React 中的函数式编程

能够从一下几个个性找到 React 中函数式编程的体现:

函数式组件和 Hook

Hook是 React16.8 的新个性,能够在不应用类组件的状况下,应用 state 以及其余的 React 个性。这样就使得函数式组件的性能更加弱小。

Hook解决的问题:

  • 函数式组件不能应用 state:函数式组件比类组件更简洁好用,而 Hook 让它更加丰盛弱小;
  • 副作用问题 :诸如数据获取、订阅、定时执行工作、手动批改ReactDOM 这些行为都能够称为副作用;而 Hook 的呈现能够应用 useEffect 来解决这些副作用;
  • 有状态的逻辑重用组件
  • 简单的状态治理 :之前通常应用reduxdvamobx 这些第三方状态管理器来治理简单的状态,而 Hook 能够应用 useReduceruseContext 配合实现简单的状态治理;
  • 开发效率和品质问题:函数式组件比类组件简洁,效率高,性能也好。

数据是不可变的

  • 在 React 中,强调一个组件不能去批改传入的 prop 值,这遵循了 Immutable 的准则;
  • 在 Redux 中,更是强调 Immutable 的作用,每个 reducer 不可能批改 state,只能返回一个新的 state;

纯函数

  • 在 React 中,组件的 render 函数应该是一个纯函数。只有这样,组件渲染的后果才只和 state/props 有关系,遵循 UI = f(state) 这个公式;
  • 在 Redux,reducer 必须是一个纯函数,也是函数式编程的要求;

高阶组件

React 中的高阶组件(HOC)也是函数式编程的利用。

高阶组件是一个函数,接管一个组件,返回一个新组件。高阶组件就是设计模式里的装璜者模式。HOC 是 纯函数,没有副作用。

const EnhancedComponent = highOrderComponent(WrappedComponent);

高阶组件是 React 中实现代码复用的形式之一。

Vue 中的函数式编程

Composition API

Vue3 中的组合式 API 让人一下就想到了 React 中的Hook,这不也是函数式编程么?

  • vue2 是选项式 API,将 mounted,data,computed,watch 之类的办法作为一个对象的属性进行导出;
  • vue3 新增了一个名为 setup 的入口函数,value, computed, watch, onMounted等办法都须要从内部导入;

在 vue3 中,咱们能够像写一个办法一样去写这个组件的 JS 逻辑局部,应用 import 来按需引入。

这样的益处不言而喻,首先就是咱们须要写的代码量少了,其次就是咱们能够封装更多的子函数、援用更多的公共函数去保护咱们的代码,第三就是代码的可读性变高了。

参考链接

  • 函数式编程初探
  • 常见的 4 种编程范式比拟
  • 编程范式与函数式编程
  • 扼要 JavaScript 函数式编程——入门篇
  • React 世界的函数式编程

正文完
 0