Roshin's blog Roshin's blog
首页
  • 《数据结构与算法》笔记
  • 手写代码
  • 实用方法
  • 文档教程
  • 面试集锦
  • 分类
  • 标签
  • 归档
  • 个人介绍
  • 关于博客
  • 友情链接
GitHub (opens new window)

Roshin

如果你只做自己能力范围之内的事情,就永远没法进步。
首页
  • 《数据结构与算法》笔记
  • 手写代码
  • 实用方法
  • 文档教程
  • 面试集锦
  • 分类
  • 标签
  • 归档
  • 个人介绍
  • 关于博客
  • 友情链接
GitHub (opens new window)
  • 手写代码

  • 实用方法

    • 函数柯里化
      • 定义
      • 简单版本
        • 不借助中转函数
      • 占位符版本
  • JavaScript
  • 实用方法
roshin
2021-04-15

函数柯里化

定义 #

把接受多个参数的函数转换成接受一个单一参数的函数

curry 的一些性能问题

  • 存取 arguments 对象通常要比存取命名参数要慢一点
  • 一些老版本的浏览器在 arguments.length 的实现上是相当慢的
  • 使用 fn.apply() 和 fn.call() 通常比直接调用 fn() 稍微慢点
  • 创建大量嵌套作用域和闭包函数会带来花销,无论是在内存还是速度上

简单版本 #

/**
 * 将函数柯里化
 * @param {Function} func 待柯里化的原函数
 * @param {Number} len 所需的参数个数,默认为原函数的形参个数
 * @returns {*} 柯里化后的函数
 */
function curry(func, len = func.length) {
  return _curry.call(this, func, len);
}

/**
 * 柯里化中转函数
 * @param {Function} func 待柯里化的原函数
 * @param {Number} len 所需的参数个数,默认为原函数的形参个数
 * @param {Number} args 已接收的函数列表
 * @returns {*} 继续柯里化函数或最终结果
 */
function _curry(func, len, ...args) {
  return function (...params) {
    const _args = [...args, ...params];
    // 接收足够参数后,执行原函数
    if (_args.length >= len) {
      return func.apply(this, _args);
    }
    return _curry.call(this, func, len, ..._args);
  };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

验证一下:

const fn = curry(function (a, b, c, d, e) {
  console.log(a, b, c, d, e);
});

fn(1, 2, 3, 4, 5); // 1 2 3 4 5
fn(1)(2)(3, 4, 5); // 1 2 3 4 5
fn(1, 2)(3, 4)(5); // 1 2 3 4 5
fn(1)(2)(3)(4)(5); // 1 2 3 4 5
1
2
3
4
5
6
7

不借助中转函数 #

/**
 * 将函数柯里化
 * @param {Function} func 待柯里化的原函数
 * @param {Number} len 所需的参数个数,默认为原函数的形参个数
 * @returns {*} 柯里化后的函数
 */
function curry(func, len = func.length) {
  const args1 = Array.prototype.slice.call(arguments, 2);
  return function () {
    const args2 = Array.prototype.slice.call(arguments);
    const args = [].concat(args1, args2);
    if (args.length >= len) {
      return func.apply(this, args);
    }
    return curry.call(this, func, len, ...args);
  };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

验证一下:

const fn = curry(function (a, b, c, d, e) {
  console.log(a, b, c, d, e);
});

fn(1, 2, 3, 4, 5); // 1 2 3 4 5
fn(1)(2)(3, 4, 5); // 1 2 3 4 5
fn(1, 2)(3, 4)(5); // 1 2 3 4 5
fn(1)(2)(3)(4)(5); // 1 2 3 4 5
1
2
3
4
5
6
7

占位符版本 #

/**
 * 将函数柯里化
 * @param {Function} func 待柯里化的原函数
 * @param {Number} len 所需的参数个数,默认为原函数的形参个数
 * @param {*} holder 占位符,默认为当前柯里化函数
 * @returns {*} 柯里化后的函数
 */
function curry(func, len = func.length, holder = curry) {
  return _curry.call(this, func, len, holder, [], []);
}

/**
 * 柯里化中转函数
 * @param {Function} func 待柯里化的原函数
 * @param {Number} len 所需的参数个数,默认为原函数的形参个数
 * @param {Number} holder 接收的占位符
 * @param {Number} args 已接收的函数列表
 * @param {Number} holders 已接收的占位符位置列表
 * @returns {*} 继续柯里化函数或最终结果
 */
function _curry(func, len, holder, args, holders) {
  return function (...params) {
    const _args = [...args]; // 将参数复制一份,避免多次操作同一函数导致参数混乱
    const _holders = [...holders]; // 将占位符位置列表复制一份,新增加的占位符增加到此

    // 遍历接收到的参数列表
    params.forEach((arg) => {
      // 如果当前的这个参数是正常参数
      if (arg !== holder) {
        // 如果原占位符位置列表中不存在占位符,直接追加到参数列表当中
        if (!holders.length) {
          _args.push(arg);
        }
        // 如果原占位符位置列表中存在占位符,则替换掉参数列表里的占位符
        else {
          const index = holders.shift(); // 获取之前还未被替换的占位符位置
          // 删除替换的占位符,这里 _holders 可能会存在新的占位符,所以不能删除第一个元素
          _holders.splice(_holders.indexOf(index), 1);
          _args[index] = arg;
        }
      }
      // 如果当前的这个参数是占位符
      else {
        // 如果原占位符位置列表中不存在占位符,记录占位符的位置
        if (!holders.length) {
          _args.push(arg);
          _holders.push(_args.length - 1);
        }
        // 如果原占位符位置列表中存在占位符,则删除原占位符位置列表中的占位
        else {
          holders.shift();
        }
      }
    });

    // 接收到指定数量的参数后,并且接收到的指定数量的参数中不存在占位符,则执行原函数
    if (_args.length >= len && _args.slice(0, len).every((v) => v !== holder)) {
      return func.apply(this, _args);
    }
    return _curry.call(this, func, len, holder, _args, _holders);
  };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61

验证一下:

const fn = (a, b, c, d, e) => {
  console.log(a, b, c, d, e);
};
const _ = {}; // 定义占位符
const _fn = curry(fn, fn.length, _);

_fn(1, 2, 3, 4, 5); // 1 2 3 4 5
_fn(_, 2, 3, 4, 5)(1); // 1 2 3 4 5
_fn(1, _, 3, 4, 5)(2); // 1 2 3 4 5
_fn(1, _, 3)(_, 4, _)(2)(5); // 1 2 3 4 5
_fn(1, _, _, 4)(_, 3)(2)(5); // 1 2 3 4 5
_fn(_, 2)(_, _, 4)(1)(3)(5); // 1 2 3 4 5
1
2
3
4
5
6
7
8
9
10
11
编辑 (opens new window)
上次更新: 2021-06-23 08:43:55
instanceof 运算符

← instanceof 运算符

最近更新
01
Array.prototype.flat()
04-14
02
Array.prototype.reduce()
04-14
03
instanceof 运算符
04-13
更多文章>
Theme by Vdoing Copyright © 2021-present Roshin | MIT License | 粤ICP备18116277号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式