函数柯里化

简介: 函数柯里化

实现apply、call、bind

  • 实现apply、call、bind函数
  • 注意:我们的实现是练习函数、this、调用关系,不会过度考虑一些边界情况

call函数的手写实现

给函数上面添加一个叫做hycall方法的方式:


  1. 只能给某个函数添加方法
  • foo.hycall = function(){}


  1. 给所有的函数添加一个hycall的方法
  • Funtion.prototype.hycall = function(){}


Function.prototype.hycall = function(){
    console.log("原型链调用了");
}
function foo(){
    console.log("foo函数调用了");
}
foo.hycall()//原型链调用了
----------------------------------------------------------
//我们在执行foo.hycall的时候会发现只执行了hycall函数,但是却把原本foo函数自带的信息给掩盖掉了,这肯定是不合理的,我们是要在原有的基础上进行调用,而不是另起大厦

改良写法

//差的写法,缺乏复用性
Function.prototype.hycall = function(){
    console.log("原型链调用了");
    foo()
}
function foo(){
    console.log("foo函数调用了");
}
foo.hycall()
//好的写法,可以多次复用
//本身我们在进行调用的时候,就相当于已经是隐式绑定了,foo.hycall()的时候,this的绑定就已经绑到foo上面了
Function.prototype.hycall = function(){
    console.log("原型链调用了");
    var fn = this
    fn()
}
function foo(){
    console.log("foo函数调用了");
}
foo.hycall()

怎么实现给函数传递参数

我们往call函数内传递参数,会在调用的this中打印出来,但是在我们自己手写的函数里面,打印出来的却是windows,如何解决这个问题?

Function.prototype.hycall = function(thisArg){
    var fn = this
    //调用需要被执行的函数
    thisArg.fn = fn
    thisArg.fn()
    //等函数执行完之后删掉这个属性
    delete thisArg.fn
}
function foo(){
    console.log(this);
}
foo.hycall({name:"小余",age:"20"})//{ name: '小余', age: '20', fn: [Function: foo] }

此时手写的函数已经可以传递参数了,但能传递的仅仅是对象,如果传递数字会报错,接下来我们就解决这个问题


//使用call函数传递数字参数的话
foo.call(123)//[Number: 123]
//对thisArg转成对象类型(防止传入非对象类型报错)
thisArg = thisArg ? Object(thisArg) : window

call除了第一个参数是用来改变this绑定的,后面还可以绑定一堆参数,这个放到我们自己实现要怎么去做呢?在之前是可以使用argument,现在ES6中有更好的解决方法了

ES6中的剩余参数

对于接收多少个参数我们是不确定的,那这个时候,我们形参的部分就不能够写死了,不然是写不尽写不完的,你也不能够100%猜到用户想输入几个参数

function sum(...args){
    //打印出来的args是数组形式 
    console.log(args)
    //展开运算符spread
    console.log(...args)//得到的直接是数值
}

很显然通过ES6中的...的方式来解决,接着进到最后的一步,返回结果

Function.prototype.hycall = function(thisArg,...args){
    // console.log("传递参数进来了噢",this);
    var fn = this
    //对thisArg转成对象类型(防止传入非对象类型报错)
    thisArg = thisArg ? Object(thisArg) : window
    //调用需要被执行的函数
    thisArg.fn = fn
    var result = thisArg.fn(...args)
    //等函数执行完之后删掉这个属性
    delete thisArg.fn
  //返回结果
    return result
}
function foo(){
    console.log(this);
}
foo.hycall(123,6,66,666,6666)
//==========================================>换个函数执行一下
function foo(num1,num2,num3){
    console.log("foo的this指向是",this,"三数相加的结果=",num1+num2+num3);
}
foo.mycall("小余",500,20,1)
//foo的this指向是 String {'小余', fn: ƒ} 三数相加的结果= 521

apply函数的手写实现

//自己实现hyapply
Function.prototype.myapply = function(thisArgs,argArray){//区别在于这里不需要ES6的...运算,因为传入的是一整个数组
    var fn = this
    thisArgs = thisArgs ? Object(thisArgs) : window
    thisArgs.fn = fn
    var arr = thisArgs.fn(...argArray)//但是数组需要解构出来
    delete thisArgs.fn
    return arr
}
function sum(num1,num2){
    console.log("sum被调用",this,num1,num2);
}
var result = sum.myapply("小余",[200,30])
console.log(result,'老铁666');

