JavaScript 手写call、apply、bind

227 阅读2分钟

1. 手写call

  • 思路

    • 接受两个参数,第一个参数作为函数调用是绑定的this对象,第二个参数是函数执行时需要传入的参数
    • 第一个参数为null或者undefined时,this指向全局对象window,值为原始值的指向该原始值的自动包装对象,如 StringNumberBoolean
    • 使用Symbol类型作为唯一值,避免函数名与上下文(context)的属性发生冲突(比如传入的对象有一个fn属性,此处会有冲突)
    • 将函数作为传入的上下文(context)属性执行,以对象的形式调用,即改变当前函数的this指向
    • 函数执行完成后删除该属性
    • 返回执行结果
    Function.prototype.myCall = function (context, ...args) {
      // 新建一个唯一的Symbol变量避免重复
      let fn = Symbol()
      // 判断传入第一个参数是否为null或者undefined
      context = context == null ? window : Object(context)
      // 此处的this指的是当前的函数,将其作为第一个参数的属性,然后以对象的形式调用,就达到了绑定this的效果
      context[fn] = this
      // 执行函数
      const res = context[fn](...args)
      // 删除该方法,防止对传入的对象造成污染
      delete context[fn]
    
      // 返回结果
      return res
    }
    

2. 手写apply

  • 思路与call一致,唯一不同的是第二个参数的类型必须为数组或者类数组

    Function.prototype.myApply = function (context, args = []) {
      // 新建一个唯一的Symbol变量避免重复
      let fn = Symbol()
      // 判断传入第一个参数是否为null或者undefined
      context = context == null ? window : Object(context)
      // 此处的this指的是当前的函数,将其作为第一个参数的属性,然后以对象的形式调用,就达到了绑定this的效果
      context[fn] = this
      // 执行函数
      const res = context[fn](...args)
      // 删除该方法,防止对传入的对象造成污染
      delete context[fn]
    
      // 返回结果
      return res
    }
    

3. 手写bind

bind接收两个参数,第一个是作为this,第二个作为当前调用函数的参数,并且返回一个函数,返回的函数也可以传入参数

  • 实现调用bind后独立函数调用

    Function.prototype.myBind = function (context, ...args) {
    
      return (...newArgs) => {
        // this 是当前调用myBind的函数
        return this(...args, ...newArgs)
      }
    }
    
  • 实现绑定this

    Function.prototype.myBind = function (context, ...args) {
      context = context == null ? window : Object(context)
    
      let fn = Symbol()
      context[fn] = this
      return (...newArgs) => {
        return context[fn](...args, ...newArgs)
      }
    }
    
  • 实现返回的新的函数可以作为构造函数调用

    • 注意:此处context[fn]不可以删除,因为返回的函数可能调用多次
    Function.prototype.myBind = function (context, ...args) {
      context = context == null ? window : Object(context)
      let fn = Symbol()
      // 将此处的fn属性设置为不可枚举
      Object.defineProperty(context, fn, {
        configurable: true,
        enumerable: false,
        writable: false,
        value: this
      })
      return function newFn(...newArgs) {
        // 判断是否作为构造函数调用
        if(this instanceof newFn) {
          return new context[fn](...args, ...newArgs)
        }
        return context[fn](...args, ...newArgs)
      }
    }
    
  • 验证

    function sum(num1, num2) {
      console.log(this, num1, num2)
    }
    
    const foo = sum.bind({
      name: 'kobe'
    }, 3)
    foo(5) // Object 3 5
    new foo(5) // sum 3 5
    
    const bar = sum.myBind({
      name: 'kobe'
    }, 3)
    bar(5) // Object 3 5
    new bar(5) // sum 3 5