当前位置: 首页 > Web前端 > HTML

从一道面试题到“我可能看过假源码”

时间:2023-04-02 15:16:36 HTML

今天想说一道前端面试题。我做面试官的时候,经常喜欢用它来考察面试官的基础是否扎实,还有逻辑思维能力和临场表现,题目是:《模拟nativebind功能的实现在ES5”。也许这个问题已经不再新鲜,有些读者会有想法来回答。社区也有很多对nativebind的研究,比如用它来实现函数“currying”或者“uncurring”。但是,我敢肯定有许多您没有注意到的细节,并且社区通常在这个主题上遗漏了很多细节。本文面向有扎实JS基础的读者。将从最基本的理解入手,分析ES5-shim实现bind的源码。相信不同层次的读者都能有所收获。也欢迎你和我一起讨论。bind函数到底是什么?在开始我们的探索之前,有必要先弄清楚bind到底实现了什么:1)简单来说,bind就是用来绑定this指向的。(如果不了解this在JS中的指向问题和执行环境上下文的奥秘,本文暂时不适合阅读)。2)Bind使用语法:fun.bind(thisArg[,arg1[,arg2[,...]]])bind方法将创建一个新函数。当这个新函数被调用时,bind运行时会把第一个参数当作this,后面的参数序列会在实际参数传递之前作为它的参数传入。本文无意科普基础知识。如果还有不明白的,请参考MDN的内容。3)bind返回的绑定函数也可以使用new操作符创建对象:这种行为就像是把原函数当做构造函数一样。提供的this值将被忽略,调用时的参数将提供给模拟函数。初步实现理解了以上内容之后,我们来实现一个初步的bind函数Polyfill:Function.prototype.bind=function(context){varme=this;varargsArray=Array.prototype.slice.call(arguments);returnfunction(){returnme.apply(context,argsArray.slice(1))}}这是一般“表现不错”的面试官能给我的答案。如果面试官能写到这里,我就给他60分。先简单说明一下:基本原理是使用apply进行模拟。函数体中的this是需要绑定this的实例函数,或者说是原函数。最后,我们使用apply绑定参数(上下文)并返回。同时,使用第一个参数(context)以外的参数作为预置参数提供给原始函数,也是“curring”的基本依据。初级实现加分上面的实现(包括下面的实现)其实就是一个典型的“Monkeypatching(猴子补丁)”,即“为内置对象扩展方法”。所以,如果面试官能“嗅”出来并进行兼容处理,那就是锦上添花,我加10分。Function.prototype.bind=Function.prototype.bind||function(context){...}粒度化(curring)实现在上面的实现中,我们返回的参数列表包含:atgsArray.slice(1),其中的问题是存在预设参数函数丢失的现象。想象一下,在我们返回的绑定函数中,如果要实现预置参数传递(就像bind做的那样),我们将面临一个尴尬的局面。真正实现颗粒化的“完美方式”是:Function.prototype.bind=Function.prototype.bind||函数(上下文){varme=this;varargs=Array.prototype.slice.call(arguments,1);返回函数(){varinnerArgs=Array.prototype.slice.call(arguments);varfinalArgs=args.concat(innerArgs);返回me.apply(contenxt,finalArgs);}}如果面试官能给出这样的答案,我的内心独白会是“不错,看来你就是我要找的TA~”。但是,我们注意上面bind方法介绍中提到的第三条:如果bind返回的函数作为构造函数,并且以new关键字出现,我们的绑定this就需要“忽略”。构造函数场景的兼容通过上面的解释,就不难理解需要实现兼容的构造函数场景:Function.prototype.bind=Function.prototype.bind||函数(上下文){varme=this;varargs=Array.prototype.slice.call(arguments,1);varF=函数(){};F.prototype=this.prototype;varbound=function(){varinnerArgs=Array.prototype.slice.call(参数);varfinalArgs=args.contact(innerArgs);returnme.apply(thisinstanceofF?this:context||this,finalArgs);}bound.prototype=newfNOP();returnbound;}面试官要是能这么写,我几乎给满分,我会帮忙联系HR谈薪水。当然,它可以做得更严格。更严格地说,我们需要调用的bind方法必须是一个函数,所以我们可以在函数体中进行判断:if(typeofthis!=="function"){thrownewTypeError("Function.prototype.bind-whatistryingtobeboundisnotcallable");}我很乐意为所有这一切打满分。其实MDN上有一个自己实现的polyfill,就是这样实现的。另外,《JavaScript Web Application》一书中bind()的实现也是如此。故事似乎走到了尽头——一切还没有结束,高潮即将上演。如果你认为这就是结局,其实我告诉你,高潮就要上演了。之前一直认为上面的方法很完美,直到看了es5-shim的源码(已经适当删除):bind:functionbind(that){vartarget=this;if(!isCallable(target)){thrownewTypeError('Function.prototype.bind调用不兼容'+target);}varargs=array_slice.call(arguments,1);变量绑定;varbinder=function(){if(thisinstanceofbound){varresult=target.apply(this,array_concat.call(args,array_slice.call(arguments)));如果($对象(结果)===结果){返回结果;}返回这个;}else{returntarget.apply(that,array_concat.call(args,array_slice.call(arguments)));}};varboundLength=max(0,target.length-args.length);varboundArgs=[];对于(vari=0;i