函数式编程基本知识#
为什么要学函数式编程?#
在过去,他是命令式编程占据主流的。命令式编程是颗粒度最小的编程,它可以精准的控制每个元素的状态。
但是颗粒度小既意味着自由度高,也意味着复杂。因为颗粒度高所以你如果想要实现一个简单常用的功能,你必须从头开始去构建。很多基础设施都没有办法复用。
有一些函数是可以复用的,比如常见的比大小,排序,过滤等等。这些函数是可以复用的,但是在命令式编程中,这些函数都是附属品,而不是核心。
函数式编程是一种声明式的编程范式,它的核心是函数。函数式编程的核心是函数,而不是状态。
OK,所以函数式编程本质上学的是什么?如若说思想的话,那就是分层。将一个核心函数分成一个个附属函数的过程。如若说实现的化,说的就是附属函数的 API 语法。
什么是柯里化?#
如果说函数式编程中有两种操作是必不可少的那无疑就是柯里化(Currying)和函数组合(Compose),柯里化其实就是流水线上的加工站,函数组合就是我们的流水线,它由多个加工站组成。
f(a,b,c) → f(a)(b)(c)
我们尝试写一个 curry 版本的 add 函数
var add = function(x) {
return function(y) {
return x + y;
};
};
const increment = add(1);
increment(10); // 11
为什么这个单元函数很重要?还记得我们之前说过的,函数的返回值,有且只有一个嘛?
如果我们想顺利的组装流水线,那我就必须保证我每个加工站的输出刚好能流向下个工作站的输入。因此,在流水线上的加工站必须都是单元函数。
现在很好理解为什么柯里化配合函数组合有奇效了,因为柯里化处理的结果刚好就是单输入的。
函数式编程的相关语法#
JavaScript 中的函数式编程支持多种实用的函数,主要包括但不限于以下几类:
-
映射(Map):对数组中的每个元素应用一个函数,并创建一个新数组,该数组包含应用函数后的结果。
const numbers = [1, 2, 3, 4]; const doubled = numbers.map(x => x * 2); // [2, 4, 6, 8]
-
过滤(Filter):根据测试函数确定是否保留数组中的元素,创建一个包含通过测试的元素的新数组。
const numbers = [1, 2, 3, 4]; const evens = numbers.filter(x => x % 2 === 0); // [2, 4]
-
归约(Reduce):将数组元素组合起来,通过 reducer 函数减少为单个值。
Array.prototype.reduce()
方法在 JavaScript 中是一个非常强大的工具,用于将数组中的所有元素归并(或者说 “减少”)为单个值。这个方法对于累加数组中的值、合并数组内容或者在单个遍历中实现复杂的数据转换等操作非常有用。reduce
方法的基本用法如下:array.reduce(callback(accumulator, currentValue, currentIndex, array), initialValue)
就是回调函数的默认参数,callback:执行数组中每个值的函数,包含四个参数:
accumulator:累加器累加回调的返回值;它是上一次调用回调时返回的累积值,或者是提供的初始值(initialValue)。
currentValue:数组中正在处理的元素。
currentIndex(可选):数组中正在处理的当前元素的索引。如果提供了 initialValue,则起始索引号为 0,否则为 1。
array(可选):调用 reduce 方法的数组。initialValue(可选):作为第一次调用 callback 函数时第一个参数(accumulator)的值。如果没有提供初始值,则将使用数组的第一个元素。
const numbers = [1, 2, 3, 4]; const sum = numbers.reduce((accumulator, current) => accumulator + current, 0); // 10
-
每个(Every):检查数组中的所有元素是否都符合提供的测试函数。
无论如何 every 返回的都是布尔值,如果他接受的不是布尔值,他会将其转换成布尔值进行运算。
在 JavaScript 中,Array.prototype.every()
方法确实期望一个函数作为参数,这个函数对数组中的每个元素执行测试。如果回调函数对每个元素都返回一个真值(truthy value),every
方法才会返回true
;否则返回false
。
这个回调函数不一定非得返回布尔值(true
或false
)。JavaScript 中的任何值都可以被视为真值或假值(falsy value)。如果回调函数返回的值可以被转换成布尔上下文中的true
,那么这个值就被认为是真值。常见的假值包括0
、null
、undefined
、NaN
、空字符串""
和false
本身。除此之外的所有值都被认为是真值。
例如,如果回调函数返回数字1
,尽管1
不是布尔值,但它是一个真值,因此every
会将其解释为true
。如果回调函数返回0
(一个假值),every
会将其解释为false
。const numbers = [1, 2, 3, 4]; const allPositive = numbers.every(x => x > 0); // true
-
某个(Some):检查数组中是否至少有一个元素符合提供的测试函数。也与 every 同理。
const numbers = [1, 2, 3, 4]; const hasNegative = numbers.some(x => x < 0); // false
-
查找(Find):返回数组中满足提供的测试函数的第一个元素的值。
const numbers = [1, 2, 3, 4]; const firstEven = numbers.find(x => x % 2 === 0); // 2
-
查找索引(FindIndex):返回数组中满足提供的测试函数的第一个元素的索引。
const numbers = [1, 2, 3, 4]; const indexOFEven = numbers.findIndex(x => x % 2 === 0); // 1
-
排序(Sort):对数组的元素进行排序,并返回排序后的数组。
const numbers = [4, 2, 3, 1]; numbers.sort((a, b) => a - b); // [1, 2, 3, 4]
-
切片(Slice):返回数组的一部分,不修改原数组。返回一个新的数组,包含从 start(包括该元素) 到 end (不包括该元素)的 arrayObject 中的元素。
const numbers = [1, 2, 3, 4]; const middleTwo = numbers.slice(1, 3); // [2, 3]
这些函数是函数式编程在 JavaScript 中的基本工具,它们提供了一种声明式和不可变的编程范式,有助于写出更清晰、更易于维护的代码。
现在让我们去默写一遍这些功能。
简单默写一下你熟悉的函数式 JS,API
- every, 他的作用是将数组的每个 API 去执行一下回调,如果数组的所有元素回调返回的值为 true,那它就会返回为 true,如果有一个不为 true 就返回 false。
- same,和 every 同理,如果有一个返回为 true 就为 true,都返回 false 就返回 false。
- map,他的作用是映射,就是将一个数组里的每个数变成回调函数的返回值,然后最后组合起来返回一个数组。
- filter,他的作用是过滤,就是将满足回调函数的数保留起来,不满足回调函数,也就是回调函数返回 false 的数就删除,他接受的回调函数也是要接受一个布尔类型的。
- find,他的作用是将数组里每个数进行查询,如果数组的数满足回调函数,就返回这个数,回调接受的也是个布尔类型。
- findindex,和 find 一样不过它返回的是这个数的索引。
- sort,排序,将数组的数按规则排序,怎么按规则排序呢?他接受两个数,如果返回负数,那就是 a 在前,如果返回正数,那就是 b 在前,如果返回 0,那就是不变。最后也是返回排序后的数组。
- slice,切片,他接受两个参数,不接受回调。它的两个参数,一个是开始的元素一个是结束的元素,返回 [,) 的一个数组,就是不包括结束元素的数组。
- reduce,他是将整个数组归一化的函数,他可以让一数组变成一个数,简单的应用比如可以做累加器。也可以做数组变成列表的操作。他接受两个参数,一个是回调函数,一个是初始值,回调函数第一个值是之前的返回值,第二个值是当前值。
这些其实不只是函数化编程,更多的其实是 Array 的一些对象方法。那除此之外还有什么对象方法呢?#
concat()
:连接两个或更多的数组,并返回一个新的数组。copyWithin()
:在数组内部,将一系列元素从指定位置复制到另一个指定位置,并返回修改后的数组。entries()
:返回一个新的 Array Iterator 对象,该对象包含数组中每个索引的键 / 值对。every()
:测试数组的所有元素是否都通过了指定函数的测试。fill()
:使用一个固定值来填充数组中从起始索引到终止索引的全部元素。filter()
:创建一个新数组,其包含通过所提供函数实现的测试的所有元素。find()
:返回数组中满足提供的测试函数的第一个元素的值。findIndex()
:返回数组中满足提供的测试函数的第一个元素的索引。forEach()
:对数组的每个元素执行一次提供的函数。from()
:从类数组或可迭代对象中创建一个新数组实例。includes()
:判断一个数组是否包含一个指定的值,根据情况,如果包含则返回true
,否则返回false
。indexOf()
:返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回 - 1。isArray()
:判断传入的参数是否是一个数组。join()
:将数组中的所有元素连接成一个字符串并返回。keys()
:返回一个包含数组中每个索引键的 Array Iterator 对象。lastIndexOf()
:返回指定元素在数组中的最后一个的索引,如果不存在则返回 -1。map()
:创建一个新数组,其结果是该数组中的每个元素是调用一次提供的函数后的返回值。pop()
:从数组中删除最后一个元素,并返回该元素的值。push()
:向数组的末尾添加一个或更多元素,并返回新的长度。reduce()
:对累加器和数组中的每个元素(从左到右)应用一个函数,将其减少为单个值。reduceRight()
:接受一个函数作为累加器和数组的每个值(从右到左),将其减少到单个值。reverse()
:颠倒数组中元素的位置,第一个元素成为最后一个元素,最后一个元素成为第一个元素。shift()
:从数组中删除第一个元素,并返回该元素的值。slice()
:返回一个由开始和结束(不包括结束)决定的浅拷贝数组的一部分,并以新数组对象的形式返回。some()
:测试数组中的某些元素是否通过由提供的函数实现的测试。sort()
:对数组元素进行排序,并返回数组。splice()
:通过删除现有元素和 / 或添加新元素来更改一个数组的内容。toString()
:返回一个字符串,表示指定的数组及其元素。unshift()
:向数组的开头添加一个或更多元素,并返回新的长度。valueOf()
:返回数组对象的原始值。
新增方法:Array.of()
:创建一个具有可变数量参数的新数组实例,而不考虑参数的数量或类型。Array.at()
:接受一个整数值并返回该索引处的元素,允许使用正数和负数索引。Array.flat()
:根据指定的深度递归进入数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组。Array.flatMap()
:首先使用映射函数映射每个元素,然后将结果压缩成一个新数组。