函数原型方法call/apply/bind源码实现

514 阅读3分钟

前言

第一版是一年前发的,现在再看一遍实现方法,感觉有些地方没有考虑周全,真是每看一遍收获都不一样啊~现在就在原来的基础上更改一下。

这三个方法都是改变其函数this指向的,首先给出call/apply/bind方法的区别:

  1. bind:不会立即执行函数,并且返回了一个函数,如果形参有多个的情况下,实参还可以分开传送。
  2. call:立即执行函数,传参形式为参数序列
  3. apply:立即执行函数,传参形式为数组

基本思路

  1. bind

运用柯里化函数的思想,执行bind()之后,返回了一个新的函数,也是闭包的一个运用场景。

注意

  1. 当 bind 返回的函数作为构造函数的时候,让这个返回的函数指向特定的this不可行,应该是指向那个实例的,所以要考虑。

  2. 因为bing是返回的一个函数,所以这个返回的函数就得考虑到原型的问题,不然原函数经过bind一顿操作,原型上的属性都不见了....,这是个细节的地方,需要注意一下。

  3. call

主要是搞清楚执行call时,干了啥,1. 把this方法执行,这是的this就是那个调用call方法的函数,这里假设为fn1。 2. 将这个fn1this指向改为你指定的那个。

重要

  1. bar.call(obj):想让bar中的this指向obj,直接在obj对象里面创建一个bar函数。

  2. apply

applycall方法思路相同,只是传的参数类型不同。

上代码

   let obj = {
      fn(x, y) {
        console.log(this, x, y)
      }
    }

运用自执行函数形成闭包,以免影响全局,保护了方法。

~function anonymous(proto) {
      function bind(context) {
        context = context == null ? window : context // 考虑特殊情况null和undefined
        let _this = this
        // bind函数可以分批次传参
        let args = [].slice.call(arguments, 1) 
        //这里运用es5来处理,将类数组转换为数组。
        //也可以写Array.prototype.slice.call(arguments, 1)
        // console.log(args) [10,20]
        return function proxy() {
            var arg1 = [].slice.call(arguments, 0)
        //判断返回的函数是否是一个构造函数,因为构造函数,可以通过new操作符,重定向this指向,则bind指定的this无效。
          _this.apply(this instanceof proxy ? this : context, args.concat(arg1))
          proxy.prototype = _this.prototype
        }
      }
      function call(context, ...args) {
        /* 干了三件事:
        1. 将this执行,假设为fn1
        2. 将fn1中的this指向context 
        3. 要考虑到特殊情况,如果context不是引用类型的话,则需要自己转换
        */
        context = context == null ? window : context
        const type = typeof context
        if (type !== 'object' && type !== 'function' && type !== 'symble') {
          //基本数据类型
          switch (type) {
            case ('number'): context = new Number(context); break;
            case ('string'): context = new String(context); break;
            case ('Boolean'): context = new Boolean(context); break;
          }
        }
        context.$fn = this
        const res = context.$fn(...args)
        return res
      }
      function apply(context, args) {
        context = context == null ? window : context
        const type = typeof context
        if (type !== 'object' && type !== 'function' && type !== 'symble') {
          //基本数据类型
          switch (type) {
            case ('number'): context = new Number(context); break;
            case ('string'): context = new String(context); break;
            case ('Boolean'): context = new Boolean(context); break;
          }
        }
        context.$fn = this
        const res = context.$fn(...args) //展开
        return res
      }
      proto.bind = bind
      proto.call = call
      proto.apply = apply
    }(Function.prototype)

测试:

    setTimeout(obj.fn.bind(window, 10, 20), 1000)
    obj.fn.call('4', 10, 20) // String {'4', $fn: ƒ} 10 20
    obj.fn.apply('4', [10, 20]) // String {'4', $fn: ƒ} 10 20