当前位置: 首页 > 科技观察

js实现了Bind的五层,你在哪一层?

时间:2023-03-17 15:03:38 科技观察

本文转载自微信公众号《秋风手记》,作者:蓝秋风。转载本文请联系秋风笔记公众号。最近在帮闺蜜复习JS相关的基础知识。当她遇到不会的问题时,她会来问我。这不是很简单吗?三遍,五遍,两遍,分分钟解决。functionbind(fn,obj,...arr){returnfn.apply(obj,arr)}于是把这段代码发过去,立马被闺蜜虐了。这时,我的马老师坐不住了。不服气,就去复习bind,发现自己太久没写基础代码了,复习还是花了一点时间。这次不得不写一个深绑定,深德玛老师的真传被他分为五层速记。第一层——原型绑定的方法这一层很简单,得益于JS原型链的特性。由于函数xxx的原型链指向的是Function.prototype,所以当我们调用xxx.bind时,我们调用的是Function.prototype上的方法。Function.prototype._bind=function(){}这样,我们就可以直接在一个构造函数上调用我们的bind方法了~比如这样。funcitonmyfun(){}myfun._bind();如果想详细了解这方面,可以看这张图和这篇文章(https://github.com/mqyqingfeng/blog/issues/2)第二层——改变这个这可以说是核心特征bind的,也就是改变this的指针,返回一个函数。要改变这个,我们可以通过已知的apply和call来实现,这里我们暂时用apply来模拟。首先通过self保存当前的this,也就是传入的函数。因为我们知道this有隐式绑定的规则(来自?2.2.2),functionfoo(){console.log(this.a)}varobj={a:2,foo};obj.foo();//2通过以上特点,我们就可以编写我们的_bind函数了。Function.prototype._bind=function(thisObj){constself=this;returnfunction(){self.apply(thisObj);}}varobj={a:1}functionmyname(){console.log(this.a)}我的名字。_bind(obj)();//1很多朋友可能就到这里了,因为一般面试,尤其是一些校招面试,可能只需要知道前两个就可以了。但这仍然不足以让采访中的每个人都感到惊喜。接下来,我们将继续探索和研究。第三层——对柯里化函数的支持柯里化是一个常见的话题,所以我们在这里回顾一下。functionfn(x){returnfunction(y){returnx+y;}}varfn1=fn(1);fn1(2)//3不难发现柯里化用到了闭包。当我们执行fn1时,函数内部使用了外层函数的x,这样就形成了一个闭包。而我们的bind函数也类似,我们获取当前外部函数的参数,并移除绑定对象,保存为变量args,最后返回,再次获取当前函数的参数,最后进行一次mergewith最终参数。Function.prototype._bind=function(thisObj){constself=this;constargs=[...arguments].slice(1)returnfunction(){constfinalArgs=[...args,...arguments]self.apply(thisObj,finalArgs);}}通过上面的代码,让我们绑定方法,越来越健壮。varobj={i:1}functionmyFun(a,b,c){console.log(this.i+a+b+c);}varmyFun1=myFun._bind(obj,1,2);myFun1(3);//7一般来说,当你达到这个水平的时候,可以说已经很不错了,但是如果你坚持下去,就会成为一张完美的答卷。第四层——考虑new的调用。要知道我们的方法,通过bind绑定后,仍然可以通过new实例化,而且new的优先级会比bind高(来自?2.3优先级)。此时,我们验证并比较原始绑定与我们第四层的_bind。//Nativevarobj={i:1}functionmyFun(a,b,c){//这里使用了新方法,this指向当前函数myFunconsole.log(this.i+a+b+c);}varmyFun1=myFun.bind(obj,1,2);newmyFun1(3);//NAN//第4层bindvarobj={i:1}functionmyFun(a,b,c){console.log(this.i+a+b+c);}varmyFun1=myFun._bind(obj,1,2);newmyFun1(3);//7注意这里使用了bind方法,所以我们需要在bind里面处理new。new.target属性只是用来检测是否通过new运算符调用了构造函数。接下来,我们需要自己实现一个new,根据MDN,new关键字会执行以下操作:1.创建一个空的简单JavaScript对象(即{});2.将对象(设置对象的构造函数)链接到另一个对象;3、使用步骤1中新建的对象作为this的context;4.如果函数不返回对象,则返回this。Function.prototype._bind=function(thisObj){constself=this;constargs=[...arguments].slice(1);returnfunction(){constfinalArgs=[...args,...arguments];//new.target用于检测是否被new调用if(new.target!==undefined){//this指向构造函数本身varresult=self.apply(this,finalArgs);//判断函数是否返回一个objectif(resultinstanceofObject){returnreuslt;}//如果没有返回对象,则返回thisreturnthis;}else{//如果不是新的,则返回原逻辑。无辜的,但最后有一个小细节。第五层-上面保留函数原型的方法在大多数情况下都很好,但是当我们的构造函数具有原型属性时,就会出错。所以我们要补原型,调用对象必须是函数。Function.prototype._bind=function(thisObj){//判断是否是函数调用if(typeoftarget!=='function'||Object.prototype.toString.call(target)!=='[objectFunction]'){thrownewTypeError(this+'mustbeafunction');}constself=this;constargs=[...arguments].slice(1);varbound=function(){varfinalArgs=[...args,...arguments];//新的。target用于检测是否被new调用if(new.target!==undefined){//表示被new调用varresult=self.apply(this,finalArgs);if(resultinstanceofObject){returnresult;}返回此;}else{returnsself.apply(thisArg,finalArgs);}};if(self.prototype){//为什么要用Object.create?因为我们要防止由于修改bound.prototype而修改self.prototype。不要写bound.prototype=self.prototype;这可能会导致原始函数的原型被修改。bound.prototype=Object.create(self.prototype);bound.prototype.constructor=self;}returnbound;};以上就是一个比较完整的bind实现。如果您想了解更多有关实践的详细信息,可以查看。(同样由MDN推荐)https://github.com/Raynos/function-bind