JavaScript 函数式编程技巧 - 反柯里化

简介: 柯里化,是固定部分参数,返回一个接受剩余参数的函数,也称为部分计算函数,目的是为了缩小适用范围,创建一个针对性更强的函数。核心思想是把多参数传入的函数拆成单参数(或部分)函数,内部再返回调用下一个单参数(或部分)函数,依次处理剩余的参数。而反柯里化,从字面讲,意义和用法跟函数柯里化相比正好相反,扩大适用范围,创建一个应用范围更广的函数。使本来只有特定对象才适用的方法,扩展到更多的对象。

微信截图_20220427171658.png

作为函数式编程语言,JS带来了很多语言上的有趣特性,比如柯里化和反柯里化。

可以对照另外一篇介绍 JS 柯里化 的文章一起看~


1. 简介


柯里化,是固定部分参数,返回一个接受剩余参数的函数,也称为部分计算函数,目的是为了缩小适用范围,创建一个针对性更强的函数。核心思想是把多参数传入的函数拆成单参数(或部分)函数,内部再返回调用下一个单参数(或部分)函数,依次处理剩余的参数。


反柯里化,从字面讲,意义和用法跟函数柯里化相比正好相反,扩大适用范围,创建一个应用范围更广的函数。使本来只有特定对象才适用的方法,扩展到更多的对象。


2. 实现


先来看看反柯里化的通用实现吧~


Function.prototype.unCurrying = function() {
  const self = this
  return function(...rest) {
    return Function.prototype.call.apply(self, rest)
  }
}
复制代码


解释下:


  1. 为Function原型添加uncurrying方法,并在执行的时候保存执行unCurrying的方法到self
  2. 借用apply把要借用的函数作为this环境赋给call,并传入之后的形参作为参数执行


还有一个实现:


Function.prototype.unCurrying = function() {
  return this.call.bind(this)
}
复制代码


如果你觉得把函数放在Function.prototype上不太好,也可以这样:


function unCurrying(fn) {
  return function(tar, ...argu) {
    return fn.apply(tar, argu)
  }
}
复制代码


3. 使用


3.1 简单使用


unCurrying通用实现简单的实用一下试试:


Function.prototype.unCurrying = function() {
  const self = this                        // 这里的self就是Array.prototype.push方法
  return function(...rest) {              // rest为传入的两层参数[[1,2,3],4]
    return Function.prototype.call.apply(self, rest)
  }
}
const push = Array.prototype.push.unCurrying()
~function(...rest) {       // rest:[1,2,3]
  push(rest, 4)
  console.log(rest)    // [1, 2, 3, 4]
}(1, 2, 3)
复制代码


3.2 借用其他方法


反柯里化其实反映的是一种思想,即扩大方法的适用范围,仍然调用刚刚的通用unCurrying方法借用push方法:


const push = Array.prototype.push.unCurrying()
const obj = { a: '嘻嘻' }
push(obj, '呵呵', '哈哈', '嘿嘿')
console.log(obj)                    // { '0': '呵呵', '1': '哈哈', '2': '嘿嘿', a: '嘻嘻', length: 3 }
复制代码


相当于obj.push(...),obj不仅多了类似于数组一样以数字作为索引的属性,还多了个类似于数组的length属性,让引擎自动管理数组成员和length属性;(文后有V8引擎实现push方法的源码) 这样一个数组的push方法就被借用出来,可以应用于任何其他对象了。


只要是方法,unCurrying就可以借用,call方法也可以:


var call = Function.prototype.call.unCurrying();
function $(id) {
    return this.getElementById(id);
}
call($, document, 'demo')            // #demo 元素
复制代码


相当于document.$('demo'),成功的借用了call方法,当然可以把document改成你希望作为this绑定到$的任何对象,比如{

getElementById:T=>console.log(T+'呃') } // demo呃


3.3 借用自己


unCurrying本身也是方法,也可以借用自己...-。-


const unCurrying = Function.prototype.unCurrying.unCurrying()
const map = unCurrying(Array.prototype.map)
map({ 0: 4, 1: 'a', 2: null, length: 3 }, n => n + n)                    // [8, "aa", 0]
复制代码


神奇吧~


4. 总结


简单说,函数柯里化就是对高阶函数的降阶处理,缩小适用范围,创建一个针对性更强的函数。举栗子:


