首页 > 其他分享 >js函数柯里化

js函数柯里化

时间:2024-05-24 15:20:47浏览次数:18  
标签:return 函数 js add 参数 柯里化 fn

JavaScript函数柯里化详解

 更新时间:2022年01月14日 15:33:47   作者:天界程序员     这篇文章主要为大家介绍了JavaScript函数柯里化,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助   −
目录

一、简单了解apply和call

  • call 和 apply 都是为了改变某个函数运行时的 context 即上下文而存在的,换句话说,就是为了改变函数体内部 this 的指向。
  • call 和 apply二者的作用完全一样,只是接受参数的方式不太一样。call其实是apply的一种语法糖。
  • 格式:apply(context,[arguments]),call(context,param1,param2,...)

二、什么是函数柯里化?

柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。

在这里举个例子,有一个add()函数,它是用来处理我们传给它的参数(param1,params2,…)相加求和的一个函数。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 // 在这里第一个具有两个参数`x`、`y`的`add(x , y)`函数 function add(x , y){     return x + y; } // 调用`add()`函数,并给定两个参数`4`和`6` add(4,6); // 模拟计算机操作,第一步 传入第一个参数 4 function add(4 , y){     return 4 + y; } // 模拟计算机操作,第二步 传入第一个参数 6 function add(4 , 6){     return 4 + 6; }

如果我们将add()函数柯里化,是什么样子呢?在这里简单的实现一下:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 // 柯里化过的add()函数,可以接受部分参数 function add(x ,y){     if (typeof y === 'undefined') {         return function (newy){             return x + newy;         }     }     // 完整应用     return x + y; } // 测试调用 console.log(typeof add(4)); // [Function] console.log(add(4)(6)); // 10 // 可以创建保存函数 let saveAdd = add(4); console.log(saveAdd(6)); // 10

从以上简单柯里化的add()函数可以看出,函数可以接受部分函数,然后返回一个新的函数,使其继续处理剩下的函数。

三、写一个公共的柯里化函数

在这里我们创建一个公共的柯里化函数,那样我们就不必每次写一个函数都要在其内部实现复杂的柯里化过程。

1 2 3 4 5 6 7 8 9 10 // 定义一个createCurry的函数 function createCurry(fn){     var slice = Array.prototype.slice,     stored_args = slice.call(arguments,1);     return function () {         let new_args = slice.call(arguments),         args = stored_args.concat(new_args);         return fn.apply(null,args);     } }

在以上公共的柯里化函数中:

  • arguments,并不是一个真的数组,只是一个具有length属性的对象,所以我们从Array.prototype中借用slice方法帮我们把arguments转为一个真正的数组,方便我们更好的操作。
  • 当我们第一次调用函数createCurry的时候,其中变量stored_args 是保持了除去第一个参数以外的参数,因为第一个参数是我们需要柯里化的函数。
  • 当我们执行createCurry函数中返回的函数时,变量new_args获取参数并转为数组。
  • 内部返回的函数通过闭包访问变量stored_args中存储的值和变量new_args的值合并为一个新的数组,并赋值给变量args
  • 最后调用fn.apply(null,args)方法,执行被柯里化的函数。

现在我们来测试公共的柯里化函数

1 2 3 4 5 6 7 8 9 10 // 普通函数add() function add(x , y){     return x + y; } // 柯里化得到一个新的函数 var newAdd = createCurry(add,4); console.log(newAdd(6)); // 10   //另一种简便方式 console.log(createCurry(add,4)(6));// 10

当然这里并不局限于两个参数的柯里化,也可以多个参数:

1 2 3 4 5 6 7 8 9 10 11 // 多个参数的普通函数 function add(a,b,c,d){     return a + b + c + d; } // 柯里化函数得到新函数,多个参数可以随意分割 console.log(createCurry(add,4,5)(5,6)); // 20 // 两步柯里化 let add_one = createCurry(add,5); console.log(add_one(5,5,5));// 20 let add_two = createCurry(add_one,4,6); console.log(add_two(6)); // 21

通过以上的例子,我们可以发现一个局限,那就是不管是两个参数还是多个参数,它只能分两步执行,如以下公式:

  • fn(x,y) ==> fn(x)(y);
  • fn(x,y,z,w) ==> fn(x)(y,z,w) || fn(x,y)(z,w)||…