在call函数的基础上进行小范围修改,貌似已经满足了apply函数的要求了,但是当我们除了第一个用于this指定之外,其他参数我们不传就会出现问题了


  • 因为当我们不传递参数的时候,argArray就会是undefined,我们在解构的时候就会变成...undefined了,对underfunded进行扩展是错误的
  • 这个时候我们就可以进行一个判断来解决这个问题
  • 那为什么在call函数的手写的时候没有遇到这个问题呢?因为我们在call函数中的形参是...args,那这东西的格式就是个数组,你什么都不传都默认是空数组[]
Function.prototype.myapply = function(thisArgs,argArray){
    var fn = this
    thisArgs = thisArgs ? Object(thisArgs) : window
    thisArgs.fn = fn
  argArray = argArray || []//不止这种写法,也可以使用三元运算符
    var arr = thisArgs.fn(...argArray)
    delete thisArgs.fn
    return arr
}
function sum(num1,num2,num3){
    console.log("sum被调用",this,num1,num2+num3);
}
var result = sum.myapply("小余")
console.log(result,'老铁666');

手写call和apply的补充

在我们改变this指向的时候,输入0的时候,会指向window,这是由于我们三元运算符那些的写法导致的,也有其他情况可以避免,这样是对边界效应的一种延伸考虑

thisArg = (thisArg !== undefined && thisArg !== null) ? Object(thisArg) : window

bind函数的手写实现

首先我们来看看JS中bind传参数的3种方式,第一种传值方式跟call的方式很像,从第二种跟第三种开始,跟其他两个函数(call、apply)发生了不一样的变化,我们来看看他们是怎么实现的吧

//方式1:在bind中传值
function foo(num1,num2,num3,num4){
    console.log(this,num1,num2,num3,num4);
}
var bar = foo.bind('小余',10,20,30,40)
bar()
//方式2:在接收bind的bar中传值
function foo(num1,num2,num3,num4){
    console.log(this,num1,num2,num3,num4);
}
var bar = foo.bind('小余')
bar(10,20,30,40)
//方式3:方式1跟方式2的结合,从方式1到方式2中按顺序传递
function foo(num1,num2,num3,num4){
    console.log(this,num1,num2,num3,num4);
}
var bar = foo.bind('小余',10,20)
bar(30,40)
//以上三种方式的答案都是:String {'小余'} 10 20 30 40

我们是有变量接收值的,就像上方的bar来接收,对于这种情况就需要在手写的函数中最后进行一个return返回

Function.prototype.mybind = function(thisArg,...argArray){
    function proxyFn(){
    }
    return proxyFn
}

首先我们揭秘第二种方式是怎么做到的,也就是得到了值还能继续往里面传值,那是因为我们手写bind函数返回的还是一个函数,而里面这个函数是可以接收值的,所有自然就造成这种情况了,第三种则是手写bind函数跟bind函数内部的函数都可以接收值,然后再做一个拼接就完成了

Function.prototype.mybind = function(thisArg,...argArray){
    //1.获取真实要调取的函数
    var fn = this
    //对特殊情况的处理
    thisArg = (thisArg !== undefined && thisArg !== null) ? Object(thisArg) : window
    function proxyFn(...args){
        //将函数放到thisArg中进行调用
        thisArg.fn = fn
        //对传入的两个参数进行合并
        var finalArgs = [...argArray,...args]
        var result = thisArg.fn(finalArgs)
        delete thisArg.fn
        //返回结果
        return result
    }
    return proxyFn
}
function foo(num1,num2,num3,num4){
    console.log(this,num1,num2,num3,num4);
}
var bar = foo.bind('小余',10,20)
bar(30,80)

认识arguments

  • argument是一个对应于 传递给函数的参数类数组(array-like)对象
  • 类数组对象什么意思?就是长得像数组,但实际上是个对象
  • 我们在实参传递的个数如果超过形参的数量的话,多余的不是丢弃掉,而是跟着前面其他几个参数一起放到argument中了
  • array-like意味着它不是一个数组类型,而是一个对象类型:
  • 但是它却拥有数组的一些特性,比如说length,比如说可以通过index索引来访问
  • 但是它却没有数组的一些方法,比如forEach、map等等
