# 1. 原理介绍

callapplybind 是挂在 Function 对象上的三个方法,调用这三个方法的必须是一个函数。如下:

func.call(thisArg, param1, param2, ...)
func.apply(thisArg, [param1,param2,...])
func.bind(thisArg, param1, param2, ...)
1
2
3

其中 func 是要调用的函数,thisArg 一般为 this 所指向的对象,后面的 param1, param2, ... 为函数 func 的多个参数,如果 func 不需要参数,则后面的 param1、2 可以不写。

这三个方法共有的、比较明显的作用就是,都可以改变函数 functhis 指向。callapply 的区别在于,传参的写法不同:apply 的第 2 个参数为数组; call 则是从第 2 个至第 N 个都是给 func 的传参;而 bind 和这两个(callapply)又不同,bind 虽然改变了 functhis 指向,但不是马上执行,而这两个(callapply)是在改变了函数的 this 指向之后立马执行。

# 2. 手写实现

# 2.1 call 函数

  • 描述

    call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。

  • 语法

    func.call(thisArg, arg1, arg2, ...)
    
    1
  • 参数

    • thisArg:可选的。在 fun 函数运行时指定的 this 值*。*需要注意的是,指定的 this 值并不一定是该函数执行时真正的 this 值,如果这个函数在非严格模式 (opens new window)下运行,则指定为 nullundefinedthis 值会自动指向全局对象(浏览器中就是 window 对象),同时值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的自动包装对象。
    • arg1, arg2, ...:可选的。指定的参数列表。
  • 返回值

    使用调用者提供的 this 值和参数调用该函数的返回值。若该方法没有返回值,则返回 undefined

  • 代码实现

    Function.prototype.myCall = function (context, ...args) {
      if (context == null) context = globalThis;
      // 值类型,变为对象
      if (typeof context !== "object") context = new Object(context); 
    
      const fnKey = Symbol(); // 不会出现属性名称的覆盖
      context[fnKey] = this; // this 就是当前的函数
    
      const res = context[fnKey](...args); // 绑定了 this
    
      delete context[fnKey]; // 清理掉 fn ,防止污染
    
      return res;
    };
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    实现思路:

    • 首先,判断调用mycall的是不是函数,如果不是,则直接抛出异常;
    • 接着,判断是否传入了第一个参数context,也就是要指定的this值,如果没有传入,则默认为window全局对象;
    • 然后,谁将来调用mycall,那么this就是谁,将其赋给context.fn;
    • 然后,通过参数伪数组将context后面的参数取出来,并传给context.fn获得执行结果result
    • 最后,删除掉context.fn,并将result返回;

# 2.2 aplly 函数

  • 描述

    apply() 方法调用一个具有给定this值的函数,以及作为一个数组(或类似数组对象 (opens new window))提供的参数。

  • 语法

    func.apply(thisArg, [argsArray]);
    
    1
  • 参数

    • thisArg:可选的。在 func 函数运行时使用的 this 值。请注意,this可能不是该方法看到的实际值:如果这个函数处于非严格模式 (opens new window)下,则指定为 nullundefined 时会自动替换为指向全局对象,原始值会被包装。
    • argsArray:可选的。一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 func 函数。如果该参数的值为 nullundefined,则表示不需要传入任何参数。从 ECMAScript 5 开始可以使用类数组对象。
  • 返回值

    调用有指定 this 值和参数的函数的结果。

  • 代码实现

    Function.prototype.customApply = function (context, args = []) {
      if (context == null) context = globalThis;
    
      // 值类型,变为对象
      if (typeof context !== "object") context = new Object(context);
    
      const fnKey = Symbol(); // 不会出现属性名称的覆盖
      context[fnKey] = this; // this 就是当前的函数
    
      const res = context[fnKey](...args); // 绑定了 this
    
      delete context[fnKey]; // 清理掉 fn ,防止污染
    
      return res;
    };
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

    实现思路:

    • 首先,判断调用myapply的是不是函数,如果不是,则直接抛出异常;
    • 接着,判断是否传入了第一个参数context,也就是要指定的this值,如果没有传入,则默认为window全局对象;
    • 然后,谁将来调用myapply,那么this就是谁,将其赋给context.fn;
    • 然后,判断是否传入了第二个参数,如果传入了则将其使用展开运算符...传给context.fn获得执行结果result,如果没有传入,则直接调用context.fn获得执行结果result
    • 最后,删除掉context.fn,并将result返回

# 2.3 bind 函数

  • 描述

    **bind()方法创建一个新的函数,在bind()**被调用时,这个新函数的thisbind的第一个参数指定,其余的参数将作为新函数的参数供调用时使用。

  • 语法

    func.bind(thisArg[, arg1[, arg2[, ...]]])
    
    1
  • 参数

    • **thisArg:**调用绑定函数时作为this参数传递给目标函数的值。 如果使用new运算符构造绑定函数,则忽略该值。当使用bindsetTimeout中创建一个函数(作为回调提供)时,作为thisArg传递的任何原始值都将转换为object。如果bind函数的参数列表为空,执行作用域的this将被视为新函数的thisArg
    • **arg1, arg2, ...😗*当目标函数被调用时,预先添加到绑定函数的参数列表中的参数。
  • 返回值

    返回一个原函数的拷贝,并拥有指定的 this 值和初始参数。

  • 代码实现

    Function.prototype.mybind = function (context, ...bindArgs) {
      // context 是 bind 传入的 this
      // bindArgs 是 bind 传入的各个参数
      const self = this; // 当前的函数本身
    
      return function (...args: any[]) {
        // 拼接参数
        const newArgs = bindArgs.concat(args);
        return self.apply(context, newArgs);
      };
    };
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    实现思路:

    • 首先,判断调用mybind的是不是函数,如果不是,则直接抛出异常;
    • 接着,谁将来调用mybind,那么this就是谁,将其赋给_this,缓存一下;
    • 然后,通过参数伪数组将context后面的参数(预先添加到绑定函数的参数)取出来,记作args,
    • 然后,返回一个函数,并判断如果使用new运算符构造绑定函数,则忽略传入的第一个参数context,并将预先添加到绑定函数的参数args和将来传入新函数的参数arguments分别通过展开运算符...依次传入给调用mybind的调用者,并将结果返回。
    • 最后,如果不是使用new运算符构造绑定函数,则对调用者使用apply方法,将传入的第一个参数以及预先添加到绑定函数的参数args和将来传入新函数的参数arguments分别通过展开运算符...依次传入给调用mybind的调用者,并将结果返回;