前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >每个 JavaScript 程序员都应该掌握这个工具!

每个 JavaScript 程序员都应该掌握这个工具!

作者头像
程序员老鱼
发布2022-12-22 15:54:04
6630
发布2022-12-22 15:54:04
举报

大家好,我是前端实验室的大师兄!

大师兄最近对一个工具库的使用上瘾了!这个给大家分享下。这是每个 JavaScript 程序员都应该掌握的工具:Ramda

简介

Ramda 是一款实用的 JavaScript 函数式编程库。类似的库中,大家最为熟悉的有Underscore、 Lodash等。

这时大家可能会问:

既然 Underscore 和 Lodash 已经这么流行了,为什么还要学习好像雷同的 Ramda 呢?

回答是,Ramda 强调更加纯粹的函数式风格。数据不变性和函数无副作用是其核心设计理念。这可以帮助你使用简洁、优雅的代码来完成工作。

Ramda 的目标更为专注:专门为函数式编程风格而设计,更容易创建函数式 pipeline、且从不改变用户已有数据。

对比区分

Underscore 和 Lodash的参数位置不对,把处理的数据放到了第一个参数。

var square = n => n * n;
_.map([4, 8], square) // [16, 64]

上面代码中,_.map的第一个参数[4, 8]是要处理的数据,第二个参数square是数据要执行的运算。

Ramda 的数据一律放在最后一个参数,理念是"function first,data last"

Ramda应该是目前最符合函数式编程的工具库,它需要操作的数据参数都是放在最后的。

var R = require('ramda');
R.map(square, [4, 8]) // [16, 64]

把要操作的数据放在最后一个参数,作为参数排列顺序,不仅满足 Ramda 的核心设计,同时使得所有方法都支持柯里化

安装

使用 node:

$ npm install ramda

或从 CDN 上获取:

<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.0/ramda.min.js"></script>

然后即可使用:

import * as R from 'ramda'
// OR
const R = require('ramda');

也就是说,所有多参数的函数,默认都可以单参数使用。也可以让你在只提供部分参数的情况下,轻松地在已有函数的基础上创建新函数。

// 写法一
R.map(square, [4, 8])

// 写法二
R.map(square)([4, 8])
// 或者
var mapSquare = R.map(square);
mapSquare([4, 8]);

上面代码中,写法一是多参数版本,写法二是柯里化以后的单参数版本。Ramda 都支持,并且推荐使用第二种写法。

今天,接下来是我总结的Ramda的几种常见的使用场景,展示怎样用 Ramda 写出既简洁易读,又方便扩展复用的代码。

使用集合迭代函数代替循环

.forEach

不必写显式for循环,而是用 forEach 函数代替循环。示例如下:

// Replace this:
for (const value of myArray) {
  console.log(value+5)
}
 
// with:
const printXPlusFive = x => console.log(x + 5);
R.forEach(printXPlusFive, [1, 2, 3]); //=> [1, 2, 3]
// logs 6
// logs 7
// logs 8

forEach 接受一个函数和一个数组,然后将函数作用于数组的每个元素。虽然 forEach 是这些函数中最简单的,但在函数式编程中它可能是最少用到的一个。forEach 没有返回值,所以只能用在有副作用的函数调用中。

.map

其实最常用的函数是 map。类似于 forEach,map 也是将函数作用于数组的每个元素。但与 forEach 不同的是,map 将函数的每个返回值组成一个新数组,并将其返回。示例如下:

R.map(x => x * 2, [1, 2, 3]) 
//=> [2, 4, 6]
//这里使用了匿名函数,但我们也可以在这里使用具名函数:
const double = x => x * 2
R.map(double, [1, 2, 3])
R.map(double, {x: 1, y: 2, z: 3}); //=> {x: 2, y: 4, z: 6}
.filter/ reject

.reject 接下来,我们来看看 filter 和 reject。就像名字所示,filter 会根据函数的返回值从数组中选择元素,例如:

