手写call、apply、bind

122 阅读2分钟

原理

三个方法都是为了改变 this 指向。主要区别:call和apply都是立即执行,call的传参是一个一个传,apply是直接传的数组;bind不会立即执行,而是返回一个新函数,传参也是一个一个,另外它可以在调用bind时和调用返回的新函数时都传入参数。

  • 先找到调用这三个方法的对象:我们知道一个方法的调用对象其实就是该方法里面的 this,我们把它命名为 fn
  • 要改变 this 指向其实跟上面的原理一样,用需要改变 this 指向的目的对象(thisArg)调用 fn就可以了;但是 thisArg 里面没有 fn 方法,可以给 thisArg 里面加个属性,并把 fn 赋值给他,调用完之后删除即可。
  • 这里定义的 fn 属性会存在传入的 thisArg 本身可能就有这个属性,这就会导致原属性值被覆盖。解决方案可以使用 Symbol 来做属性名,这样就不会存在同名了。

源码

    Function.prototype.myCall = function(thisArg, ...params){
      // 当前的this就是调用这个方法的对象  
      var fn = this;
      // this指向目标为null或undefined 直接返回window,否则返回Object包装后的对象(主要为了把简单类型的值转换成包装对象)
      thisArg = thisArg == null ? window : Object(thisArg)
      // 在thisArg中加个属性为fn,且值为fn,并调用就相当于隐式绑定this指向为thisArg了
      thisArg.fn = fn
      var result = thisArg.fn(...params)
      // 删除添加的fn属性
      delete thisArg.fn
      return result
    }
    // 手写apply
    Function.prototype.myApply = function(thisArg, params){
      var fn = this
      thisArg = thisArg == null ? window : Object(thisArg)
      thisArg.fn = fn
      var reslut = thisArg.fn(...params)
      delete thisArg.fn
      return result
    }
    // 手写bind
    Function.prototype.myBind = function(thisArg, ...params1){  // 接收调用bind时传入的参数
      var fn = this
      thisArg = thisArg == null ? window : Object(thisArg)
      return function(...params2){ // 接收调用bind返回的函数时传入的参数
        thisArg.fn  = fn
        var params = [...params1, ...params2];
        var result = thisArg.fn(...params)
        delete thisArg.fn
        return result
      }
    }