手写call apply bind

241 阅读2分钟

call,apply,bind函数内部实现是怎样的?

this的特性对于我们前端来说是很重要的,本文承接个人总结的JS基础中的this部分

tips:代码是JS的模拟实现,只是为了掌握this,真实的call apply bind并不是这么简单的用JS实现的而且相信他们也会处理更多的边界问题. 其中bind实现参考了这篇文章

      Function.prototype.myCall = function (thisArg, ...args) {
        const fn = Symbol('fn')
        thisArg = thisArg ?? window
        thisArg= Object(thisArg)
        thisArg[fn]=this
        const result = thisArg[fn](...args)
        delete thisArg[fn]
        return result
      }
      function foo(num1, num2){
        return num1 + num2
      }
      console.log(foo.myCall(null, 1, 2))
      
      Function.prototype.myApply = function (thisArg, args) {
        const fn = Symbol('fn')
        thisArg = thisArg ?? window
        thisArg = Object(thisArg)
        thisArg[fn] = this
        const result = thisArg[fn](...args)
        delete thisArg[fn]
        return result
      }
      function foo(num1, num2, num3){
        return num1 + num2 + num3
      }
      console.log(foo.myApply('abc', [4, 5, 1]))
      
   // 如果你很了解apply 那么我们可以利用他们来实现一个完整的myBind
  Function.prototype.myBind = function (thisArg, ...arrayArg) {
    const fn = this
    if (typeof fn !== 'function') {
        throw new Error('Function.prototype.bind - what is trying to be bound is not callable')
    }
    thisArg = thisArg ?? window
    thisArg = Object(thisArg)
    const fBound = function (...args) {
      arrayArg = arrayArg.concat(args)
      if (new.target !== undefined) {
        return new fn(...arrayArg)
      } else {
        return fn.apply(thisArg, arrayArg)
      }
    }
    return fBound;
  }

  // 不利用apply关键字实现myBind
  Function.prototype.myBind = function (thisArg, ...arrayArg) {
    const fn = this
    const fnName = Symbol('fnName')
    if (typeof fn !== 'function') {
        throw new Error('Function.prototype.bind - what is trying to be bound is not callable')
    }
    thisArg = thisArg ?? window 
    thisArg = Object(thisArg)
    // 定义一个空函数 来中转
    const fNOP = function () {}
    const fBound = function (...args) {
        arrayArg = [...arrayArg, ...args]
        // 如果new.target 不等于undefined 说明函数被new调用了
        if(new.target !== undefined){
            // 所以当前this会执行new的实例,我们要把fn函数隐式绑定到new实例上
            this[fnName] = fn 
            const result = this[fnName](...arrayArg)
            delete this[fnName]
            return result
        } else {
            // 如果没被new调用,那么this就隐式绑定到传进来的thisArg
            thisArg[fnName] = fn
            const result = thisArg[fnName](...arrayArg)
            delete thisArg[fnName]
            return result
        }
    }
    // 将调用bind绑定函数的原型赋值给fNOP中转函数的原型
    fNOP.prototype = fn.prototype
    // 将fNOP的实例赋值给将要返回fBound函数的原型实现原型式继承,并且利用中转函数使得fn.prototype和fBound.prototype不会共享同一个对象
    fBound.prototype = new fNOP()
    return fBound
  }
      var value = 2
      
      var foo = {
        value: 1,
      }
      
      function bar(name, age) {
        this.habit = "shopping"
        console.log(this.value)
        console.log(name)
        console.log(age)
      }
      
      bar.prototype.friend = "kevin"
      
      var bindFoo = bar.myBind(foo, "daisy")
      // console.log('bindFoo',bindFoo('19'));
      // console.log('bindFoo',bindFoo('19'));
      var obj = new bindFoo("18")
      // undefined
      // daisy
      // 18
      console.log(obj.habit)
      console.log(obj.friend)
      // shopping
      // kevin