夜猫子的知识栈 夜猫子的知识栈
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《Web Api》
    • 《ES6教程》
    • 《Vue》
    • 《React》
    • 《TypeScript》
    • 《Git》
    • 《Uniapp》
    • 小程序笔记
    • 《Electron》
    • JS设计模式总结
  • 《前端架构》

    • 《微前端》
    • 《权限控制》
    • monorepo
  • 全栈项目

    • 任务管理日历
    • 无代码平台
    • 图书管理系统
  • HTML
  • CSS
  • Nodejs
  • Midway
  • Nest
  • MySql
  • 其他
  • 技术文档
  • GitHub技巧
  • 博客搭建
  • Ajax
  • Vite
  • Vitest
  • Nuxt
  • UI库文章
  • Docker
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

夜猫子

前端练习生
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《Web Api》
    • 《ES6教程》
    • 《Vue》
    • 《React》
    • 《TypeScript》
    • 《Git》
    • 《Uniapp》
    • 小程序笔记
    • 《Electron》
    • JS设计模式总结
  • 《前端架构》

    • 《微前端》
    • 《权限控制》
    • monorepo
  • 全栈项目

    • 任务管理日历
    • 无代码平台
    • 图书管理系统
  • HTML
  • CSS
  • Nodejs
  • Midway
  • Nest
  • MySql
  • 其他
  • 技术文档
  • GitHub技巧
  • 博客搭建
  • Ajax
  • Vite
  • Vitest
  • Nuxt
  • UI库文章
  • Docker
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • ES6简介
  • 变量的解构赋值
  • 字符串的扩展
  • 字符串的新增方法
  • 正则的扩展
  • 数值的扩展
  • 函数的扩展
  • 数组的扩展
  • 对象的扩展
  • 对象的新增方法
  • Symbol
  • Set 和 Map 数据结构
  • Proxy
  • Reflect
  • Promise 对象
  • Iterator 和 for-of 循环
  • Generator 函数的语法
  • Generator 函数的异步应用
  • async 函数
  • Class 的基本语法
  • Class 的继承
  • Module 的语法
  • Module 的加载实现
  • 编程风格
  • 读懂ES6规格
  • 异步遍历器
  • ArrayBuffer
  • 最新提案
  • 装饰器
  • 函数式编程
    • 柯里化
    • 函数合成
    • 参数倒置
    • 执行边界
    • 队列操作
    • 合并操作
    • 配对操作
    • 参考链接
  • Mixin
  • SIMD
  • ES6中的链判断符
  • Promise原理探究
  • 实用新规范
  • 《ES6教程》
夜猫子
2022-04-11
目录

函数式编程

# 函数式编程

JavaScript 语言从一诞生,就具有函数式编程的烙印。它将函数作为一种独立的数据类型,与其他数据类型处于完全平等的地位。在 JavaScript 语言中,你可以采用面向对象编程,也可以采用函数式编程。有人甚至说,JavaScript 是有史以来第一种被大规模采用的函数式编程语言。 ES6 的种种新增功能,使得函数式编程变得更方便、更强大。本章介绍 ES6 如何进行函数式编程。

# 柯里化

柯里化(currying)指的是将一个多参数的函数拆分成一系列函数,每个拆分后的函数都只接受一个参数(unary)。

function add (a, b) {
  return a + b;
}

add(1, 1) // 2
1
2
3
4
5

上面代码中,函数add接受两个参数a和b。

柯里化就是将上面的函数拆分成两个函数,每个函数都只接受一个参数。

function add (a) {
  return function (b) {
    return a + b;
  }
}
// 或者采用箭头函数写法
const add = x => y => x + y;

const f = add(1);
f(1) // 2
1
2
3
4
5
6
7
8
9
10

上面代码中,函数add只接受一个参数a,返回一个函数f。函数f也只接受一个参数b。

封装转换柯里化工具函数

