call, apply, bind 区别及原理

3,624 阅读1分钟

三者异同

相同点:

三者都是用来改变函数的上下文,也就是this指向的。

不同点:

fn.bind: 不会立即调用,而是返回一个绑定后的新函数。

fn.call:立即调用,返回函数执行结果,this指向第一个参数,后面可有多个参数,并且这些都是fn函数的参数。

fn.apply:立即调用,返回函数的执行结果,this指向第一个参数,第二个参数是个数组,这个数组里内容是fn函数的参数。

应用场景

  • 需要立即调用使用call/apply
  • 要传递的参数不多,则可以使用fn.call(thisObj, arg1, arg2 ...)
  • 要传递的参数很多,则可以用数组将参数整理好调用fn.apply(thisObj, [arg1, arg2 ...])
  • 不需要立即执行,而是想生成一个新的函数长期绑定某个函数给某个对象使用,使用const newFn = fn.bind(thisObj); newFn(arg1, arg2...)

实现原理

  • call的实现
Function.prototype.myCall = function(thisObj = window){
    thisObj.fn = this //此处this是指调用myCall的function
    // 处理arguments
    let arg = [...arguments].slice(1)
    let result = thisObj.fn(...arg) // 执行该函数
    delete thisObj.fn
    return result
}

验证一下

var foo = {
    value: 1
};
function bar(name, age) {
    console.log(name)
    console.log(age)
    console.log(this.value);
}
bar.myCall(foo, 'kevin', 18);
// kevin
// 18
// 1
  • apply的实现,和call类似
Function.prototype.myApply = function (context = window, arr){
    context.fn = this
    let result
    if(!arr){
        result = context.fn()
    } else {
        result = context.fn(...arr)
    }
    delete context.fn
    return result
}
  • bind的实现

bind 的实现要借助刚才的apply

Function.prototype.myBind = function(context){
    //  类型判断
    if(typeof this !== 'function'){
        throw new TypeError('must be a function')
    }
    let self = this // 这个this是调用bind的函数
    let argsArr = [...arguments].slice(1)

    return function(){
        let bindFuncArg = [...arguments]
        // 合并两次传的参数
        let totalArgs = argsArr.concat(bindFuncArg)
        return self.apply(context, totalArgs)
    }
}