//argument的基础使用
function foo(num1,num2,num3){
    // console.log(arguments);
    //常见对argument的3个操作
    //1.获取参数长度
    console.log(arguments.length);
    //2。根据索引值获取某一个参数,像数组一样的操作
    console.log(arguments[1]);
    //3.callee属性,获取argument中所在的函数
    console.log(arguments.callee);
}   
foo(10,20,30,40,50)

argument转数组

function foo(num1,num2,num3,num4){
    //1.自己遍历
    var newArr = []
    for(var i = 0;i < arguments.length;i++){
        newArr.push(arguments[i] *40)
    }
    console.log(newArr);
    //2.arguments转成array数组类型
    //2.1自己遍历arguments中所有的元素
    //2.2 使用slice
    var newArr2 = Array.prototype.slice.call(arguments)
    console.log(newArr2,"这是newArr2");
    //这里其实跟2.2是一样的,this显示绑定arguments大于隐式绑定
    var newArr3 = [].slice.call(arguments)
    console.log(newArr3)
    //2.3 ES6的语法
    var newArr4 = Array.from(arguments)
    console.log(newArr4)
    //展开运算符
    var newArr5 = [...arguments]
    console.log(newArr5)
}
foo(1,2,3,4)

数组里的slice实现

//补充:在原型链上加函数的方法,我们确实可以在每个函数上面都调用,但是当我们想要调用这个函数的方法本身的时候,就略显麻烦
Function.prototype.aaa = function(){
    //xxxx
}
Function.prototype.aaa()//这样调用
//自己简单实现的slice
Array.prototype.hyslice = function(start,end){//这里可以进行优化,看用户是否有传递进来这两个参数,没有的话我们就做一个判断处理
    var arr = this
    start = start || 0
    end = end || arr.length
    var newArray = []
    for(var i = start;i < end ;i++){
        newArray.push(arr[i])//就将this里的东西给填入进去,而this已经被我们手动改成我们想要的指向了
    }
    return newArray
}
var newArray = Array.prototype.hyslice.call(["小余","大余","超大余"],1,3)//相当于使用call来调用slice,我们上方函数里的this指向就被改变到我们手写的这个数组内容上了
console.log(newArray);

箭头函数-无arguments

我们在箭头函数中是没有arguments的,如果你需要的话,js会去上层作用域里面找


向下方这个,arguments到上层作用域,也就是全局作用域中寻找,这全局作用域是分两种情况的,在node中是有的,在浏览器中则是没有(显示你没有定义)

var name = "小余"
var foo = ()=>{
    console.log(name)
    console.log(arguments);
}
foo()

案例

在ES6中用...剩余函数来替代arguments,用来接收所有参数,形成数组

function foo(){
    var bar = ()=>{
        console.log(arguments);//打印出来的就是上层foo的arguments
    }
    return bar
}
var fn = foo(123)
fn()
目录
相关文章
|
26天前
|
存储 算法
什么是高阶函数
什么是高阶函数
30 1
|
6月前
2 # 函数柯里化
2 # 函数柯里化
15 0
|
6月前
|
JavaScript 前端开发 Java
函数式编程入门:理解纯函数、高阶函数与柯里化
函数式编程入门:理解纯函数、高阶函数与柯里化
62 0
|
6月前
1 # 高阶函数
1 # 高阶函数
31 0
|
9月前
柯里化函数简单实现
柯里化是一种函数式编程技术,可以将一个接受多个参数的函数转换成一系列接受一个参数的函数,这些函数可以在被顺序调用的过程中逐步累积参数,最终返回结果。
|
10月前
|
存储 JavaScript 前端开发
柯里化
柯里化
55 0
高阶函数和回调函数的区别
高阶函数和回调函数的区别
高阶函数和回调函数的区别
|
存储
函数柯里化详解
函数柯里化详解
98 0
|
编译器 Scala 开发者
函数柯里化 | 学习笔记
快速学习函数柯里化
64 0
|
自然语言处理 JavaScript 前端开发
一文讲懂什么是函数柯里化,柯里化的目的及其代码实现
一文讲懂什么是函数柯里化,柯里化的目的及其代码实现
243 0
一文讲懂什么是函数柯里化,柯里化的目的及其代码实现