const isEven = n => n % 2 === 0;

R.filter(isEven, [1, 2, 3, 4]); //=> [2, 4]

R.filter(isEven, {a: 1, b: 2, c: 3, d: 4}); //=> {b: 2, d: 4}

.filter 将函数(本例中为 isEven)作用于数组中的每个元素。每当函数返回 "true" 时,相应的元素将包含到结果中;反之当断言函数返回为 "falsy" 值时,相应的元素将从结果数组中排除(过滤掉)。

reject 是 filter 的补操作。 它保留使断言函数返回 "falsy" 的元素,排除使断言函数返回 "truthy" 的元素。

const isOdd = (n) => n % 2 === 1;

R.reject(isOdd, [1, 2, 3, 4]); //=> [2, 4]

R.reject(isOdd, {a: 1, b: 2, c: 3, d: 4}); //=> {b: 2, d: 4}
.find

find 将函数作用于数组中的每个元素,并返回第一个使函数返回真值的元素。

R.find(isEven, [1, 2, 3, 4]) //=> 2
const xs = [{a: 1}, {a: 2}, {a: 3}];
R.find(R.propEq('a', 2))(xs); //=> {a: 2}
R.find(R.propEq('a', 4))(xs); //=> undefined
.reduce

reduce 比之前遇到的其他函数要复杂一些。

reduce 接受一个二元函数(reducing function)、一个初始值和待处理的数组。

归约函数的第一个参数称为 "accumulator" (累加值),第二个参数取自数组中的元素;返回值为一个新的 "accumulator"。

先来看一个示例,然后看看会发生什么。

const subtract= (accum, value) => accum - value

R.reduce(subtract, 0, [1, 2, 3, 4]) // => ((((0 - 1) - 2) - 3) - 4) = -10
// - -10
// / \ / \
// - 4 -6 4
// / \ / \
// - 3 ==> -3 3
// / \ / \
// - 2 -1 2
// / \ / \
// 0 1 0 1
  • reduce 首先将初始值 5 和 数组中的首个元素 1 传入归约函数 subtract,subtract 返回一个新的累加值:0- 1 = -1。
  • reduce 再次调用subtract,这次使用新的累加值 -1 和 数组中的下一个元素 2 作为参数subtract返回 -3。
  • reduce 再次使用 -3和 数组中的下个元素 3 来调用 subtract,输出 -6。
  • reduce 最后一次调用subtract,使用 -6 和 数组中的最后一个元素 4 ,输出 -10。
  • reduce 将最终累加值 -10作为结果返回 以上关于集合的处理,是大多数库都或多或少涵盖了。这里主要是告知大家 Ramda 使用方法在参数排列的差异。

Ramda更重要的是接下来的这些内容。

函数的组合

Ramda 为简单的函数组合提供了一些函数。这使得我们能操作一些较为复杂的逻辑。

函数的合成

compose:将多个函数合并成一个函数,从右到左执行。

R.compose(Math.abs, R.add(1), R.multiply(2))(-4) // 7

pipe:将多个函数合并成一个函数,从左到右执行。

var negative = x => -1 * x;
var increaseOne = x => x + 1;

var f = R.pipe(Math.pow, negative, increaseOne);
f(3, 4) // -80 => -(3^4) + 1

converge:接受两个参数,第一个参数是函数,第二个参数是函数数组。传入的值先使用第二个参数包含的函数分别处理以后,再用第一个参数处理前一步生成的结果。

var sumOfArr = arr => {
  var sum = 0;
  arr.forEach(i => sum += i);
  return sum;
};
var lengthOfArr = arr => arr.length;

var average = R.converge(R.divide, [sumOfArr, lengthOfArr])
average([1, 2, 3, 4, 5, 6, 7])
// 4
// 相当于 28 除以 7

