1.我们先看看MDN上对bind的描述和作用
首先,我们可以知道bind是函数对象Function的原型上面的方法,所以,我们所有自定义的函数对象都继承了该方法,也就是都可以使用bind方法。
2.分析bind的作用
定义描述的比较官方,下面举个例子来描述bind的作用,好让我们知道,我们到底要手写一个怎么样的需求。
let obj={
name:'lisi',
getName(){
return this.name
}
}
let a=obj.getName
console.log(a())//undefined
先分析为什么会输出这样的结果,首先我们要先搞清楚函数中this的指向问题,明显上述代码在外层声明了一个a变量指向了getName函数,现在相当于let a=function(){ return this.name }然后再调用a函数,该函数作用域里没有name属性,就会往上一层的作用域里面找name属性,显然上一层的window对象里面也是没有name属性。所以是undefined。为了验证我们的说法,可以给window加个name属性,试试
// let name='wangwu'
window.name='wangwu'
let obj={
name:'lisi',
getName(){
return this.name
}
}
let a=obj.getName
console.log(a())// wangwu
输出了wangwu,证明我们的说法是正确的。按照bind的作用描述,尝试调用bind,obj.getName.bind(obj),返回了一个新函数,这个函数中的this指向了第一个参数就是obj,现在相当于let a=obj.getName.bind(obj)=function(){return obj.name}, 因为this指向了obj我们可以认为this就是obj,下面代码
// let name='wangwu'
// window.name='wangwu'
let obj={
name:'lisi',
getName(){
return this.name
}
}
let a=obj.getName.bind(obj)
console.log(a())// lisi
输出了lisi,看到这里我们应该知道bind的使用和作用了。
3.手写bind
其实我们手写某些方法,首先知道这些方法怎么使用的,然后用自己的代码去复现它这个使用而已。为了区分我们定义这个方法名叫_bind吧,然后我们使用_bind也能想上面一样输出lisi。
①像bind一样,把_bind加到函数对象的原型上面
Function.prototype._bind=function(){
}
let obj={
name:'lisi',
getName(){
return this.name
}
}
let a=obj.getName._bind(obj)
console.log(a())// lisi
②返回一个新函数,获取第一个参数。
Function.prototype._bind=function(){
let firstArg=[].shift.call(arguments)//获取第一个参数
return function(){
//返回新函数
}
}
let obj={
name:'lisi',
getName(){
return this.name
}
}
let a=obj.getName._bind(obj)
console.log(a())// lisi
③新函数里面的this指向第一个参数
Function.prototype._bind=function(){
let firstArg=[].shift.call(arguments)
return function(){
return this.apply(firstArg)//新函数里面的this指向第一个参数
}
}
let obj={
name:'lisi',
getName(){
return this.name
}
}
let a=obj.getName._bind(obj)
console.log(a())// lisi
到这里我们已经可以输出lisi了,已经实现了我们需要的功能。
4.完善功能
最主要的功能已实现,bind方法是可以在指定对象的时候传入参数的,比如我们想给getName传入一个参数,
Function.prototype._bind=function(){
let firstArg=[].shift.call(arguments)//第一个参数
let _this=this//调用_bind的函数
return function(){
return _this.apply(firstArg)//调用调用_bind的函数,并将调用_bind的函数的this指向调用_bind时传入的第一个参数
}
}
let obj={
name:'lisi',
getName(age){
//要调用的函数有参数
return this.name+':'+age
}
}
let a=obj.getName._bind(obj,12)//希望绑定的时候传入参数给要调用的函数,这里是getName
console.log(a())// lisi:12//希望得到的结果
其实我只要获取调用_bind时的参数,并给到调用_bind的函数就可以了。
Function.prototype._bind=function(){
let firstArg=[].shift.call(arguments)//第一个参数
let args=[].slice.call(arguments)//剩余参数
let _this=this//调用_bind的函数
return function(){
return _this.apply(firstArg,args)//调用调用_bind的函数,并将调用_bind的函数的this指向调用_bind时传入的第一个参数
}
}
let obj={
name:'lisi',
getName(age){
//要调用的函数有参数
return this.name+':'+age
}
}
let a=obj.getName._bind(obj,12)//希望绑定的时候传入参数给要调用的函数,这里是getName
console.log(a())// lisi:12//希望得到的结果
最终输出了lisi:12,符合我们的期望。现在又有了一个要求,我们希望既可以在调用_bind的时候输入参数,也可以在调用a的时候,也就是在调用新函数的时候传入参数,如下代码:
Function.prototype._bind=function(){
let firstArg=[].shift.call(arguments)//第一个参数
let args=[].slice.call(arguments)//剩余参数
let _this=this//调用_bind的函数
return function(){
return _this.apply(firstArg,args)//调用调用_bind的函数,并将调用_bind的函数的this指向调用_bind时传入的第一个参数
}
}
let obj={
name:'lisi',
getName(age,sex){
//要调用的函数有参数
return this.name+':'+age+':'+sex
}
}
let a=obj.getName._bind(obj,12)//希望绑定的时候传入参数给要调用的函数,这里是getName
console.log(a('male'))// lisi:12:male//希望得到的结果
其实我们把调用新函数也就是a的时候传入的参数和之前的调用的_bind的时候传入的参数一起放到调用_bind的函数里面就可以了,也就是放到_this.apply()里面,代码如下
Function.prototype._bind=function(){
let firstArg=[].shift.call(arguments)//第一个参数
let args=[].slice.call(arguments)//剩余参数
let _this=this//调用_bind的函数
return function(){
return _this.apply(firstArg,args.concat([].slice.call(arguments)))//调用调用_bind的函数,并将调用_bind的函数的this指向调用_bind时传入的第一个参数
}
}
let obj={
name:'lisi',
getName(age,sex){
//要调用的函数有参数
return this.name+':'+age+':'+sex
}
}
let a=obj.getName._bind(obj,12)//希望绑定的时候传入参数给要调用的函数,这里是getName
console.log(a('male'))// lisi:12:male//希望得到的结果
最终如期得到我们的结果,现在已经手写实现bind了。bind方法的使用并不复杂,已有了bind方法我们为何要去费劲重新实现一边呢,其实在实现小小的bind的过程我们需求弄懂的东西很多,如函数作用域,this指向,如何接受参数,以及我们如何分解需求来用代码实现。这些东西搞懂了受益匪浅,也有助于我们今后阅读源码,提高我们解决问题的能力。