Javascript call,apply,bind的区别和简单模拟实现

383 阅读4分钟

「这是我参与11月更文挑战的第20天,活动详情查看:2021最后一次更文挑战

Javascript call,apply,bind的区别和简单模拟实现

call,apply,bind最主要的作用,是改变this的指向。在平常的业务中可能用得不是特别多,一般可能是在写一些基础类,工具函数,公用库方法等的时候会用到。啊对了,还有面试的时候哈哈哈。这篇文章主要是来梳理一下三者的特性和用法。

一、共同点和区别

1. call 和 apply 的共同点

call和apply的共同点是,都能够改变函数执行时的执行上下文(即this的指向),而且是立即执行的。

2. call 和 apply 的区别

call和apply的区别主要是体现在语法中的参数传递上。我们可以先把传入调用函数的参数称为调用参数。
call()方法接受的是参数列表,而apply()方法接受的是一个参数数组。 换句话说,call() 中调用参数是依次传入的,而 apply() 中调用参数是作为一个整体(数组或类数组)传入的。
具体的我们不妨顺便复习一下call和apply的语法。

call 的语法

func.call(thisArg, arg1, arg2, ...)

注意点:

  • call 的调用者必须是函数 Function。
  • call 的第一个参数,是一个对象。 Function 的调用者,将会指向这个对象。如果这个函数处于非严格模式下,指定为 null 或 undefined(不传) 时会自动替换为指向全局对象原始值会被包装
  • 第二个参数开始,可以接收任意个参数。每个参数会映射到相应位置的 Function 的参数上。

apply 的语法

func.apply(thisArg , [ argsArray])

注意点:

  • apply 的调用者必须是函数 Function,并且只接收两个参数。
  • apply 的第一个参数规则和 call 一样。
  • 第二个参数,必须是数组或者类数组,它们会被转换成类数组,传入 Function 中,并且会被映射到 Function 对应的参数上。

3. (call,apply)和 bind 的区别

先看MDN上关于bind的介绍

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

function.bind(thisArg[, arg1[, arg2[, ...]]])

可以发现

  • bind() 会创建并返回一个新函数,它不是立即调用的。
  • bind() 的第一个参数规则和 call 一样。
  • bind() 的传参方式和 call() 类似。

三、简单模拟实现

1. call

//传入一个this 绑定上所有的属性
Function.prototype.MyCall=function(context){
    if(typeof this !=='function'){
        throw new TypeError('error')
    }
    context=context||window;
    context.fn=this;
    const args=[...arguments].slice(1);//除去要绑定的对象,剩下参数应该绑定进去
    const result=context.fn(...args);
    delete context.fn;
    return result;
}

2. apply

// apply
Function.prototype.Myapply()=function(context){
    if (typeof this !=='function'){
        throw TypeError('Error');
    }
    context=context||window;
    context.fn=this;
    let result;
    //判断是否存在数组参数,毕竟是可选参数
    if(arguments[1]){
        result=context.fn(...arguments[1]);
    }else{
        result=context.fn();
    }
    delete context.fn;
    return result;
}

3. bind

// bind
Function.prototype.myBind=function(context){
    if(typeof this !=='function'){
        throw TypeError ('Error');
    }
    const _this=this;
    const args=[...arguments].slice(1);
    return function F(){
        //如果采用的是new方法的话,就不动this
        if(this instanceof F){
            //链式调用要把新旧参数加上去哦
            return new _this(...args,...arguments);
        }else{
            return _this.apply(context,args.concat([...arguments]));
        }
    }
}
  1. thisArg 调用绑定函数时作为this参数传递给目标函数的值。 如果使用new运算符构造绑定函数,则忽略该值。
  2. 当使用bind在setTimeout中创建一个函数(作为回调提供)时,作为thisArg传递的任何原始值都将转换为object。
  3. 如果bind函数的参数列表为空,执行作用域的this将被视为新函数的thisArg。
  4. arg1, arg2, ... 当目标函数被调用时,预先添加到绑定函数的参数列表中的参数。

注意点:

  • 因为返回的是一个函数,所以要考虑new的情况
  • 由于链式调用,还要小心只有第一次调用bind传入的this才会生效(才是被绑定的对象)。