美文网首页前端开发
每个 JavaScript 程序员都应该掌握这个工具!

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

作者: 老鱼的储物柜 | 来源:发表于2022-12-20 17:38 被阅读0次

    哈喽,我是老鱼!
    最近对一个工具库的使用上瘾了!这个给大家分享下。这是每个 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/

    写在最后

    我是老鱼,一名致力于在技术道路上的终身学习者、实践者、分享者!

    如果我的内容能对你有所帮助,一个赞同和喜欢鼓励一下下~

    相关文章

      网友评论

        本文标题:每个 JavaScript 程序员都应该掌握这个工具!

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