function(arg1,arg2)        // => function(arg1)(arg2)
function(arg1,arg2,arg3)        // => function(arg1)(arg2)(arg3)
function(arg1,arg2,arg3,arg4)        // => function(arg1)(arg2)(arg3)(arg4)
function(arg1,arg2,…,argn)        // => function(arg1)(arg2)…(argn)
复制代码


而反柯里化就是反过来,增加适用范围,让方法使用场景更大。使用unCurrying, 可以把原生方法借出来,让任何对象拥有原生对象的方法。举个栗子:


obj.func(arg1, arg2)        // => func(obj, arg1, arg2)
复制代码


也可以这样理解:柯里化是在运算前提前传参,可以传递多个参数; 反柯里化是延迟传参,在运算时把原来已经固定的参数或者this上下文等当作参数延迟到未来传递。



附:


V8引擎中Array.prototype.push方法源码实现:


function ArrayPush() {
    var n = TO_UINT32(this.length);
    var m = %_ArgumentsLength();
    for (var i = 0; i < m; i++) {
        this[i + n] = %_Arguments(i);        // 属性拷贝
        this.length = n + m;                    // 修正length
        return this.length;
    }
}
复制代码

网上的帖子大多深浅不一,甚至有些前后矛盾,在下的文章都是学习过程中的总结,如果发现错误,欢迎留言指出~



相关文章
|
9月前
|
前端开发 JavaScript 数据处理
深入学习JavaScript ES8函数式编程:特性与实践指南
深入学习JavaScript ES8函数式编程:特性与实践指南
63 0
|
1月前
|
存储 JavaScript 前端开发
JavaScript——函数式编程Functor(函子)
JavaScript——函数式编程Functor(函子)
12 0
|
1月前
|
JavaScript 前端开发
JS : 柯里化 分布传参给函数
JS : 柯里化 分布传参给函数
|
3月前
|
JavaScript 前端开发 测试技术
JavaScript进阶-高阶函数与柯里化
【6月更文挑战第18天】在JavaScript中,高阶函数和柯里化是函数式编程的核心。高阶函数接收或返回函数,常用于数组操作和事件处理。柯里化将多参数函数转化为单参数的逐次求值过程,用于参数预绑定和函数组合。两者能简化逻辑、提高复用性,但也需注意易错点,如混淆参数、过度柯里化,应适度使用并配合测试保证正确性。通过实践和使用工具,如lodash的`_.curry`,能更好地利用这些技术。
40 5
|
3月前
|
前端开发 JavaScript 开发者
函数式编程在JavaScript中的应用
【6月更文挑战第10天】本文探讨了函数式编程在JavaScript中的应用,介绍了函数式编程的基本概念,如纯函数和不可变数据。文中通过示例展示了高阶函数、不可变数据的使用,以及如何编写纯函数。此外,还讨论了函数组合和柯里化技术,它们能提升代码的灵活性和可重用性。掌握这些函数式编程技术能帮助开发者编写更简洁、可预测的JavaScript代码。
|
4月前
|
JavaScript 前端开发
JavaScript 的数组方法 map()、filter() 和 reduce() 提供了函数式编程处理元素的方式
【5月更文挑战第11天】JavaScript 的数组方法 map()、filter() 和 reduce() 提供了函数式编程处理元素的方式。map() 用于创建新数组,其中元素是原数组元素经过指定函数转换后的结果;filter() 则筛选出通过特定条件的元素生成新数组;reduce() 将数组元素累计为单一值。这三个方法使代码更简洁易读,例如:map() 可用于数组元素乘以 2,filter() 用于选取偶数,reduce() 计算数组元素之和。
39 2
|
4月前
|
JavaScript 前端开发 测试技术
JavaScript中的函数式编程:纯函数与高阶函数的概念解析
【4月更文挑战第22天】了解JavaScript中的函数式编程,关键在于纯函数和高阶函数。纯函数有确定输出和无副作用,利于预测、测试和维护。例如,`add(a, b)`函数即为纯函数。高阶函数接受或返回函数,用于抽象、复用和组合,如`map`、`filter`。函数式编程能提升代码可读性、可维护性和测试性,帮助构建高效应用。
|
4月前
|
JavaScript 前端开发 网络架构
函数柯里化:JavaScript中的高级技巧
函数柯里化:JavaScript中的高级技巧
|
4月前
|
前端开发 JavaScript 数据处理
深入学习JavaScript ES8函数式编程:特性与实践指南
深入学习JavaScript ES8函数式编程:特性与实践指南
94 0
|
4月前
|
JavaScript 前端开发 索引
JavaScript函数式编程【进阶】
JavaScript函数式编程【进阶】
45 1