JS进阶 - 函数式编程

179 阅读5分钟

概述

今天介绍下函数式编程,在开始之前,先要了解一下什么是编程范式。编程范式,通俗解释就是编写代码的风格。常见的编程范式有命令式编程、声明式编程、以及函数式编程等。

命令式编程

编写代码,关注代码执行的步骤,即先做什么,再做什么。

声明式编程

通过代码编写,指定应该做什么,但不指定具体怎么去做。如Vue、React

函数式编程

函数式编程有两个特点:

  • 函数是一等公民
  • 函数是纯函数

在 JavaScript 中,函数是一等公民,函数跟其它数据类型一样使用,可以赋值给其它变量,也可以作为参数传入另一个函数,或者作为其它函数的返回值。纯函数是什么意思呢?

纯函数

定义

纯函数是函数式编程中非常重要的概念,纯函数有两个特点:

  • 相同的输入总会得到相同的输出

  • 执行过程中不会产生副作用

副作用是表示,表示在执行一个函数时,除了返回函数值之外,还对调用函数产生了附加的影响。比如修改全局变量、修改参数、或改变了外部的存储等。

// 输出确定、没有副作用,是纯函数
function sum(x, y) {
  return x + y;
}

// 输出不确定,不是纯函数
function random(x) {
  return Math.random() * x;
}

// 有副作用,不是纯函数
function setFontSize(el, color) {
  el.style.color = color;
}

// 输出不确定、有副作用,不是纯函数
var count = 0;
function addCount(x) {
  count += x;
  return count;
}

纯函数的优点

  1. 因为你可以安心的编写和安心的使用
  2. 在写的时候保证了函数的纯度,只是单纯实现自己的业务逻辑即可,不需关心传入的内容是如何获取或者依赖其它的外部变量,是否已经发生了修改
  3. 用的时候,你确定输入的内容不会被任意篡改,并且自己确定的输入,一定会有确定的输出

柯里化

柯里化也是属于函数式编程里非常重要的概念。

定义

柯里化又称卡瑞化或加里化,是把接收多个参数的函数,变成接收一个单一参数(最初函数的第 1 个参数)的函数,并且返回接收余下参数,而且返回结果的新函数的技术。也属于函数式编程里非常重要的概念。

个人理解就是,只传递给函数一部分参数来调用它,让它返回一个函数去处理余下的参数,这个过程就是柯里化。

// 未柯里化的函数
function add1(x, y, z) {
  return x + y + z;
}

var result1 = add1(10, 20, 30);
console.log(result1); // 60

// 柯里化处理的函数
function add2(x) {
  return function (y) {
    return function (z) {
      return x + y + z;
    };
  };
}

var result2 = add2(10)(20)(30);
console.log(result2); // 60

// 柯里化处理的箭头函数
var add3 = (x) => (y) => (z) => x + y + z;
var result3 = add3(10)(20)(30);

console.log(result3); // 60

柯里化的优点

  1. 可以将一个函数处理的问题尽可能单一,而不是将一大堆的处理过程交给一个函数来处理
  2. 将每次传入的参数在单一的函数中进行处理,处理完成之后在下一个函数中再使用处理后的结果。
  3. 如下所示,对传入的参数分别进行处理
// 对传入的参数需要分别进行处理
function add(x) {
  x = x + 2;
  return function (y) {
    y = y * 2;
    return function (z) {
      z = z ** 2;
      return x + y + z;
    };
  };
}

var result = add(10)(20)(30);
console.log(result); // 952
  1. 可以复用业务逻辑
// makeAdder函数要求传一个num,在之后使用返回的函数时,就不需要再传入num了。
// 并且如果需要的话,可以在这里对num进行一些修改

function makeAdder(num) {
  return function (count) {
    return num + count;
  };
}

var add5 = makeAdder(5);
var result1 = add5(10);

console.log(result1); // 15

var add10 = makeAdder(10);
var result2 = add10(10);

console.log(result2); // 20

柯里化的实现

如果每次都手动进行函数柯里化,书写会很麻烦,可以创建一个自动柯里化的函数,返回传入函数的柯里化结果

function myCurry(fn) {
  function curried(...args) {
    // 当已经传入的参数大于等于需要的参数时, 就执行函数
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    } else {
      // 参数个数没有达到时, 需要返回一个新的函数, 继续来接收的参数
      function curried2(...args2) {
        // 接收到参数后, 需要递归调用curried
        return curried.apply(this, [...args, ...args2]);
      }
      return curried2;
    }
  }
  return curried;
}

function add(x, y, z) {
  return x + y + z;
}

// 将add函数柯里化
curryAdd = myCurry(add);

console.log(curryAdd(10, 20, 30)); // 60
console.log(curryAdd(10)(20)(30)); // 60
console.log(curryAdd(10, 20)(30)); // 60
console.log(curryAdd(10)(20, 30)); // 60

组合函数

组合(compose)函数是在 JavaScript 开发过程中的一种函数的使用技巧、模式

  • 比如现在需要对某一个数据进行函数调用,执行两个函数 fn1 和 fn2,这两个函数是依次调用的
  • 如果每次我们都需要进行两个函数的调用,操作上会显得重复 是否可以将两个函数组合起来,自动依次调用呢?这个过程就是对函数的组合,称为组合函数(Compose Function)
function compose(fn1, fn2) {
  return function (x) {
    // 按传入顺序,从左往右,依次执行函数
    return fn2(fn1(x));
  };
}

function double(num) {
  return num * 2;
}

function square(num) {
  return num ** 2;
}

var calcFn = compose(double, square);
console.log(calcFn(10)); // 400

组合函数的实现

上述示例组合了两个函数的调用,为了更方便的创建组合函数,可以创建一个自动组合的函数,返回传入各函数组合后的函数

function myCompose(...fns) {
  var length = fns.length;
  for (var i = 0; i < length; i++) {
    if (typeof fns[i] !== "function") {
      throw new TypeError("Expected arguments are functions");
    }
  }

  function compose(...args) {
    // 按传入顺序,从左往右,依次执行函数
    var index = 0;
    var result = length ? fns[index].apply(this, args) : args;
    while (++index < length) {
      result = fns[index].call(this, result);
    }
    return result;
  }
  return compose;
}

function double(num) {
  return num * 2;
}

function square(num) {
  return num ** 2;
}

function add100(num) {
  return num + 100;
}

var calcFn = myCompose(double, square, add100);
console.log(calcFn(10)); // 500  10 * 2 ** 2 + 10

下一篇,计划梳理 JavaScript 中的callapplybind

  • 如果认为本篇知识点梳理的尚可,欢迎点赞
  • 如果有需要补充、指正的地方,也欢迎评论留言哦~