IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> 函数柯里化怎么实现 -> 正文阅读

[JavaScript知识库]函数柯里化怎么实现

总结要点:

  • 柯里化实现只需要2层嵌套,内层只需要是匿名函数。内层函数分类讨论,处理递归调用和运行f这两种情况。
  • 柯里化实现用到了f.length,是为了知道,当前收集的参数个数够不够f接收的参数个数。

柯里化是一种转换,将 f(a,b,c) 转换为可以被以 f(a)(b)(c) 的形式进行调用。JavaScript实现通常都保持该函数可以被正常调用,并且如果参数数量不足,则返回偏函数

既然柯里化能知道参数数量是否足够,那么显然它只允许确定参数长度的函数,再强调一遍:

柯里化要求函数具有固定数量的参数。使用 rest 参数的函数,例如 f(...args),不能以这种方式进行柯里化。

柯里化实现

首先,function有name和length这两种常用属性

function f(a,b,...args){}//f.length === 2

根据这个length属性,我们只需要在函数套函数套函数(3层嵌套,因为我们要在下一层括号括起来之后再运行递归调用的curry函数)递归收集参数(用concat),直到参数个数达到f.length,运行函数并结束递归。其中f是传进来的函数。

function curryIt(f,context){
    return function curry(){
        if(arguments.length >= f.length){
            return f.apply(context,arguments)
        }
        let args = [].slice.call(arguments)
        return function(){
            return curry.apply(context,args.concat([].slice.call(arguments)))
        }
    }
}
//test
let sum = curryIt((a,b,c,d,e) => a + b + c + d + e)
console.log(sum(1,2)(3)(4,5))
console.log(sum(10,20)(30,40)(50))
console.log(sum(100,200,300,400,500))
let o = {
    name: 'wsw',
    f: function(x,y,z){console.log(this.name,x,y,z)}
}
let o1 = {name: 'hans'}
let oF = curryIt(o.f,o)
console.log(oF(11)(45)(14))//wsw ...
oF = curryIt(o.f,o1)
console.log(oF(114)(514)(0))//hans ...
把它放到原型链里
Function.prototype.curryIt = function(context){
    if(typeof this !== 'function') throw new Error('应该是函数调用!')
    let self = this
    return function curry(){
        if(self.length <= arguments.length){
            return self.apply(context,arguments)
        }
        let args = [].slice.call(arguments)
        return function(){
            return curry.apply(context,args.concat([].slice.call(arguments)))
        }
    }
}
//test
let sum = ((a,b,c,d,e) => a + b + c + d + e).curryIt()
console.log(sum(1,2)(3)(4,5))
console.log(sum(10,20)(30,40)(50))
console.log(sum(100,200,300,400,500))
let o = {
    name: 'wsw',
    f: function(x,y,z){
        console.log(this.name,x,y,z)
        return this
    }
}
let o1 = {name: 'hans'}
let oF = o.f.curryIt(o)
console.log(oF(11)(45)(14))//wsw 11 45 14;o
oF = o.f.curryIt(o1)
console.log(oF(114)(514)(0))//hans 114 514 0;o1

但总觉着3层嵌套的函数非常抽象,能不能只嵌套2层?当然可以!

  • 递归的不再是内层定义的curry函数,而是外层的curryIt函数。
  • 为了不再使用[].slice.call(arguments,1)这种冗长的写法,我们规定了args是一个参数数组,且默认值为空数组。
  • 这种写法允许我们像apply一样预先接收一些参数了。
Function.prototype.curryIt = function(context = null,args = []){
  let f = this
  if(typeof this !== 'function') throw new Error('应该是函数调用!')
  return function(){
    let all = args.concat([].slice.call(arguments))
    if(all.length >= f.length){
      return f.apply(context,all)
    }
    return Function.prototype.curryIt.call(f,context,all)
  }
}
//test
let sum = ((a,b,c,d,e) => a + b + c + d + e).curryIt()
console.log(sum(1,2)(3)(4,5))
console.log(sum(10,20)(30,40)(50))
console.log(sum(100,200,300,400,500))
let o = {
    name: 'wsw',
    f: function(x,y,z){
        console.log(this.name,x,y,z)
        return this
    }
}
let o1 = {name: 'hans'}
let oF = o.f.curryIt(o)
console.log(oF(11)(45)(14))//wsw 11 45 14;o
oF = o.f.curryIt(o1)
console.log(oF(114)(514)(110))//hans 114 514 110;o1
oF = o.f.curryIt(o1,[114])//递归外层函数的写法,可以预先接收一些参数了
console.log(oF(5)(14))//hans 114 5 14;o1
oF = o.f.curryIt(o1,[19])
console.log(oF(19)(810))//hans 19 19 810;o1

小插曲:一开始let all = args.concat([].slice.call(arguments))写的是args = args.concat([].slice.call(arguments)),于是发现测试代码里,第二次调用sum的时候报错。后来才发现args在函数运行完后应该保持原貌!h鶸不会最基本的递归不会最基本的遍历数组又一石锤。呜呜呜~

我们有一个不够完备的手打bind,它的思想和柯里化类似,也是外层函数参数拼接内层函数参数,但理解起来更简单。简单的原因,也许是它只有”运行“这一种情况,但柯里化还多了”递归“这种情况。并且同样能实现以上test里的功能。

Function.prototype._bind = function (context) {
  let self = this;
  if(typeof this !== 'function') throw new Error('应该是函数调用!')
  let firstArg = [].slice.call(arguments,1);
  return function () {
    let secArg = [].slice.call(arguments);
    let finishArg = firstArg.concat(secArg);
    return self.apply(context,finishArg);
  }
}

function sum(){return [].reduce.call(arguments,(a,b) => a+b,0)}
let f = sum._bind(null,10,20,30)
let g = f._bind(null,40,50)
console.log(g())//150
let g1 = g._bind(null,60)
console.log(g1())//210
let h = f._bind(null,60)
console.log(h())//120

参考:https://zh.javascript.info/currying-partials

  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2021-09-24 10:26:52  更:2021-09-24 10:27:20 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/27 11:57:53-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码