javascript - 求助关于call和apply的问题,反柯里化

查看:80
本文介绍了javascript - 求助关于call和apply的问题,反柯里化的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

问 题

下面是uncurring的两种实现

实现1

Function.prototype.uncurrying = function(){
    var self = this;

    return function(){
        // 获取传入的上下文对象
        var context = Array.prototype.shift.call(arguments);
        // 这里的this是调用uncurrying者
        return self.apply(context, arguments);
    };
};

var push = Array.prototype.push.uncurrying ();
var arr = [];
push(arr, 1); // ==> arr = [1]
push(arr, 4); // ==> arr = [1, 4]

实现2

Function.prototype.uncurrying = function(){
    var self = this;

    return function(){
        return Function.prototype.call.apply(self, arguments);
    };
};

var push = Array.prototype.push.uncurrying ();
var arr = [];
push(arr, 1); // ==> arr = [1]
push(arr, 4); // ==> arr = [1, 4]

两种结果是一样的,但是第二种实现的方式我有点迷糊,主要是这里

第一种方式显示的用self,在这里也就是push方法执行了一下,

    self.apply(context, arguments);

但是如下第二种实现方式,却没有发现self执行的痕迹,
按我的理解这里就是用apply修改call的上下文为self,这里也就是push,
但这样有执行push方法吗?难道call内部的实现帮忙执行了self?求解

    Function.prototype.call.apply(self, arguments);

瞬间被你点通,谢谢 !

louiszhai

Function.prototype.call.apply(self, arguments);
先用apply修改了call的上下文为self,
后续调用uncurrying,相当于在self上调用call方法,也就执行了self

解决方案

Function.prototype.call.apply(self, arguments);这个看起来有些绕,其实很好理解。
实际上,由你的第二种实现还可以推出反柯里化的第三种实现

Function.prototype.unCurrying = function () { 
  return this.call.bind(this);
};
var push = Array.prototype.push.unCurrying(), obj = {};
push(obj, '123', '456');
console.log(obj); //Object {0: "123", 1: "456", length: 2}

接下来我会先分析下你的第二种实现,再分析第三种实现。你的实现是这样的:

Function.prototype.uncurrying = function(){
    var self = this;
    return function(){
        return Function.prototype.call.apply(self, arguments);
    };
};
var push = Array.prototype.push.uncurrying();

谁调用uncurrying,谁就等于thisself. 这意味着self就是数组的push方法.
替换掉self,最终外部的push等同如下函数:

function(){
  return Function.prototype.call.apply(Array.prototype.push, arguments);
};

函数放在这里,我们先来理解apply函数,apply有分解数组为一个个参数的作用。

推导公式a.apply(b, arguments) 意味着把b当做this上下文,相当于是在b上调用a方法,并且传入所有的参数,如果b中本身就含有a方法,那么就相当于 b.a(arg1, arg2,…)

公式1a.apply(b, arguments) === b.a(arg1, arg2,…)

由于callapply 除参数处理不一致之外,其他作用一致,那么公式可以进一步演化得到:

公式2a.call(b, arg) === b.a(arg)

公式1这些代入上面的函数,有:

a = Function.prototype.call 即a等于call方法。

我们接着代入公式,有:

b = Array.prototype.push 即b等于数组的push方法

那么 Function.prototype.call.apply(Array.prototype.push, arguments)就相对于:

Array.prototype.push.call(arg1, arg2,…),那么:

push([], 1) 就相当于 Array.prototype.push.call([], 1),再代入公式2,相当于:

[].push(1)

答案已经呼之欲出了,就是往数组中末尾添加数字1。



接下来我来分析反柯里化的第三种实现:

对于this.call.bind(this);部分,this相当于Array.prototype.push,那么整体等同于如下:

Array.prototype.push.call.bind(Array.prototype.push)

这里的难点在于bind方法,bind的实现比较简单,如下:

Function.prototype.bind = function(thisArg){
  var _this = this;
  var _arg = _slice.call(arguments,1);
  return function(){
       var arg = _slice.call(arguments);
    arg = _arg.concat(arg);
      return _this.apply(thisArg,arg);
  }
}

想要理解必须化繁为简,理解得越简单,也就理解得越透彻。进一步简化bind的原理,等同于谁调用bind,就返回一个新的function。

我们假设函数fn调用bind方法如fn.bind([1, 2]),经过简化,忽略bind绑定参数的部分,最终返回如下:

function(){
  return fn.apply([1, 2], arguments);
}

以上,将fn替换为 Array.prototype.push.call[1, 2]替换为 Array.prototype.push,那么:

Array.prototype.push.call.bind(Array.prototype.push) 将等同于:

function(){
  return Array.prototype.push.call.apply(Array.prototype.push, arguments);
}

这个看起来和反柯里化的第二种实现有些不大相同,不要急,虽然表面上看起来不一致,但骨子里还是一致的。请耐心往下看:

不同的地方在于前半部分 Array.prototype.push.call,这里它是一个整体,实际上想代表的就是call方法。而我们都知道,所有函数的call方法,最终都是Function.prototypecall方法。那么,就有如下恒等式成立:

Array.prototype.push.call === Function.prototype.call //true

那么以上函数将等同于:

function(){
  return Function.prototype.call.apply(Array.prototype.push, arguments);
}

褪去代入的参数,函数可还原为:

function(){
  return Function.prototype.call.apply(self, arguments);
}

综上,最终反柯里化的第三种实现将和第二种实现完全一致,推理完毕,码字不易,喜欢的请点个赞谢谢~

为了加深对bind 和 柯里化的理解,我还专门撰写了博客深入分析它们。

请参看 函数式编程之柯里化与反柯里化Function.prototype.bind方法指南

喜欢的同学还可以关注我的专栏路易斯前端深度课

这篇关于javascript - 求助关于call和apply的问题,反柯里化的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