首页 > 其他分享 >js知识点之函数柯里化

js知识点之函数柯里化

时间:2024-06-04 20:30:03浏览次数:25  
标签:function 知识点 return 函数 js add 参数 柯里化

函数柯里化

什么是函数柯里化

在计算机科学中,柯里化(英语:Currying),又译为卡瑞化或加里化,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。

这个技术由克里斯托弗·斯特雷奇以逻辑学家哈斯凯尔·加里命名的,尽管它是 Moses Schönfinkel 和戈特洛布·弗雷格发明的。

在直觉上,柯里化声称如果你固定某些参数,你将得到接受余下参数的一个函数。

我们姑且叫它返回函数,在调用返回函数的时候,它将判断当前的参数和之前被柯里化函数固定的参数拼起来之后,是否达到了原本函数的参数个数。

如果是,则执行原本的函数,得到结果;如果没有达到,则要继续调用柯里化函数来固定目前的参数。

在理论计算机科学中,柯里化提供了在简单的理论模型中,比如:只接受一个单一参数的 lambda 演算中,研究带有多个参数的函数的方式。

函数柯里化的对偶是Uncurrying,一种使用匿名单参数函数来实现多参数函数的方法。

柯里化快速入门

接下来,我们来通过一个简单的示例,让大家快速体会函数柯里化。

假设我们有一个求取两个数之和的函数:

function add(x, y) {
    return x + y;
}
console.log(add(1, 2)); // 3
console.log(add(5, 7)); // 12

在上面的示例中,我们有一个 add 函数,接收两个形参,返回两形参的和。

在调用的时候,我们每次也需要传递两个参数。

现在,我们对其进行柯里化,如下:

function add(x) {
    return function (y) {
        return x + y;
    }
}
console.log(add(1)(2)); // 3
console.log(add(5)(7)); // 3

在上面的代码中,我们对 add 函数进行了柯里化改造,只接受一个参数,但是返回的也不是值了,而是返回一个函数,这个函数也接收一个参数,然后利用闭包的特性,可以访问到最开始传入的 x 的值,最终返回 xy 的和。

所以,通过上面的这个示例,我们能够体会到前面所说的柯里化函数的特点:

一个柯里化的函数首先会接受一些参数,接受了这些参数之后,该函数并不会立即求值,而是继续返回另外一个函数,刚才传入的参数在函数形成的闭包中被保存起来。待到函数被真正需要求值的时候,之前传入的所有参数都会被一次性用于求值。

函数柯里化实际应用

通过上面的例子,我们体验到了什么是柯里化函数。

但是问题来了,费这么大劲封装一层,到底有什么用处呢?

没有好处想让我们程序员多干事情是不可能滴,这辈子都不可能。

所以接下来我们就来看一下函数柯里化的一个实际应用。

参数复用

就是将相同的参数固定下来。

// 正常正则验证字符串 reg.test(txt)

// 函数封装后
function check(reg, txt) {
    return reg.test(txt)
}

// 即使是相同的正则表达式,也需要重新传递一次
console.log(check(/\d+/g, 'test1')); // true
console.log(check(/\d+/g, 'testtest')); // false
console.log(check(/[a-z]+/g, 'test')); // true

// Currying后
function curryingCheck(reg) {
    return function (txt) {
        return reg.test(txt)
    }
}

// 正则表达式通过闭包保存了起来
var hasNumber = curryingCheck(/\d+/g)
var hasLetter = curryingCheck(/[a-z]+/g)

console.log(hasNumber('test1')); // true
console.log(hasNumber('testtest'));  // false
console.log(hasLetter('21212')); // false

上面的示例是一个正则的校验,正常来说直接调用 check 函数就可以了,但是如果我有很多地方都要校验是否有数字,其实就是需要将第一个参数 reg 进行复用,这样别的地方就能够直接调用 hasNumber、hasLetter 等函数,让参数能够复用,调用起来也更方便。

提前确认

/**
 * 
 * @param {要绑定事件的 DOM 元素} element 
 * @param {绑定什么事件} event 
 * @param {事件处理函数} handler 
 */
