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

分析Bind的原理,手工实现Bind

时间:2023-03-13 22:01:46 科技观察

bind()bind()方法创建一个新的函数。调用bind()时,这个新函数的this被指定为bind()的第一个参数,其余的参数将作为调用时新函数的参数。—MDNbind方法与call/apply最大的区别在于前者返回一个绑定上下文的函数,而后两者直接执行该函数。下面举个例子来说明:letvalue=2;letfoo={value:1};functionbar(name,age){return{value:this.value,name:name,age:age}};bar.call(foo,"Jack",20);//直接执行函数//{value:1,name:"Jack",age:20}letbindFoo1=bar.bind(foo,"Jack",20);//返回一个函数bindFoo1();//{value:1,name:"Jack",age:20}letbindFoo2=bar.bind(foo,"Jack");//返回一个函数bindFoo2(20);//{value:1,name:"Jack",age:20}通过上面的代码我们可以看出bind有以下几个特点:1.指定this2,传入参数3.返回一个函数4.库里模拟实现:Function.prototype.bind=function(context){//调用bind不是函数,需要抛出异常if(typeofthis!=="function"){thrownewError("Function.prototype.bind-whatistryingtobeboundisnotcallable");}//this点给调用者varself=this;//实现第二点,因为第一个参数是指定的this,所以只截取第一个之后的参数varargs=Array.prototype.slice.call(arguments,1);//实现第三点,返回一个Functionreturnfunction(){//实现第四点,此时的arguments是指bind返回的函数传入的参数//即返回函数的参数varbindArgs=Array.prototype.slice.call(arguments);//实现第一个Pointreturnsself.apply(context,args.concat(bindArgs));}}但是还有一个问题,bind有如下特点:一个绑定函数也可以使用new操作符来创建对象:这种行为就像使用原始函数作为构造函数一样,this提供的值被忽略,调用时的参数提供给mock函数。例如:letvalue=2;letfoo={value:1};functionbar(name,age){this.habit='shopping';console.log(this.value);console.log(name);console.log(age);}bar.prototype.friend='kevin';letbindFoo=bar.bind(foo,'Jack');letobj=newbindFoo(20);//undefined//Jack//20obj.habit;//shoppingobj.friend;//kevin在上面的例子中,this.value的输出是undefined,不是全局值,也不是foo对象中的值。这意味着bind的this对象是无效的,在new的实现中产生了一个新的对象。这时this指向的是obj。可以通过修改返回函数的原型来实现,代码如下:Function.prototype.bind=function(context){//调用bind不是函数,需要抛出异常if(typeofthis!=="function"){thrownewError("Function.prototype.bind-whatistryingtobeboundisnotcallable");}//this指向调用者varself=this;//实现第二点,因为第一个参数是指定的this,所以只截取第一个之后的参数varargs=Array.prototype.slice.call(arguments,1);//创建一个空对象varfNOP=function(){};//实现第三点,返回一个函数varfBound=function(){//实现第四点,获取bind返回函数的参数varbindArgs=Array.prototype.slice.call(arguments);//然后和传入的参数组合成一个参数数组,作为self.apply()的第二个参数returnself.apply(thisinstanceoffNOP?this:context,args.concat(bindArgs));//注1}//注2//空对象的原型指向绑定函数的原型fNOP.prototype=this.prototype;//空对象的实例赋值给fBound.prototypefBound.prototype=newfNOP();returnfBound;}注1:当用作构造函数,this指向实例。这个时候这个instanceoffBound的结果为true,这使得实例可以从绑定的函数中获取值,也就是上面例子中的实例会有一个habit属性。作为普通函数使用时,this指向window,此时结果为false,绑定函数的this指向context注2:将返回函数的原型修改为绑定函数的原型,以及实例可以继承绑定函数的原型Value,也就是上面例子中的obj可以得到bar原型上的friend至于为什么用一个空对象fNOP作为中介,fBound。.prototype有一个缺点。修改fBound.prototype时,会直接修改this.prototype;其实也可以直接使用ES5的Object.create()方法生成新的对象,但是bind和Object.create()都是ES5方法,部分IE浏览器(IE<9)不支持注意:bind()功能是在ES5才加入的,所以不是所有的浏览器都支持,IE8及以下版本不支持。如果需要兼容性,可以使用Polyfill实现,详情请到深入分析bind原理、使用场景和模拟实现查看补充:Currying在计算机科学中,currying就是将一个接受多个参数的函数转换成一个接受单个参数(原始函数的第一个参数)并返回一个接受其余参数并返回结果的新函数。该技术由ChristopherStrachey以逻辑学家HaskellCurry的名字命名,尽管它是由MosesSchnfinkel和GottlobFrege发明的。varadd=function(x){returnfunction(y){returnx+y;};};varincrement=add(1);varaddTen=add(10);increment(2);//3addTen(2);//12add(1)(2);//3这里定义了一个add函数,它接受一个参数,返回一个新的函数。调用add之后,返回的函数通过闭包记住了add的第一个参数。所以bind本身也是闭包的一种使用场景。柯里化是一种使f(a,b,c)可调用为f(a)(b)(c)的转换。JavaScript实现通常会保留函数正常调用和当参数个数不足时返回部分函数这两个特性。