手撕bind方法

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指向,如何接受参数,以及我们如何分解需求来用代码实现。这些东西搞懂了受益匪浅,也有助于我们今后阅读源码,提高我们解决问题的能力。

猜你喜欢

转载自blog.csdn.net/weixin_44494811/article/details/113776944