javascript - 求助關(guān)于call和apply的問題,反柯里化
問題描述
下面是uncurring的兩種實(shí)現(xiàn)
實(shí)現(xiàn)1
Function.prototype.uncurrying = function(){ var self = this; return function(){// 獲取傳入的上下文對(duì)象var context = Array.prototype.shift.call(arguments);// 這里的this是調(diào)用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]
實(shí)現(xiàn)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]
兩種結(jié)果是一樣的,但是第二種實(shí)現(xiàn)的方式我有點(diǎn)迷糊,主要是這里
第一種方式顯示的用self,在這里也就是push方法執(zhí)行了一下, self.apply(context, arguments);但是如下第二種實(shí)現(xiàn)方式,卻沒有發(fā)現(xiàn)self執(zhí)行的痕跡,按我的理解這里就是用apply修改call的上下文為self,這里也就是push,但這樣有執(zhí)行push方法嗎?難道call內(nèi)部的實(shí)現(xiàn)幫忙執(zhí)行了self?求解 Function.prototype.call.apply(self, arguments);
瞬間被你點(diǎn)通,謝謝 !
louiszhaiFunction.prototype.call.apply(self, arguments);先用apply修改了call的上下文為self,后續(xù)調(diào)用uncurrying,相當(dāng)于在self上調(diào)用call方法,也就執(zhí)行了self
問題解答
回答1:Function.prototype.call.apply(self, arguments);這個(gè)看起來有些繞,其實(shí)很好理解。實(shí)際上,由你的第二種實(shí)現(xiàn)還可以推出反柯里化的第三種實(shí)現(xiàn):
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}
接下來我會(huì)先分析下你的第二種實(shí)現(xiàn),再分析第三種實(shí)現(xiàn)。你的實(shí)現(xiàn)是這樣的:
Function.prototype.uncurrying = function(){ var self = this; return function(){return Function.prototype.call.apply(self, arguments); };};var push = Array.prototype.push.uncurrying();
誰調(diào)用uncurrying,誰就等于this或self. 這意味著self就是數(shù)組的push方法.替換掉self,最終外部的push等同如下函數(shù):
function(){ return Function.prototype.call.apply(Array.prototype.push, arguments);};
函數(shù)放在這里,我們先來理解apply函數(shù),apply有分解數(shù)組為一個(gè)個(gè)參數(shù)的作用。
推導(dǎo)公式:a.apply(b, arguments) 意味著把b當(dāng)做this上下文,相當(dāng)于是在b上調(diào)用a方法,并且傳入所有的參數(shù),如果b中本身就含有a方法,那么就相當(dāng)于 b.a(arg1, arg2,…)
公式1:a.apply(b, arguments) === b.a(arg1, arg2,…)
由于call 和 apply 除參數(shù)處理不一致之外,其他作用一致,那么公式可以進(jìn)一步演化得到:
公式2:a.call(b, arg) === b.a(arg)
將公式1這些代入上面的函數(shù),有:
a = Function.prototype.call 即a等于call方法。
我們接著代入公式,有:
b = Array.prototype.push 即b等于數(shù)組的push方法
那么 Function.prototype.call.apply(Array.prototype.push, arguments)就相對(duì)于:
Array.prototype.push.call(arg1, arg2,…),那么:
push([], 1) 就相當(dāng)于 Array.prototype.push.call([], 1),再代入公式2,相當(dāng)于:
[].push(1)
答案已經(jīng)呼之欲出了,就是往數(shù)組中末尾添加數(shù)字1。
接下來我來分析反柯里化的第三種實(shí)現(xiàn):
對(duì)于this.call.bind(this);部分,this相當(dāng)于Array.prototype.push,那么整體等同于如下:
Array.prototype.push.call.bind(Array.prototype.push)
這里的難點(diǎn)在于bind方法,bind的實(shí)現(xiàn)比較簡(jiǎn)單,如下:
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); }}
想要理解必須化繁為簡(jiǎn),理解得越簡(jiǎn)單,也就理解得越透徹。進(jìn)一步簡(jiǎn)化bind的原理,等同于誰調(diào)用bind,就返回一個(gè)新的function。
我們假設(shè)函數(shù)fn調(diào)用bind方法如fn.bind([1, 2]),經(jīng)過簡(jiǎn)化,忽略bind綁定參數(shù)的部分,最終返回如下:
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);}
這個(gè)看起來和反柯里化的第二種實(shí)現(xiàn)有些不大相同,不要急,雖然表面上看起來不一致,但骨子里還是一致的。請(qǐng)耐心往下看:
不同的地方在于前半部分 Array.prototype.push.call,這里它是一個(gè)整體,實(shí)際上想代表的就是call方法。而我們都知道,所有函數(shù)的call方法,最終都是Function.prototype 的 call方法。那么,就有如下恒等式成立:
Array.prototype.push.call === Function.prototype.call //true
那么以上函數(shù)將等同于:
function(){ return Function.prototype.call.apply(Array.prototype.push, arguments);}
褪去代入的參數(shù),函數(shù)可還原為:
function(){ return Function.prototype.call.apply(self, arguments);}
綜上,最終反柯里化的第三種實(shí)現(xiàn)將和第二種實(shí)現(xiàn)完全一致,推理完畢,碼字不易,喜歡的請(qǐng)點(diǎn)個(gè)贊謝謝~
為了加深對(duì)bind 和 柯里化的理解,我還專門撰寫了博客深入分析它們。
請(qǐng)參看 函數(shù)式編程之柯里化與反柯里化 、Function.prototype.bind方法指南 。
喜歡的同學(xué)還可以關(guān)注我的專欄路易斯前端深度課
回答2:基礎(chǔ)call和apply的區(qū)別和作用不再贅述
call和apply源碼實(shí)現(xiàn)他們很接近,這里只介紹call,舉個(gè)例子:a.call(b, c)
取出第一個(gè)參數(shù)x = b || {}
x.fn = a
拼接除第一個(gè)參數(shù)以外的參數(shù),用逗號(hào)分隔,結(jié)果為d
創(chuàng)建獨(dú)立執(zhí)行環(huán)境的函數(shù)e = new Function(),函數(shù)內(nèi)部執(zhí)行x.fn(d)
執(zhí)行創(chuàng)建的e
方案二的理解這里就不考慮call和apply擴(kuò)大對(duì)象方法的問題,因?yàn)閺脑创a中方法都會(huì)動(dòng)態(tài)創(chuàng)建,以下就不再贅述這個(gè)問題。
Function.prototype.call.apply(self, arguments);var push = Array.prototype.push.uncurrying ();
self指向Array.prototype.push
(Function.prototype.call).apply(Array.prototype.push, arguments);
利用剛講解的源碼,把2變形,得出:Array.prototype.push.(Function.prototype.call)(arguments),這里還需要轉(zhuǎn)化,call接受的不是數(shù)組,見4。
arguments是類數(shù)組對(duì)象[arr, 1],把3變形,得出:Array.prototype.push.(Function.prototype.call)(arr, 1)
call的源碼已經(jīng)解釋過,于是變化4,得出arr.(Array.prototype.push)(1)
寫得好看一點(diǎn),arr.push(1)
