javascript - 求救關於call和apply的問題,反柯里化
淡淡烟草味
淡淡烟草味 2017-05-16 13:41:58
0
2
488

下面是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
淡淡烟草味
淡淡烟草味

全部回覆(2)
我想大声告诉你

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方法.uncurrying,谁就等于thisself. 这意味着self就是数组的push方法.
替换掉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方法指南 。

喜歡的同學還可以關注我的專欄路易斯前端深度課

淡淡烟草味

基礎
call和apply的區別和作用不再贅述

call和apply源碼實現
他們很接近,這裡只介紹call,舉個例子:a.call(b, c)

  1. 取出第一個參數x = b || {}

  2. x.fn = a

  3. 拼接除第一個參數以外的參數,以逗號分隔,結果為d

  4. 建立獨立執行環境的函數e = new Function(),函數內部執行x.fn(d)

  5. 執行創建的e

方案二的理解
這裡就不考慮call和apply擴大物件方法的問題,因為從原始碼中方法都會動態創建,以下就不再贅述這個問題。

    Function.prototype.call.apply(self, arguments);
    
    var push = Array.prototype.push.uncurrying ();
  1. self指向Array.prototype.push

  2. (Function.prototype.call).apply(Array.prototype.push, arguments);

  3. 利用剛講解的源碼,把2變形,得出:Array.prototype.push.(Function.prototype.call)(arguments),這裡還需要轉化,call接受的不是數組,見4。

  4. arguments是類別數組物件[arr, 1],把3變形,得出:Array.prototype.push.(Function.prototype.call)(arr, 1)

  5. call的源碼已經解釋過,於是變化4,得出arr.(Array.prototype.push)(1)

  6. 寫得好一點,arr.push(1)

熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板