var on = function (element, event, handler) {
    if (document.addEventListener) {
        if (element && event && handler) {
            element.addEventListener(event, handler, false);
        }
    } else {
        if (element && event && handler) {
            element.attachEvent('on' + event, handler);
        }
    }
}

on(div, 'click', function(){})


var on = (function () {
    if (document.addEventListener) {
        return function (element, event, handler) {
            if (element && event && handler) {
                element.addEventListener(event, handler, false);
            }
        };
    } else {
        return function (element, event, handler) {
            if (element && event && handler) {
                element.attachEvent('on' + event, handler);
            }
        };
    }
})();

on(div, 'click', function(){})

//换一种写法可能比较好理解一点,上面就是把 isSupport 这个参数给先确定下来了
var on = function (isSupport, element, event, handler) {
    isSupport = isSupport || document.addEventListener;
    if (isSupport) {
        return element.addEventListener(event, handler, false);
    } else {
        return element.attachEvent('on' + event, handler);
    }
}
on(true, div, 'click', function(){})
on(true, div, 'click', function(){})
on(true, div, 'click', function(){})

我们在做项目的过程中,封装一些 DOM 操作可以说再常见不过,上面第一种写法也是比较常见,但是我们看看第二种写法,它相对于第一种写法就是自执行然后返回一个新的函数,这样其实就是提前确定了会走哪一个方法,避免每次都进行判断。

封装通用柯里化函数

接下来我们来封装一个通用的柯里化函数。

function curry(...args) {
	const fn = args[0];  // 获取要执行的函数
	const rest = args.slice(1); // 获取传递的参数,构成一个参数数组
    // 如果传递的参数已经等于执行函数所需的参数数量
	if (rest.length === fn.length) {
		return fn.apply(this, rest);
	}
	function _curry(...args) {
        // 推入之前判断
        // 将新接收到的参数推入到参数数组中
		args.forEach((item) => {
			rest.push(item);
		});
		if (rest.length === fn.length) {
			return fn.apply(this, rest);
		}
		return _curry;
	}
	return _curry;
}

对上面的代码进行测试:

// 测试 1
function add(a, b, c) {
    return a + b + c;
}

console.log(curry(add)(1)(2)(3)); // 6
console.log(curry(add, 1)(2)(3)); // 6
console.log(curry(add, 1, 2, 3)); // 6
console.log(curry(add, 1)(3, 4)); // 8

var addCurrying = curry(add)(2);
console.log(addCurrying(7)(8)); // 17

// 测试 2
function check(reg, txt) {
    return reg.test(txt)
}
var hasNumber = curry(check)(/\d+/g);
console.log(hasNumber('test1'));// true

一道经典的柯里化面试题

实现一个 add 方法,使计算结果能够满足如下预期:

add(1)(2)(3) = 6;
add(1, 2, 3)(4) = 10;
add(1)(2)(3)(4)(5) = 15;

要完成上面的需求,我们就可以使用柯里化函数:

function add() {
    // 第一次执行时,定义一个数组专门用来存储所有的参数
    var _args = Array.prototype.slice.call(arguments);

    // 在内部声明一个函数,利用闭包的特性保存 _args 并收集所有的参数值
    var _adder = function () {
        _args.push(...arguments);
        return _adder;
    };

    // 这个是最后输出的时候被调用的,return 后面如果是函数体,
    // 为了输出函数体字符串会自动调用 toString 方法
    // 利用 toString 隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
    _adder.toString = function () {
        return _args.reduce(function (a, b) {
            return a + b;
        });
    }

    // 这个 return 是第一次调用的时候返回上面的函数体,
    // 这样后面所有的括号再执行的时候就是执行 _adder 函数体
    return _adder;
}
console.log(add(1)(2)(3).toString()); // 6
console.log(add(1, 2, 3)(4).toString()); // 10
console.log(add(1)(2)(3)(4)(5).toString()); // 15
console.log(add(2, 6)(1).toString()); // 9

真题详解

  • 什么是函数柯里化?

参考答案:

柯里化(currying)又称部分求值。一个柯里化的函数首先会接受一些参数,接受了这些参数之后,该函数并不会立即求值,而是继续返回另外一个函数,刚才传入的参数在函数形成的闭包中被保存起来。待到函数被真正需要求值的时候,之前传入的所有参数都会被一次性用于求值。

