call、apply、bind的模拟实现

本博客 hjy-xh,转载请申明出处

方法用途和异同点

先总结一下,这三个方法的主要作用是改变函数中this的指向。

关于this的指向,需要记住this永远指向最后调用它的那个对象

那三个方法使用起来的异同点是什么呢?

相同点:

  • 第一个参数都是用来指定this,即上下文
  • 都可以用后续参数进行传参

不同点:

  • bind方法返回一个新的函数,callapply则是立即调用
  • callapply基本类似,区别在于除了第一个参数之外的其它参数不同。前者接收若干个参数列表,后者接收一个包含多个参数的数组
  • 非严格模式下,call方法的第一个参数如果没传,或者是nullundefined时,this指向全局对象

模拟实现

  • call

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    Function.prototype.imitateCall = function (context) {
    // 如果没有则默认为 window,即访问全局作用域对象
    context = context || window;
    // 获取调用call的函数,用this可以获取
    context.fn = this;
    // 截取作用域对象参数后面的参数
    let args = [...arguments].slice(1);
    // 执行调用函数,记录返回值
    let result = context.fn(...args);
    // 销毁调用函数,以免作用域污染
    Reflect.deleteProperty(context, 'fn');
    return result;
    }
  • apply

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    Function.prototype.myApply = function (context) {
    // 如果没有则默认为 window,即访问全局作用域对象
    context = context || window;
    // 获取调用call的函数,用this可以获取
    context.fn = this;
    let result;
    if (arguments[1]) {
    result = context.fn(...arguments[1]);
    } else {
    result = context.fn();
    }
    // 销毁调用函数,以免作用域污染
    Reflect.deleteProperty(context, 'fn');
    return result;
    }
  • bind

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    Function.prototype.myBind = function (context) {
    // 获取绑定时的传参
    let args = [...arguments].slice(1),
    // 定义中转构造函数,用于通过原型连接绑定后的函数和调用bind的函数
    F = function () { },
    // 记录调用函数,生成闭包,用于返回函数被调用时执行
    self = this,
    // 定义返回(绑定)函数
    bound = function () {
    // 合并参数,绑定时和调用时分别传入的
    let finalArgs = [...args, ...arguments]

    // 改变作用域,注:aplly/call是立即执行函数,即绑定会直接调用
    // 这里之所以要使用instanceof做判断,是要区分是不是new xxx()调用的bind方法
    return self.call((this instanceof F ? this : context), ...finalArgs)
    };

    // 将调用函数的原型赋值到中转函数的原型上
    F.prototype = self.prototype;
    // 通过原型的方式继承调用函数的原型
    bound.prototype = new F();

    return bound;
    }

参考

this、apply、call、bind

JavaScript深入之call和apply的模拟实现

JavaScript深入之bind的模拟实现

Understanding JavaScript Bind ()

JavaScript’s Apply, Call, and Bind Methods are Essential for JavaScript Professionals

MDN