• 实现一个通用的函数柯里化的函数



    本文目的

    这篇文章会一步一步带你实现一个通用的函数柯里化的函数,即使你完全不懂柯里化这个概念也没关系,我会先介绍柯里化函数的概念,然后以实现一个完整的判断变量类型的函数的例子来让你直观体验函数柯里化,然后再实现一个通用的函数柯里化函数,所以这篇文章至少能带你掌握这两点:

    1. 实现一个通用的函数柯里化函数
    2. 借助柯里化实现一个完整的判断变量类型的函数

    总体目录

    1. 函数柯里化的概念
    2. 柯里化函数初体验
    3. 实现一个通用的函数柯里化函数
    4. 小结

    函数柯里化概念

    首先什么是函数柯里化?得先把概念搞清楚,函数柯里化就是把多个参数的传入转化成 n 个函数依次进行传参。(注意,柯里化一般都要求参数个数是确定的,并且是一个一个传)

    觉得有点抽象没关系,下面以一个实现判断变量类型函数的例子来体验下柯里化函数。

    首先我们先来思考一下可以用什么方法来判断类型?

    1. 我们可以用typeof来判断类型,但是用 typeof 有缺陷,它无法进一步判断对象,并且typeof null的值是 object,所以 typeof 我们一般用于判断基础类型;
    2. 除了 typeof 之外,我们可以用 instanceof来判断谁是谁的实例;
    3. Object.prototype.toString.call 来判断具体类型,返回的是一个字符串;
    4. 通过constructor

    那我们就开写,通过Object.prototype.toString.call,实现一个基础版本:

    	function isType(val, type) {
    	  return Object.prototype.toString.call(val) === `[object ${type}]`
    	}
    	
    	console.log(isType(123, 'String'));   // false
    	console.log(isType(123, 'Number'));   // true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    但是这么做需要用户手动去写'String'这些常量,有点风险,你怎么保证用户不会一不小心把'String'写成'Stirng'呢?

    柯里化函数初体验

    怎么解决上面说的那个问题?那我就希望这个isType()函数能更具体些,形如isString() isNumber()之类的。

    那么此时柯里化就要登场了,以isString为例,来直观感受一下柯里化:原来isType函数是传了两个参数,那么我就要把isString写成两次调用的形式:

    	function isString(type) {
    	  return function(val) {
    	    return Object.prototype.toString.call(val) === `[object ${type}]`
    	  }
    	}
    	
    	let myIsString = isString('String')
    	console.log(myIsString(123));       // false
    	console.log(myIsString('123'));     // true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    isString('String')是第一次调用传参,调用完返回一个函数赋值给了myIsString,随后myIsString()第二次调用传参123,执行返回结果,这就是函数柯里化的感觉。

    如此一来,我们就可以让用户直接通过 myIsString()来判断是否是字符串了。

    要完整判断类型,只判断 String 肯定不够,那接下来就要实现isNumber isNull等等,一个一个写? 很麻烦,能不能有个通用的柯里化函数来帮我们完成这件事呢?我只要传String``Number这些参数就好,而不必自己去手动实现isNumber等等函数。

    下面就来实现这个通用的柯里化函数,然后再解决上面说的这个类型判断的问题。


    实现一个通用的函数柯里化函数

    实现这个通用柯里化函数的思路:因为我们要把函数fn(fn就是我们希望将其柯里化的函数)的传参,改为一个一个传,并且传一个参数,就返回一个函数,所以我们每次传参的时候我们都要将参数进行记录,当记录的参数个数小于原本函数fn参数的个数时,就返回一个新的函数,如果等于,就真正执行这个函数fn。

    看代码:

    	function curring(fn) {
    	  const inner = (args = []) => {
    	    return args.length >= fn.length ? fn(...args) : (...userArgs) => inner([...args, ...userArgs]) 
    	  }
    	  return inner()
    	}
    	
    	// test
    	let sum1 = curring(sum)
    	let sum2 = sum1(1)
    	let sum3 = sum2(2,3)
    	let result = sum3(4)
    	console.log(result);    // 10
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    inner 主要是用来保存传入的参数,存在 args 中,userArgs代表每次调用新传的参数,如果累积的参数和原函数fn的个数一样,就执行fn,否则就把参数继续存到 args 中。这里面有递归的思想,不熟悉递归的多看多写几遍,好好体会。

    上面的这个curring就是一个通用的实现函数柯里化的函数了,下面我们就用这个通用的柯里化函数来实现我们还未解决的类型判断问题。

    	function isType(type, val) {
    	  return Object.prototype.toString.call(val) === `[object ${type}]`
    	}
    	let util = {};
    	let typeArr = ['String', 'Number', 'Boolean', 'Null', 'Undefined', 'Symbol', 'Function', 'Array', 'Object', 'Date']
    	typeArr .forEach(type => {
    	  util[`is${type}`] = curring(isType)(type)
    	});
    
    	// test
    	console.log(util.isNumber('123'))  // false
    	console.log(util.isNull(null))  // true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    小结

    第一遍看不懂没关系,下面这段核心代码,一定要结合文章多看几遍,多敲几遍,仔细体会它是如何把 fn 这个函数进行柯里化的。

    	function curring(fn) {
    	  const inner = (args = []) => {
    	    return args.length >= fn.length ? fn(...args) : (...userArgs) => inner([...args, ...userArgs]) 
    	  }
    	  return inner()
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
  • 相关阅读:
    代码随想录算法训练营第二十七天| 131.分割回文串
    用户画像的应用场景
    JDK内置命令工具
    高性能 低功耗Cortex-A53核心板 | i.MX8M Mini
    2023-10-23 LeetCode每日一题(老人的数目)
    十五、异常(4)
    C++ 小游戏 视频及资料集(十)
    Springboot毕设项目基于Springboot的手机电商网站lmo47(java+VUE+Mybatis+Maven+Mysql)
    EfficientDet论文讲解
    CSS标准流
  • 原文地址:https://blog.csdn.net/Curry_On/article/details/128007120