如果我们想更灵活一点:

  • fn(x,y) ==> fn(x)(y);
  • fn(x,y,z) ==> fn(x,y)(z) || fn(x)(y)(z);
  • fn(x,y,z,w) ==> fn(x,y)(z)(w) || fn(x)(y)(z)(w) || …;

我们该怎么实现呢?

四、创建一个灵活的柯里化函数

经过以上练习,我们发现我们创建的柯里化函数存在一定局限性,我们希望函数可以分为多步执行:

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 // 创建一个可以多步执行的柯里化函数,当参数满足数量时就去执行它: // 函数公式:fn(x,y,z,w) ==> fn(x)(y)(z)(w); let createCurry = (fn,...params)=> {     let args = parsms || [];     let fnLen = fn.length; // 指定柯里化函数的参数长度     return (...res)=> {         // 通过作用域链获取上一次的所有参数         let allArgs = args.slice(0);         // 深度拷贝闭包共用的args参数,避免后续操作影响(引用类型)         allArgs.push(...res);         if(allArgs.length < fnLen){            // 当参数数量小于原函数的参数长度时,递归调用createCurry函数            return createCurry.call(this,fn,...allArgs);         }else{           // 当参数数量满足时,触发函数执行           return fn.apply(this,allArgs);         }     } }   // 多个参数的普通函数 function add(a,b,c,d){     return a + b + c + d; } // 测试柯里化函数 let curryAdd = createCurry(add,1); console.log(curryAdd(2)(3)(4)); // 10

以上我们已经实现了灵活的柯里化函数,但是这里我们又发现了一个问题:

  • 如果我第一次就把参数全部传入,但是它并没有返回结果,而是一个函数(function)。
  • 只有我们再次将返回的函数调用一次才能返回结果:curryAdd(add,1,2,3,4)();
  • 可能有人说如果是全部传参,就调用原来的add()函数就行了,这也是一种办法;但是我们在这里既然是满足参数数量,对于这种情况我们还是处理一下。