const curry = (fn) => {
  return function curryFn(...args) {
    console.log(args,fn)
    if (args.length >= fn.length) {
      //当实际参数个数大于函数形参个数时执行函数fn
      return fn(...args);
    }
      // 此处进行了柯里化
    return function (...args2) {
       //否则合并下一个参数,并传给递归函数
      return curryFn(...args.concat(args2));
    };
  };
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14

验证一下

const sum = (a, b, c) => {
  return a + b + c;
};

const currySum = curry(sum);

console.log(currySum(1)(2)(3)); //6
console.log(currySum(1, 2)(3)); //6

console.log(currySum(1)(2, 3)); //6
// -> args: [1] fn: (a, b, c) => {
//  return a + b + c;
// }
//-> args: [1, 2, 3] fn: (a, b, c) => {
//  return a + b + c;
// }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 函数合成

函数合成(function composition)指的是,将多个函数合成一个函数。

// 使用管道运算符(未实装)
const compose = f => g => x => f(g(x));

const f = compose (x => x * 4) (x => x + 3);
f(2) // 20
1
2
3
4
5

上面代码中,compose就是一个函数合成器,用于将两个函数合成一个函数。

可以发现,柯里化与函数合成有着密切的联系。前者用于将一个函数拆成多个函数,后者用于将多个函数合并成一个函数。

什么是管道?

管道是将一个函数的输出直接发送到另一个函数。在伪代码中,可以表示为:

output = input -> func1 -> func2 -> func3

在这种情况下,将 input 通过管道输送到 func1,将 func1 通过管道输送到 func2 的输出,在将 func2 通过管道输送到 func3。

在不支持管道的情况下,我们可以这么做:

const output = func3(func2(func1(input)))
1

这是很难阅读且不直观的!如果我们能在 JavaScript 中使用实际的管道操作符(|>),那就太好了,但实际上我们还不能使用它。他目前还没内置在 JavaScript 中,正处于 TC39 审议的草案/第1阶段 (opens new window)

# 参数倒置

参数倒置(flip)指的是改变函数前两个参数的顺序。

var divide = (a, b) => a / b;
var flip = f.flip(divide);

flip(10, 5) // 0.5
flip(1, 10) // 10

var three = (a, b, c) => [a, b, c];
var flip = f.flip(three);
flip(1, 2, 3); // => [2, 1, 3]
1
2
3
4
5
6
7
8
9

上面代码中,如果按照正常的参数顺序,10 除以 5 等于 2。但是,参数倒置以后得到的新函数,结果就是 5 除以 10,结果得到 0.5。如果原函数有 3 个参数,则只颠倒前两个参数的位置。

参数倒置的代码非常简单。

let f = {};
f.flip =
  fn =>
    (a, b, ...args) => fn(b, a, ...args.reverse());
1
2
3
4

# 执行边界

执行边界(until)指的是函数执行到满足条件为止。

let condition = x => x > 100;
let inc = x => x + 1;
let until = f.until(condition, inc);

until(0) // 101

condition = x => x === 5;
until = f.until(condition, inc);

until(3) // 5
1
2
3
4
5
6
7
8
9
10

上面代码中,第一段的条件是执行到x大于 100 为止,所以x初值为 0 时,会一直执行到 101。第二段的条件是执行到等于 5 为止,所以x最后的值是 5。

执行边界的实现如下。

let f = {};
f.until = (condition, f) =>
  (...args) => {
    var r = f.apply(null, args);
    return condition(r) ? r : f.until(condition, f)(r);
  };
1
2
3
4
5
6

上面代码的关键就是,如果满足条件就返回结果,否则不断递归执行。

# 队列操作

队列(list)操作包括以下几种。

  • head: 取出队列的第一个非空成员。
  • last: 取出有限队列的最后一个非空成员。
  • tail: 取出除了“队列头”以外的其他非空成员。
  • init: 取出除了“队列尾”以外的其他非空成员。

下面是例子。

f.head(5, 27, 3, 1) // 5
f.last(5, 27, 3, 1) // 1
f.tail(5, 27, 3, 1) // [27, 3, 1]
f.init(5, 27, 3, 1) // [5, 27, 3]
1
2
3
4

这些方法的实现如下。

let f = {};
f.head = (...xs) => xs[0];
f.last = (...xs) => xs.slice(-1);
f.tail = (...xs) => Array.prototype.slice.call(xs, 1);
f.init = (...xs) => xs.slice(0, -1);
1
2
3
4
5

# 合并操作

合并操作分为concat和concatMap两种。前者就是将多个数组合成一个,后者则是先处理一下参数,然后再将处理结果合成一个数组。

f.concat([5], [27], [3]) // [5, 27, 3]
f.concatMap(x => 'hi ' + x, 1, [[2]], 3) // ['hi 1', 'hi 2', 'hi 3']
1
2

这两种方法的实现代码如下。

let f = {};
f.concat =
  (...xs) => xs.reduce((a, b) => a.concat(b));
f.concatMap =
  (f, ...xs) => f.concat(xs.map(f));
1
2
3
4
5

# 配对操作

配对操作分为zip和zipWith两种方法。zip操作将两个队列的成员,一一配对,合成一个新的队列。如果两个队列不等长,较长的那个队列多出来的成员,会被忽略。zipWith操作的第一个参数是一个函数,然后会将后面的队列成员一一配对,输入该函数,返回值就组成一个新的队列。

下面是例子。

let a = [0, 1, 2];
let b = [3, 4, 5];
let c = [6, 7, 8];

f.zip(a, b) // [[0, 3], [1, 4], [2, 5]]
f.zipWith((a, b) => a + b, a, b, c) // [9, 12, 15]
1
2
3
4
5
6

上面代码中,zipWith方法的第一个参数是一个求和函数,它将后面三个队列的成员,一一配对进行相加。

这两个方法的实现如下。

let f = {};

f.zip = (...xs) => {
  let r = [];
  let nple = [];
  let length = Math.min.apply(null, xs.map(x => x.length));

  for (var i = 0; i < length; i++) {
    xs.forEach(
      x => nple.push(x[i])
    );

    r.push(nple);
    nple = [];
  }

  return r;
};

f.zipWith = (op, ...xs) =>
  f.zip.apply(null, xs).map(
    (x) => x.reduce(op)
  );
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 参考链接

  • Mateo Gianolio, Haskell in ES6: Part 1 (opens new window)
编辑 (opens new window)
#概念#ES6
上次更新: 2023/7/11 18:57:41
装饰器
Mixin

← 装饰器 Mixin→

最近更新
01
IoC 解决了什么痛点问题?
03-10
02
如何调试 Nest 项目
03-10
03
Provider注入对象
03-10
更多文章>
Copyright © 2019-2025 Study | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式