怎样用Javascript实现函数柯里化与反柯里化

函数柯里化(黑人问号脸)???Currying(黑人问号脸)???妥妥的中式翻译既视感;下面来一起看看究竟什么是函数柯里化:

维基百科的解释是:把接收多个参数的函数变换成接收一个单一参数(最初函数的第一个参数)的函数,并返回接受剩余的参数而且返回结果的新函数的技术。其由数学家Haskell Brooks Curry提出,并以curry命名。

概念往往都是干涩且难懂的,让我们用人话来解释就是:如果我们不确定这个函数有多少个参数,我们可以先给它传入一个参数,然后通过JS闭包(如若不懂JS闭包,请先学习闭包知识点再来学习本篇博文https://www.cnblogs.com/dengyao-blogs/p/11475575.html)来进行返回一个函数,内部函数接收除开第一个参数外的其余参数进行操作并输出,这个就是函数的柯里化;

举个小例子:

场景(需求):

众所周知程序员每天加班的时间还是比较多的,如果我们需要计算一个程序员每天的加班时间,那么我们的第一反应应该是这样;

var overtime=0;
function time(x){
    return overtime+=x;
}

time(1);  //1
time(2);  //3
time(3);  //6

上面的代码固然没有问题,可是需要每天调用都算加一下当天的时间,很麻烦,并且每调用一次函数都要进行一定的操作,如果数据量巨大,有可能会有影响性能的风险,那么有没有可以偷懒又能解决问题的办法呢?有的!

function time(x){
  return function(y){
        return x+y;
    }
}

var times=time(0);
times(3);

但是上面代码依然存在问题,在实际开发中很多时候我们的参数是不确定的,上面代码虽然简单的实现了柯里化的基本操作,但是对于参数不确定的情况是处理不了的;所以存在着函数参数的局限性;不过我们从上面的代码中基本可以知道函数柯里化是个啥意思了;就是一个函数调用的时候只允许传入一个参数,然后通过闭包返回内部函数去处理和接收剩余参数,返回的函数通过闭包的方式记住了time的第一个参数;

我们再来把代码改造一下:

//  首先定义一个变量接收函数
var overtime = (function() {
//定义一个数组用来接收参数
  var args = [];
//这里运用闭包,调用外部函数返回一个内部函数
  return function() {
  //arguments是浏览器内置对象,专门用来接收参数
  //如果参数的长度为0即没有参数的时候
    if(arguments.length === 0) {
    //定义变量用来累加
      var time = 0;
    //循环累加,用i和args的长度进行比较
      for (var i = 0, l = args.length; i < l; i++) {
    //进行累加操作   等价于time=time+args[i]
        time += args[i];
      }
    // 返回累加的结果
      return time;
    //如果arguments对象参数长度不为零,即有参数的时候
    }else {
    //定义的空数组添加arguments参数作为数组项,第一个参数古args作为改变this指向,第二个参数arguments把剩余参数作为数组形式添加至空数组中
      [].push.apply(args, arguments);
    }
  }
})();

overtime(3.5);    // 第一天
overtime(4.5);    // 第二天
overtime(2.1);    // 第三天
//...

console.log( overtime() );    // 10.1

代码经过我们的改造已经实现了功能,但是这不是一个函数柯里化的完整实现,那么我们要怎么完整实现呢?下面我们来介绍一种通用的实现方式:

//定义方法currying,先传入一个参数
var currying=function(fn){
  //定义空数组装arguments对象的剩余参数
  var args=[];
  //利用闭包返回一个函数处理剩余参数
  return function (){
    //如果arguments的参数长度为0,即没有剩余参数
    if(arguments.length===0){
    //执行上面方法
    //这里的this指向下面的s,类似于s(),代表参数长度为0的时候直接调用函数
      return fn.apply(this,args)
    }
    console.log(arguments)
  //如果arguments的参数长度不为0,即还有剩余参数
  //在数组的原型对象上添加数组,apply用来更改this的指向为args
  //将[].slice.call(arguments)的数组添加到原型数组上
  //这里的[].slice.call(arguments)===Array.prototype.slice.call(arguments)实质上就是将arguments对象转成数组并具有slice功能

   Array.prototype.push.apply(args,[].slice.call(arguments))
    //args.push([].slice.call(arguments))
    console.log(args)
  //这里返回的arguments.callee是返回的闭包函数,callee是arguments对象里面的一个属性,用于返回正被执行的function对象
    return arguments.callee
  }
}
  //这里调用currying方法并传入add函数,结果会返回闭包内部函数
  var s=currying(add);
  //调用闭包内部函数,当有参数的时候会将参数逐步添加到args数组中,待没有参数传入的时候直接调用
  //调用的时候支持链式操作
  s(1)(2)(3)();
//也可以一次性传入多个参数
   s(1,2,3);
  console.log(s());

JS函数柯里化的优点:

  • 可以延迟计算,即如果调用柯里化函数传入参数是不调用的,会将参数添加到数组中存储,等到没有参数传入的时候进行调用;
  • 参数复用,当在多次调用同一个函数,并且传递的参数绝大多数是相同的,那么该函数可能是一个很好的柯里化候选。

世间万物相对,有因必有果,当然了,有柯里化必然有反柯里化;

反柯里化(uncurrying),从字面意思上来讲就是跟柯里化的意思相反;其实真正的反柯里化的作用是扩大适用范围,就是说当我们调用某个方法的时候,不需要考虑这个对象自身在设计的过程中有没有这个方法,只要这个方法适用于它,我们就可以使用;(这里引用的是动态语言中的鸭子类型的思想)

在学习JS反柯里化之前,我们先学习一下动态语言的鸭子类型思想,以助于我们更好的理解:

动态语言鸭子类型思想(维基百科解释):

在程序设计中,鸭子类型(duck typing)是动态类型的一种风格。

在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由当前方法和属性的集合决定。

这个概念的名字来源于由 James Whitcomb Riley 提出的鸭子测试,“鸭子测试”可以这样表述:

当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。

理论上的解释往往干涩难懂,换成人话来说就是:你是你妈妈的儿子/女儿,不管你是否优秀,是否漂亮,只要你是你妈亲生的,那么你就是你妈的儿子/女儿;换成鸭子类型就是,只要你会呱呱叫,走起来像鸭子,只要你拥有的行为像鸭子,不管你是不是鸭子,那么你就可以被称为鸭子;

在Javascript中有很多鸭子类型的引用,比如我们在对一个变量进行赋值的时候,显然是不需要考虑变量的类型的,正是因为如此,Javascript才更加的灵活,所以Javascript是一门典型的动态类型语言;

我们来看一下反柯里化中是怎么引用鸭子类型的:

//函数原型对象上添加uncurring方法
Function.prototype.uncurring = function() {
//改变this的指向
//这里的this指向是Array.prototype.push
  var self = this;
    //这里的闭包用来返回内部函数的执行
  return function() {
    //创建一个变量,在数组的原型对象上添加shift上面删除第一个参数
    //改变数组this的指向为arguments
    var obj = Array.prototype.shift.call(arguments);
    //最后返回执行并给方法改变指向为obj也就是arguments
   // 并传入arguments作为参数
    return self.apply(obj, arguments);
  };
};

//数组原型对象上添加uncurrying方法
var push = Array.prototype.push.uncurring();

//测试一下
//匿名函数自执行
(function() {
    //这里的push就是一个函数方法了
    //相当于传入参数arguments和4两个参数,但是在上面shift方法中删除第一个参数,这里的arguments参数被截取了,所以最后实际上只传入了4
  push(arguments, 4);
  console.log(arguments); //[1, 2, 3, 4]
//匿名函数自调用并带入参数1,2,3
})(1, 2, 3)

到这里大家可以想一想arguments是一个接收参数的对象,里面是没有push方法的,那么arguments为什么能调用push方法呢?

这是因为代码var push = Array.prototype.push.uncurring();在数组的原型对象的push方法上添加了uncurring方法,然后在执行匿名函数的方法push(arguments, 4);时候实质上是在调用上面的方法在Function的原型对象上添加uncurring方法并返回一个闭包内部函数执行,在执行的过程中因为Array原型对象上的shift方法会把push(arguments, 4);中的arguments截取,所以其实方法的实际调用是push(4),所以最终的结果才是[1,2,3,4]

在《JavaScript设计模式与开发实践》一书中,JS函数的反柯里化的案例是这样写的:

//定义一个对象
var obj = {
    "length":1,
    "0":1
}
//在Function原型对象定义方法uncurrying
Function.prototype.uncurrying = function() {
    //this指向Array.prototype.push
    var self = this;
    //闭包返回一个内部函数
    return function() {
    // 这里可以拆开理解
    //首先执行apply return
    //Function.prototype.call(Array.prototype.push[obj,2])
   //然后Array.prototype.push.call(obj,2)
    //call改变指向  obj.push(2)
    //所以最后结果就是  {0: 1, 1: 2, length: 2}
        return Function.prototype.call.apply(self, arguments);
}
}

//在
var push = Array.prototype.push.uncurrying()

push(obj, 2)
console.log(obj);
//{0: 1, 1: 2, length: 2}

上面的方式不好理解?没关系,咱们来个好理解的:

Function.prototype.unCurrying = function () {
    var self = this;
    return function () {
    //[].slice.call(arguments,1)===Array.prototype.push.slice.call(arguments,1)===arguments.slice(1)
return self.apply(arguments[0], [].slice.call(arguments, 1));
    };
};

var push = Array.prototype.push.uncurrying()
console.log(push);
push(obj,2) //{0: 1, 1: 2, length: 2}
console.log(obj);

分析一下:

1、首先在Function原型对象上添加uncurrying方法,这样所有的Function都可以借用;

2、返回一个闭包内部函数

3、闭包函数返回的结果中返回的是调用方法,self指向Array.prototype.push,apply方法中第一个参数是更改指向,对应下面push(obj,2)相当于更改指向为obj.push(2)

4、apply方法中第二个参数的call方法是更改指向为arguments,并且arguments中能使用slice方法,等于arguments.slice(1)

以上就是怎样用Javascript实现函数柯里化与反柯里化的详细内容,更多关于Javascript函数柯里化与反柯里化的资料请关注我们其它相关文章!

(0)

相关推荐

  • 浅析JavaScript 函数柯里化

    柯里化 (Currying)是把接收多个参数的原函数变换成接受一个单一参数(原来函数的第一个参数的函数)并返回一个新的函数,新的函数能够接受余下的参数,并返回和原函数相同的结果. ES6的方式实现柯里化的通用 function currying(fn,...rest1){ return function(...rest2){ //这里用apply 是为把数组形式的参数直接传入原函数 null是因为不需要改变this return fn.apply(null,rest1.concat(rest2)

  • Javascript闭包与函数柯里化浅析

    闭包和柯里化都是JavaScript经常用到而且比较高级的技巧,所有的函数式编程语言都支持这两个概念,因此,我们想要充分发挥出JavaScript中的函数式编程特征,就需要深入的了解这两个概念,闭包事实上更是柯里化所不可缺少的基础. 一.柯里化的概念 在计算机科学中,柯里化是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术.这个技术由Christopher Strachey以逻辑学家 Haskell Curry 命名的,尽管

  • JS中精巧的自动柯里化实现方法

    以下内容通过代码讲解和实例分析了JS中精巧的自动柯里化实现方法,并分析了柯里化函数的基础用法和知识,学习一下吧. 什么是柯里化? 在计算机科学中,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术.这个技术由 Christopher Strachey 以逻辑学家 Haskell Curry 命名的,尽管它是 Moses Schnfinkel 和 Gottlob Frege 发明的. 理论看着头大?没

  • javascript中利用柯里化函数实现bind方法【推荐】

    • 柯理化函数思想:一个js预先处理的思想:利用函数执行可以形成一个不销毁的作用域的原理,把需要预先处理的内容都储存在这个不销毁的作用域中,并且返回一个小函数,以后我们执行的都是小函数,在小函数中把之前预先存储的值进行相关的操作处理即可: • 柯里化函数主要起到预处理的作用: • bind方法的作用:把传递进来的callback回调方法中的this预先处理为上下文context; /** * bind方法实现原理1 * @param callback [Function] 回调函数 * @par

  • 浅谈JS中的反柯里化( uncurrying)

    反柯里化 相反,反柯里化的作用在与扩大函数的适用性,使本来作为特定对象所拥有的功能的函数可以被任意对象所用. 即把如下给定的函数签名, obj.func(arg1, arg2) 转化成一个函数形式,签名如下: func(obj, arg1, arg2) 这就是 反柯里化的形式化描述. 例如,下面的一个简单实现: Function.prototype.uncurrying = function() { var that = this; return function() { return Func

  • javascript中利用柯里化函数实现bind方法

    柯理化函数思想:一个js预先处理的思想:利用函数执行可以形成一个不销毁的作用域的原理,把需要预先处理的内容都储存在这个不销毁的作用域中,并且返回一个小函数,以后我们执行的都是小函数,在小函数中把之前预先存储的值进行相关的操作处理即可: 柯里化函数主要起到预处理的作用: bind方法的作用:把传递进来的callback回调方法中的this预先处理为上下文context; /** * bind方法实现原理1 * @param callback [Function] 回调函数 * @param con

  • 深入剖析JavaScript中的函数currying柯里化

    curry化来源与数学家 Haskell Curry的名字 (编程语言 Haskell也是以他的名字命名).   柯里化通常也称部分求值,其含义是给函数分步传递参数,每次传递参数后部分应用参数,并返回一个更具体的函数接受剩下的参数,这中间可嵌套多层这样的接受部分参数函数,直至返回最后结果. 因此柯里化的过程是逐步传参,逐步缩小函数的适用范围,逐步求解的过程. 柯里化一个求和函数 按照分步求值,我们看一个简单的例子 var concat3Words = function (a, b, c) { r

  • 浅谈JS中的bind方法与函数柯里化

    绑定函数bind()最简单的用法是创建一个函数,使这个函数不论怎么调用都有同样的this值.不同于call和apply只是单纯地设置this的值后传参,它还会将所有传入bind()方法中的实参(第一个参数之后的参数)与this一起绑定. 关于这个特性看<JS权威指南>原文的例子: var sum = function(x,y) { return x + y }; var succ = sum.bind(null, 1); //让this指向null,其后的实参也会作为实参传入被绑定的函数sum

  • 通过实例了解Javascript柯里化流程

    函数式编程是一种如今比较流行的编程范式,它主张将函数作为参数进行传递,然后返回一个没有副作用的函数,说白了,就是希望一个函数只做一件事情. 像Javascript,Haskell,Clojure等编程语言都支持函数式编程. 这种编程思想涵盖了三个重要的概念: 纯函数 柯里化 高阶函数 而这篇文章主要是想向大家讲清楚柯里化这个概念. 什么是柯里化 首先我们先来看一个例子: function sum(a, b, c) { return a + b + c; } // 调用 sum(1, 2, 3);

  • JavaScript函数柯里化详解

    什么是柯里化 柯里化是这样的一个转换过程,把接受多个参数的函数变换成接受一个单一参数(译注:最初函数的第一个参数)的函数,如果其他的参数是必要的,返回接受余下的参数且返回结果的新函数. 柯理化函数思想:一个js预先处理的思想:利用函数执行可以形成一个不销毁的作用域的原理,把需要预先处理的内容都储存在这个不销毁的作用域中,并且返回一个小函数,以后我们执行的都是小函数,在小函数中把之前预先存储的值进行相关的操作处理即可: 柯里化函数主要起到预处理的作用: bind方法的作用:把传递进来的callba

  • js函数柯里化的方法和作用实例分析

    本文实例讲述了js函数柯里化的方法和作用.分享给大家供大家参考,具体如下: 函数柯里化的方法: 1,利用数组的reduce方法可实现柯里化,具体参见我的另外一篇文章 2,代码如下 const curry = (fn, ...args) =>{ console.log('args',args) // console.log(fn.length,args.length) return args.length < fn.length // 参数长度不足时,重新柯里化该函数,等待接受新参数 ? (..

  • JavaScript函数柯里化原理与用法分析

    本文实例讲述了JavaScript函数柯里化原理与用法.分享给大家供大家参考,具体如下: 柯里化是这样的一个转换过程,把接受多个参数的函数变换成接受一个单一参数(译注:最初函数的第一个参数)的函数,如果其他的参数是必要的,返回接受余下的参数且返回结果的新函数. 也就是说是固定部分参数,返回一个接受剩余参数的函数,也称为部分计算函数,目的是为了缩小适用范围,创建一个针对性更强的函数. 例如,我想创建一个做自我介绍的函数,每个人只要输入自己姓名.性别.年龄即可.但是当A使用这个函数时,每次调用,都必

  • Javascript柯里化实现原理及作用解析

    函数式编程是一种如今比较流行的编程范式,它主张将函数作为参数进行传递,然后返回一个没有副作用的函数,说白了,就是希望一个函数只做一件事情. 像JavaScript,Haskell,Clojure等编程语言都支持函数式编程. 这种编程思想涵盖了三个重要的概念: 纯函数 柯里化 高阶函数 而这篇文章主要是想向大家讲清楚柯里化这个概念. 什么是柯里化 首先我们先来看一个例子: function sum(a, b, c) { return a + b + c; } // 调用 sum(1, 2, 3);

  • JavaScript函数柯里化实现原理及过程

    简介 当我们在读Vue源码到时候会发现,在它的 _update 实例中就用到了函数柯里化,(createPatchFunction方法)有兴趣的可以 去看一下. 柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术. 在<Mostly adequate guide>中,这样总结了 Currying --只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数. Currying 是函数式编

  • javascript实现函数柯里化与反柯里化过程解析

    函数柯里化(黑人问号脸)???Currying(黑人问号脸)???妥妥的中式翻译既视感:下面来一起看看究竟什么是函数柯里化: 维基百科的解释是:把接收多个参数的函数变换成接收一个单一参数(最初函数的第一个参数)的函数,并返回接受剩余的参数而且返回结果的新函数的技术.其由数学家Haskell Brooks Curry提出,并以curry命名. 概念往往都是干涩且难懂的,让我们用人话来解释就是:如果我们不确定这个函数有多少个参数,我们可以先给它传入一个参数,然后通过JS闭包(如若不懂JS闭包,请先学

  • 深入解析JavaScript中函数的Currying柯里化

    引子 先来看一道小问题: 有人在群里出了到一道题目: var s = sum(1)(2)(3) ....... 最后 alert(s) 出来是6  var s = sum(1)(2)(3)(4) ....... 最后 alert(s) 出来是10  问sum怎么实现? 刚看到题目,我第一反应是sum返回的是一个function,但是没有最终实现,印象中看到过类似的原理,但是记不清了.   后来同事说,这个是叫柯里化, 实现方法比较巧妙: function sum(x){ var y = func

  • JavaScript偏函数与柯里化实例详解

    本文实例讲述了JavaScript偏函数与柯里化.分享给大家供大家参考,具体如下: 到目前为止我们仅讨论绑定this,现在让我们更深入学习. 我们不仅能绑定this,也可以是参数,这较少使用,但有时很方便. bind完整的语法为: let bound = func.bind(context, arg1, arg2, ...); 可以绑定上下文this和函数的初始参数.举例,我们有个乘法函数mul(a,b): function mul(a, b) { return a * b; } 我们可以在该函

  • 详解JS中的柯里化(currying)

    何为Curry化/柯里化? curry化来源与数学家 Haskell Curry的名字 (编程语言 Haskell也是以他的名字命名). 柯里化通常也称部分求值,其含义是给函数分步传递参数,每次传递参数后部分应用参数,并返回一个更具体的函数接受剩下的参数,这中间可嵌套多层这样的接受部分参数函数,直至返回最后结果. 因此柯里化的过程是逐步传参,逐步缩小函数的适用范围,逐步求解的过程. 柯里化一个求和函数 按照分步求值,我们看一个简单的例子 var concat3Words = function (

随机推荐