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;
    }
}
复制代码

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



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