在这里我们只需要在返回函数前做一下判断就行了:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 let createCurry = (fn,...params)=> {     let args = parsms || [];     let fnLen = fn.length; // 指定柯里化函数的参数长度     if(length === _args.length){        // 加入判断,如果第一次参数数量以经足够时就直接调用函数获取结果            return fn.apply(this,args);         }     return (...res)=> {         let allArgs = args.slice(0);         allArgs.push(...res);         if(allArgs.length < fnLen){            return createCurry.call(this,fn,...allArgs);         }else{           return fn.apply(this,allArgs);         }     } }

以上可以算是完成了一个灵活的柯里化的函数了,但是这里还不算很灵活,因为我们不能控制它什么时候执行,只要参数数量足够它就自动执行。我们希望实现一个可以控制它执行的时机该怎么办呢?

五、写一个可控制的执行时间的柯里化函数

我们这里直接说明一下函数公式:

  • fn(a,b,c) ==> fn(a)(b)(c )();
  • fn(a,b,c) ==> fn(a);fn(b);fn(c );fn();
  • 当我们参数足够时它并不会执行,只有我们再次调用一次函数它才会执行并返回结果。在这里我们在以上例子中加一个小小的条件就可以实现。
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 // 当参数满足,再次执行时调用函数 let createCurry = (fn,...params)=> {     let args = parsms || [];     let fnLen = fn.length; // 指定柯里化函数的参数长度     //当然这里的判断需要注释掉,不然当它第一次参数数量足够时就直接执行结果了     //if(length === _args.length){        // 加入判断,如果第一次参数数量以经足够时就直接调用函数获取结果            //return fn.apply(this,args);         //}     return (...res)=> {         let allArgs = args.slice(0);         allArgs.push(...res);         // 在这里判断输入的参数是否大于0,如果大于0在判断参数数量是否足够,         // 这里不能用 && ,如果用&& 也是参数数量足够时就执行结果了。         if(res.length > 0 || allArgs.length < fnLen){            return createCurry.call(this,fn,...allArgs);         }else{           return fn.apply(this,allArgs);         }     } }   // 多个参数的普通函数 function add(a,b,c,d){     return a + b + c + d; } // 测试可控制的柯里化函数 let curryAdd = createCurry(add,1); console.log(curryAdd(2)(3)(4)); // function console.log(curryAdd(2)(3)(4)()); // 10 console.log(curryAdd(2)(3)()); // 当参数不足够时返回 NaN

JavaScript函数柯里化详解_javascript技巧_脚本之家 (jb51.net)

标签:return,函数,js,add,参数,柯里化,fn
From: https://www.cnblogs.com/webljl/p/18211014

相关文章

  • lua打印调用的函数文件及行数
    lua根据调用堆栈可以打印调谁调用了我 string.split=function(s,delim)localsplit={}localpattern="[^"..delim.."]+"string.gsub(s,pattern,function(v)table.insert(split,v)end)returnsplitendfunctiongetWhoCallsMe()......
  • nodejs安装及环境配置
    Node.js的安装及环境配置可以遵循以下步骤:一、Node.js的安装访问Node.js的官方网站(https://nodejs.org/en/),下载对应你操作系统的Node.js安装包。找到下载的安装包目录,双击进行安装。在安装过程中,接受用户协议,选择安装的位置(最好是英文路径,不要有空格)。选择安装项,一般选择......
  • C++-函数
    函数(Function):是一个提前封装好的、可重复使用的、完成特定功能的独立代码单元。特点:提前封装、可重复使用的、完成特定功能将针对特定功能的、有重复使用需求的代码,提前封装到函数内,在需要的时候随时调用。基础函数语法return语句执行后,函数立刻结束函数不可定义在mai......
  • 在Go语言中如何实现变参函数和函数选项模式
    在Go语言编程中,我们经常会遇到需要给函数传递可选参数的情况。传统的做法是定义一个结构体,将所有可选参数作为结构体字段,然后在调用函数时创建该结构体的实例并传递。这种方式虽然可行,但是当可选参数较多时,创建结构体实例的代码就会变得冗长และ不太直观。Go语言的......
  • JS 监听用户页面访问&页面关闭并进行数据上报操作
    JS监听用户页面访问&页面关闭操作并进行数据上报前言最近在做安全方面的项目,有个需求是在用户访问页面和关闭页面的时候,发送对应的数据。刚拿到需求的时候,觉得没啥东西,init的时候发送一次,页面unload的时候发送一次就行了,很简单,后面开发了一下,又根据当前项目,发现没这么简单......
  • mySql 存储过程与函数
    过程CREATEDEFINER=`root`@`%`PROCEDURE`clearDate_Jk`()LANGUAGESQLNOTDETERMINISTICCONTAINSSQLSQLSECURITYDEFINERCOMMENT''BEGINDELETEFROMsys_deptWHEREcreate_time>'2023-12-31';truncatetablesys_file;ENDCREAT......
  • 浅谈C++函数
    目录一、函数的概念二、调用函数的两个前提三、函数传参的三种形式四、函数返回类型一、函数的概念函数是C++程序的基本模块,通常一个C++程序由一个或多个函数组成。函数可以完成用户指定的任务,一般分为库函数和用户自定义的函数。函数由函数头和函数体组成,函数头中包......
  • React后台管理(八)-- 开发页面前准备---插槽以及函数组件传值
    文章目录前言一、插槽的使用1.父组件结构如下2.子组件接受插槽内容二、父子函数组件传值1.父组件传值给子组件(1)父组件定义属性传值给子组件(2)子组件通过props去接收属性值2.子组件传值父组件(1)父组件接收子组件的值,更新数据(2)子组件通过方法,将值传出给父组件,类似vue的......
  • 47.C语言函数练习题整理
    题目来自练习册和牛客网的一些编程题目整理函数都有返回值且只有一个返回值声明类型为void可以返回空值若调用一个函数中没有return语句返回一个不确定的值形参是动态变量实参和形参之间的数据传递方式为实参到形参的单向值传递形参的值发生改变不会影响主调函数中的......
  • 基于three.js的Instanced Draw+LOD+Frustum Cull的改进实现
    大家好,本文在上文的基础上,优化了InstancedDraw+LOD+FrustumCull的性能,性能提升了3倍以上关键词:three.js、InstancedDraw、大场景、LOD、FrustumCull、优化、Web3D、WebGL、开源上文:three.js使用InstancedDraw+FrustumCull+LOD来渲染大场景(开源)相对于上文的改进点相对于......