举个例子,就是把原本:

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)

-EOF-

标签:function,知识点,return,函数,js,add,参数,柯里化
From: https://blog.csdn.net/fyw1789/article/details/139449256

相关文章

  • python之pyexecjs
    pyexecjs是一个用Python来执行JavaScript代码的工具库,该库支持多种JavaScript运行时环境,如Node.js、PhantomJS、SlimerJS等,允许开发者在Python中无缝地调用和执行JavaScript代码。 [安装]pipinstallpyexecjs [使用]1.eval方式importexecjsprint(execjs.eval('"abc......
  • `jsonb` 报错 `invalid input syntax for type timestamp with time zone ““
    哈喽,大家好,我是木头左!大家好,我是你们的朋友,公众号博主。今天要聊一聊一个常见的数据库问题:jsonb报错invalidinputsyntaxfortypetimestampwithtimezone:""。这个问题可能会影响到你的开发工作,但是别担心,我会用最简单易懂的方式,帮助你解决这个问题。1.问题解析需要......
  • Vue.js 动画与过渡效果实战
    title:Vue.js动画与过渡效果实战date:2024/6/4updated:2024/6/4description:这篇文章介绍了如何在网页设计中使用过渡动画和组件效果,以及如何利用模式和列表展示信息。还提到了使用钩子实现组件间通信的方法。categories:前端开发tags:过渡动画组件效果模式......
  • 数据库知识点和一些命令以及使用步骤
     一、基本命令:(1)连接本地数据库服务:mysql-uroot-p(2)连接其它电脑上的数据库服务:mysql-hip地址-uroot-p(3)在连接数据库服务时直接选择库:mysql-D库名-uroot-p(4)退出数据库服务:exit或quit或\q二、使用步骤:1、链接数据库服务2、创建一个数据库—>选择库3、设......
  • etherjs估算gasLimit(调用estimateGas方法)的两种方式
    前言:一种是provider,一种是signer  方式一:直接获取constEtherJS=require('etherjs');//创建一个Provider实例,指向你的以太坊节点constprovider=newEtherJS.providers.JsonRpcProvider('http://localhost:8545');//构造一个交易consttransaction={to:......
  • JSON类型处理器
    数据库的user表中有一个info字段,是JSON类型:格式像这样:{"age":20,"intro":"佛系青年","gender":"male"}而目前User实体类中却是String类型:这样一来,我们要读取info中的属性时就非常不方便。如果要方便获取,info的类型最好是一个Map或者实体类。而一旦我们把info改为对象......
  • 基于JSP的农产品供销服务系统
    你好呀,我是计算机学长猫哥!如果有需求可以文末加我。开发语言:Java数据库:MySQL技术:JSP技术工具:IDEA/Eclipse、Navicat、Maven系统展示首页用户注册农产品管理订单管理摘要本文主要介绍了农产品供销服务系统的设计和实现。系统采用JSP技术,基于B/S结构,使用MySQL......
  • 将来自 Telegraf 的 JSON 数据扁平化,以便在 ThingsBoard 中使用
    我连接了ThingsBoard和Telegraf以可视化CPU使用率,但收到的数据是嵌套JSON格式。我尝试了不同的方法,但无法以扁平化的JSON格式获取数据。使用Telegraf1.30.0版本,数据以以下格式返回:[{"fields":{"usage_guest":0、"usage_guest_nice":0、......
  • C++知识点
    explicit关键字explicit关键字explicit关键字在理解explicit关键字之前,我们必须要了解构造函数的类型转换作用,以便于我们更好的理解explicit关键字,如果有不懂构造函数,可以来看看这篇文章:构造函数点击查看代码classDate{public://构造函数Date(intyear):_......
  • 统计学知识点
    一、选择题。1.对50名联工的工资收入情况进行调查,则总体单位是()A.50名职工B.50名职工的工资总额C.每一名职工 D.每一名职工的工资2.按调查对象包括的范围不同,统计调查可以分为()。①全面调查②抽样调查③非全面调查A①②③        B①③      ......