var toUpperCase = s => s.toUpperCase();
var toLowerCase = s => s.toLowerCase();
var strangeConcat = R.converge(R.concat, [toUpperCase, toLowerCase])
strangeConcat("Yodel")
// "YODELyodel"
// 相当于 R.concat('YODEL', 'yodel')
柯里化

curry:将多参数的函数,转换成单参数的形式。

var addFourNumbers = (a, b, c, d) => a + b + c + d;

var curriedAddFourNumbers = R.curry(addFourNumbers);
var f = curriedAddFourNumbers(1, 2);
var g = f(3);
g(4) // 10

partial:允许多参数的函数接受一个数组,指定最左边的部分参数。

var multiply2 = (a, b) => a * b;
var double = R.partial(multiply2, [2]);
double(2) // 4

var greet = (salutation, title, firstName, lastName) =>
  salutation + ', ' + title + ' ' + firstName + ' ' + lastName + '!';

var sayHello = R.partial(greet, ['Hello']);
var sayHelloToMs = R.partial(sayHello, ['Ms.']);
sayHelloToMs('Jane', 'Jones'); //=> 'Hello, Ms. Jane Jones!'

complement:返回一个新函数,如果原函数返回true,该函数返回false;如果原函数返回false,该函数返回true。

var gt10 = x => x > 10;
var lte10 = R.complement(gt10);
gt10(7) // false
lte10(7) // true
函数的执行

binary:参数函数执行时,只传入最前面两个参数。

var takesThreeArgs = function(a, b, c) {
  return [a, b, c];
};

var takesTwoArgs = R.binary(takesThreeArgs);
takesTwoArgs(1, 2, 3) // [1, 2, undefined]

tap:将一个值传入指定函数,并返回该值。

var sayX = x => console.log('x is ' + x);
R.tap(sayX)(100) // 100

R.pipe(
  R.assoc('a', 2),
  R.tap(console.log),
  R.assoc('a', 3)
)({a: 1})
// {a: 3}

zipWith:将两个数组对应位置的值,一起作为参数传入某个函数。

var f = (x, y) => {
  // ...
};
R.zipWith(f, [1, 2, 3])(['a', 'b', 'c'])
// [f(1, 'a'), f(2, 'b'), f(3, 'c')]

apply:将数组转成参数序列,传入指定函数。

var nums = [1, 2, 3, -99, 42, 6, 7];
R.apply(Math.max)(nums) // 42

还有诸如ascend升序排列、descend降序排列的方法等。

其他

Ramda 还提供了比较运算、数学运算、逻辑运算、字符串、数组、对象等的实用方法。

比如eqBy:比较两个值传入指定函数的运算结果是否相等。

R.eqBy(Math.abs, 5)(-5)
// true

比如divide:返回第一个参数除以第二个参数的商。

R.divide(71)(100) // 0.71

比如test:判断一个字符串是否匹配给定的正则表达式。

R.test(/^x/)('xyz')
// true

R.test(/^y/)('xyz')
// false

比如omit:过滤对象指定属性。

R.omit(['a', 'd'])({a: 1, b: 2, c: 3, d: 4})
// {b: 2, c: 3}

Ramda 提供的方法远不止这些。欢迎大家参阅官方链接。

官方地址: https://ramda.cn/

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-12-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端实验室 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 简介
  • 对比区分
  • 安装
  • 使用集合迭代函数代替循环
    • .forEach
      • .map
        • .filter/ reject
          • .find
            • .reduce
            • 函数的组合
              • 函数的合成
                • 柯里化
                  • 函数的执行
                  • 其他
                  相关产品与服务
                  内容分发网络 CDN
                  内容分发网络(Content Delivery Network,CDN)通过将站点内容发布至遍布全球的海量加速节点,使其用户可就近获取所需内容,避免因网络拥堵、跨运营商、跨地域、跨境等因素带来的网络不稳定、访问延迟高等问题,有效提升下载速度、降低响应时间,提供流畅的用户体验。
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档