关于call apply bind实现

378 阅读2分钟

本片文章主要讲三个方法的原理和实现。

首先,这三个方法跟this指向息息相关,因为可以通过他们实现this的硬绑定。

一、call和apply 使用方面,两者区别仅仅就是传入参数的方式。

call的参数是一个个传入,apply是以数组形式传入。两个方法实际是执行绑定的函数,以下是两者的实现:

Function.prototype.call = function (thisArg) {
    if (!this) {
        new TypeError('this is not defined');
    }
    // 拿到被调用函数的引用
    var func = Object(this);
    // 绑定当前上下文this
    thisArg.func = func;
    // 获取参数
    var funcArguments
    if (arguments.length > 1) {
        funcArguments = Array.from(arguments).slice(1);
        return thisArg.func(...funcArguments);
    }
    return thisArg.func();
}
Function.prototype.apply = function (thisArg) {
    if (!this) {
        new TypeError('this is not defined');
    }
    // 拿到被调用函数的引用
    var func = Object(this);
    // 绑定当前上下文this
    thisArg.func = func;
    // 获取参数
    const funcArguments = arguments[1];
    return thisArg.func(...funcArguments);
}

二、bind

bind与call(apply)的区别是,外面包裹了一层函数,返回的是一个函数而不是直接执行函数。实现如下:

function bind(context) {
    var func = Object(this);
    return function () {
        // return func.apply(context);
        return func.call(context);
    }
}

是不是bind过于简单了?可以看看core.js的实现方式:

function bind(that /* , ...args */) {
    // 绑定的是一个函数
    var fn = aFunction(this);
    // 获取第二个参数   就是绑定函数的参数列表,是一个数组
    var partArgs = arraySlice.call(arguments, 1);
    var bound = function (/* args... */) {
      var args = partArgs.concat(arraySlice.call(arguments));
      return this instanceof bound ? construct(fn, args.length, args) : invoke(fn, args, that);
    };
    // 继承
    if (isObject(fn.prototype)) bound.prototype = fn.prototype;
    return bound;
  }


  var construct = function (F, len, args) {
    if (!(len in factories)) {
      for (var n = [], i = 0; i < len; i++) n[i] = 'a[' + i + ']';
      // eslint-disable-next-line no-new-func  
      // 使用Function定义构造函数   
      factories[len] = Function('F,a', 'return new F(' + n.join(',') + ')');
    } return factories[len](F, args);
  };

  function aFunction(it) {
    if (typeof it != 'function') throw TypeError(it + ' is not a function!');
    return it;
  }

  function invoke(fn, args, that) {
    var un = that === undefined;
    switch (args.length) {
      case 0: return un ? fn()
                        : fn.call(that);
      case 1: return un ? fn(args[0])
                        : fn.call(that, args[0]);
      case 2: return un ? fn(args[0], args[1])
                        : fn.call(that, args[0], args[1]);
      case 3: return un ? fn(args[0], args[1], args[2])
                        : fn.call(that, args[0], args[1], args[2]);
      case 4: return un ? fn(args[0], args[1], args[2], args[3])
                        : fn.call(that, args[0], args[1], args[2], args[3]);
    } return fn.apply(that, args);
  }

  function isObject(it) {
    return typeof it === 'object' ? it !== null : typeof it === 'function';
  }

可以看出来,与我写的区别是,core.js做了一个判断

return this instanceof bound ? construct(fn, args.length, args) : invoke(fn, args, that);

而invoke其实就是我写的方法,重点看一下construct实现:

var construct = function (F, len, args) {
    if (!(len in factories)) {
      for (var n = [], i = 0; i < len; i++) n[i] = 'a[' + i + ']';
      // eslint-disable-next-line no-new-func  
      // 使用Function定义构造函数   
      factories[len] = Function('F,a', 'return new F(' + n.join(',') + ')');
    } return factories[len](F, args);
  };

其实就是用Function定义了一个函数,其中,

factories[len] = Function('F,a', 'return new F(' + n.join(',') + ')') 这一行可以这么理解

factories[len] = function(F,a){
  // a是一个数组
	return new F(...a);
}

所以,core.js想的更全面了,bind返回的函数有可能会作为构造函数来用 比如,

function A(){},

var B=A.bind(null);

new B()

这个时候就会走construct逻辑,其实是new A(),

image.png 但是,image.png

所以,core.js后面加了一句,

if (isObject(fn.prototype)) bound.prototype = fn.prototype;

再次印证了,construct(fn, args.length, args)是作为构造函数来实现的。