JavaScript+ES6 函数式编程

499 阅读3分钟

函数式编程是一种编程范式,函数式思维必不可少。

前言

函数式编程在前端已经是一个非常热门的话题,而且可以看到现在很多的代码库,例如React,都大量使用着函数式编程思想。

Q & A

问题:

  • 什么是函数式编程 函数式编程是一种范式,我们能够以此创建仅依赖输入值就可以完成自身逻辑的函数。这保证了当函数被多次调用时仍然返回相同的结果。
  • 函数式编程的好处是什么
  • Q: JavaScript 是函数式编程语言么 A: 不是(可以创建一个不接受参数,并且什么也不返回的函数),JavaScript 不是一种纯函数语言,更像是一种多范式语言。不过非常适合函数式编程范式。js 支持将函数作为参数,以及将函数传递给另外一个函数等特性-- 主要原因是 js 将函数视为一等公民。
  • 声明式、命令式、抽象 不要命令式

特性

  • 引用透明性(Referential Transparency): 所有的函数对于相同的输入都将返回相同的值。(在并发代码和可缓存代码中发挥重要作用)
  • 无副作用
  • 函数是一等公民

纯函数 & 好处

大多数函数式编程的好处来自于编写纯函数。

纯函数定义: 纯函数是对给定的注入返回相同的输出的函数。 纯函数的好处:

  • 纯函数产生可测试的代码,纯函数不应改变任何外部环境的变量。
  • 合理的代码
  • 并发的代码, 纯函数总是允许并发的执行函数
  • 可缓存,由于函数总是为给定的输入返回相同的输出(引用透明性),所有我们可以缓存函数的输出
  • 纯函数应该被设计为只做一件事。(只做一件事并把它做到完美是 UNIX 的哲学)
  • 纯函数可以组合

常见的函数式编程

闭包

高阶函数

高阶函数 HOC(Higher-Order Function): 高阶函数是接受函数作为其参数并且/或者返回函数座位输出的函数。

当一门语言允许函数作为任何其他数据类型使用时,函数被称为一等公民。也就是说函数可被赋值给变量,作为参数传递,也可被其他函数返回。

数组的函数式编程

const map = (arr, fn) => {
  if (!Array.isArray(arr)) return [];
  const result = [];
  for (let i = 0; i <= arr.length - 1; i++) {
    result.push(fn(arr[i], i));
  }
  return result;
};

const filter = (arr, fn) => {
  if (!Array.isArray(arr)) return [];
  const result = [];
  for (let i = 0; i <= arr.length - 1; i++) {
    fn(arr[i], i) ? result.push(arr[i]) : null;
  }
  return result;
};

const reduce = (arr, fn, initValue) => {
  let accumlator;
  let start = 0;
  if (initValue != undefined) {
    accumlator = initValue;
  } else {
    accumlator = arr[0];
  }

  if (initValue === undefined) {
    start = 1;
  }

  for (let i = start; i <= arr.length - 1; i++) {
    accumlator = fn(accumlator, arr[i], i);
  }

  return accumlator;
};

const zip = (leftArr, rightArr, fn) => {
  let i,
    result = [];
  for (i = 0; i < Math.min(leftArr.length, rightArr.length); i++) {
    result.push(fn(leftArr[i], rightArr[i]));
  }
  return result;
};

柯里化

柯里化: 把一个多参数函数转换为一个嵌套的一元函数的过程。

const curry = (fn) => {
  if (typeof fn !== 'function') {
    throw Error('fn is not function');
  }
  return function curryFn(...args) {
    if (args.length < fn.length) {
      return function () {
        return curryFn.apply(null, args.concat([].slice.call(arguments)));
      };
    }

    return fn.apply(null, args);
  };
};

// 利用柯里化查找数组中含有数字的项
const match = curry(function (expr, string) {
  return string.match(expr);
});
let hasNumber = match(/[0-9]+/);
let arrayFilter = curry(function (f, ary) {
  return ary.filter(f);
});
let findNumberInArray = arrayFilter(hasNumber);
console.log(findNumberInArray(['js', 'numqwe1'])); // 'numqwe1'

偏函数 (partial)

const partial = function (fn, ...partialArgs) {
  let args = partialArgs;

  return function (...fullArguments) {
    let argCount = 0;
    let _args = [...args];

    for (
      let i = 0;
      (i < fullArguments.length) & (argCount < fullArguments.length);
      i++
    ) {
      if (_args[i] === undefined) {
        _args[i] = fullArguments[argCount++];
      }
    }
    return fn.apply(null, _args);
  };
};

let obj1 = { foo: 'bar', bar: 'foo' };
let obj2 = { foo: 'test' };

let prettyPrintJson = partial(JSON.stringify, undefined, null, 2);

prettyPrintJson(obj1); // {"foo": "bar","bar": "foo"}
prettyPrintJson(obj2); // {"foo": "test"}

组合

compose 数据流从右往左

const compose = (...fns) => {
  if (fns.length === 0) return (arg) => arg;

  if (fns.length === 1) {
    return fns[0];
  }

  return (...values) => {
    return fns.reverse().reduce(function (ac, fn) {
      return fn(ac);
    }, values);
  };
};

// redux 中的compose
const composeA = (...fns) => {
  if (fns.length === 0) return (arg) => arg;

  if (fns.length === 1) {
    return fns[0];
  }

  return fns.reduce((a, b) => {
    return (...args) => {
      return a(b(...args));
    };
  });
};

const splitString = (str) => str.split(' ');
const countA = (arr) => arr.length;
const oddOrEven = (ip) => (ip % 2 === 0 ? 'even' : 'odd');
const countStringW = compose(oddOrEven, countA, splitString);

const result = countStringW('test work count string'); // even
console.log(result);

结束语

更多优质文章

参考: 《JavaScript+ES6 函数式编程》