背景
在下前端小白,近日在刷各种算法/编程题,今天碰到一编程题,考点是apply
,虽说简单,但在解题时发现了一个挺有意思的东东,特来分享一下。欢迎各位大佬指点~
正文
话不多说,直接上题目:二次封装函数。
已知函数 fn 执行需要 3 个参数。请实现函数 partial,调用之后满足如下条件:1、返回一个函数 result,该函数接受一个参数2、执行 result(str3) ,返回的结果与 fn(str1, str2, str3) 一致
哈哈,这题简单!稍微学过js的朋友就能写出来:
function partial(fn,str1,str2) { var result = function(str3) { return fn(str1,str2,str3); } return result;}
这里用个call
可能会显得有点逼格(笑),当然apply
,bind
也能达到一样的效果。
function partial(fn,str1,str2) { var result = function(str3) { return fn.call(null,str1,str2,str3); } return result;}
不多停留,我们来看下一题,同样是二次封装函数:
实现函数 partialUsingArguments,调用之后满足如下条件:1、返回一个函数 result2、调用 result 之后,返回的结果与调用函数 fn 的结果一致3、fn 的调用参数为 partialUsingArguments 的第一个参数之后的全部参数以及 result 的调用参数
emmmmm,传入的参数不固定? 有了! 用arguments
~
function partialUsingArguments(fn) { var _arguments = Array.prototype.slice.call(arguments,1) var result = function() { var newArguments = _arguments.concat(Array.prototype.slice.call(arguments,0)); return fn.apply(null,newArguments) } return result;}
唔,好像也没什么难的,_arguments
就是partialUsingArguments第一个参数后的所有参数组成的数组,将它和result的所有参数合并起来,利用apply
传入fn,ok~解决了!
诶诶?有意思的东西呢?这特么一点意思都没啊。
说出来你们可能不信,其实我看到这题的时候,虽然我想到了用apply
,但我并没有想到fn.apply(null,newArguments)
。那我想到的是什么呢? Function.prototype.call.apply
什么鬼?我也不知道是在哪看过这东西(也许没看过),又好像是
Function.prototype.call.call
?
还是
Function.prototype.apply.apply
?
还是
Function.prototype.apply.call
?
好像都差不多,妈个鸡,试试就知道了!
return Function.prototype.call.apply(fn,newArguments)
Function.prototype.call.apply(fn,newArguments)
同等于fn.call(a,b,c,...z)
// newArguments = [a,b,c,...z],这里的...z不是ES6中的...rest
哦,只是表示省略了中间的参数。 知道了原理(并不知道),那就好办了,我给newArguments数组的头部补一个元素上去不就好了~
newArguments.unshift(0);Function.prototype.call.apply(fn,newArguments)
浏览器一跑——没毛病老铁!
完了吗?
并没有!
既然.call.apply
可以用,那其他3个按理来说也能用,况且这里多了一步对数组的操作,能不能更优雅一点呢?(另外3个怎么用,观众老爷们心里有答案吗?)
分析及结论如下
原理其实很简单,Function.prototype.apply(call)
其实就是一个函数,将视为一个整体,记作A。原式就可以转换成:A.apply(参数)
或A.call(参数)
,然后进一步转换,参数1.A(参数2)
,什么参数、参数1、参数2的?
其实很简单,apply就传数组,call就传一个序列。
apply
和call
的区别大家肯定知道,我就不多说了。第一个参数肯定是fn,这个没跑了。关键就在第二个参数上。
第二个参数,首先它形式上要看后一个apply/call
,如果是apply,我们就传数组进去,如果是call,就传一个序列。然后它的内容就要看A了,也就是前一个apply/call
,同理,如果是apply,就传数组参数,如果是call,就传序列参数。
所以我们可以得出以下结果:
Function.prototype.apply.apply(fn,[null,newArguments])Function.prototype.call.call(fn,null,...newArguments])Function.prototype.apply.call(fn,null,newArguments])//上面3式可以实际上等于fn.apply(null,newArguments)fn.call(null,...newArguments) //...为扩展运算符,...[arr] = arr[0],arr[1],...arr[n]fn.apply(null,newArguments)
绕了一大圈,又回来了~ :)
而上面的Function.prototype.call.apply
也可以改写成: Function.prototype.call.apply(fn,[null,...newArguments])
从而减少了对数组newArguments的操作。
写在最后
第一次写文章(水贴),十分紧张,删了改,改完了删,总觉得写的不好、十分啰嗦。
可能会有人觉得毫无意义,但我觉得这个倒是可以作为一道面试题。请在填写空白内容使等式成立:fn.apply(null,args) = Function.prototype.apply.apply(_____)
如果真的有人遇到,请回来点赞^ ^
也希望此文能多少帮助到前端新人,大家一起学习,进步!哪里要是写不好可以直说!帮助我进步。谢谢!不废话了,完。