call && apply && bind 的作用

callapplybind的作用都是改变函数的 this 指向

  • call:call 的第一个参数就是对函数绑定的 this,为一个对象,后面的参数是函数调用时所需要的参数
  • apply:apply 只接受两个参数,第一个与 call 相同,第二个参数是为数组,内容是函数调用所需要的参数
  • bind:bindcall作用相同,不过 bind 会返回一个新的函数,第一个参数之后的参数将绑定原函数的参数。

实现 call

先来看看原生call的使用方法

let obj = {
    a: 1
}

function fn(){
    console.log(this.a)
}

fn()    // undefined

fn.call(obj)    // 1

call 是如何将 this 绑定到 obj 上的呢? 我们换一种思路,先看下面的代码

let obj = {
    a: 1,
    fn(){
        console.log(this.a)
    }
}

obj.fn()    // 1

这段代码大家都能理解对吧,接下来,让我们打开脑洞。

如果 call 只是将调用函数绑定到了第一个参数身上呢?似乎一切就说得通了

function fn(){
    console.log(this.a)
}

Function.prototype.myCall = function(context){
    context = context || window
    //首先判断是否传入第一个参数,如果没传,那么 this 就默认绑定到 window

    context.callFn = this
    let result = context.callFn()
    delete context.callFn
    return result
}

let obj = {
    a: 1
}

fn.myCall(obj)    // 1

ok,初步绑定 this 的工作我们已经完成,接下来思考如何将剩下的参数传给调用函数。

在函数中,我们不光可以访问 this 还可以访问 arguments 这个属性,他是一个类数组,内容就是传递给函数的参数,于是与,获取参数的问题也解决了。

function fn(val1,val2){
    console.log(this.a)
    console.log('val: ', val1, val2)
}

Function.prototype.myCall = function(context){
    context = context || window
    let args = []

    // ES5 的方法
    for(let i = 1; i<arguments.length; i++){
        args.push(arguments[i])
    }

    /*
     * ES6的方法
     * args = [...arguments].slice(1)
     *
     * 这个做法不推荐,在实现 cal l的方法中使用 call,有点本末倒置了
     * args = Array.prototype.silce.call(arguments, 1)
     */

    context.callFn = this

    // ES5 的做法,eval 接收一个字符串,将字符串作为 JS 代码执行
    // 字符串模板会将数组变量展开
    let result = eval(`context.callFn(${args})`)

    /*
     * ES6 的做法
     * let result =  context.callFn(...args)
     */

    delete  context.callFn
    return result
}

let obj = {
    a: 1
}

fn.myCall(obj, 66,11)    // 1, val: 61, 11

实现 apply

基本思路跟 call 是一样的,只不过需要注意,apply的第二个参数是数组

function fn(val,val2){
    console.log(this.a)
    console.log('val', val, val2)
}

Function.prototype.myApply = function(context){
    context = context || window
    let args = arguments[1]
    context.applyFn = this
    let result = eval(`context.applyFn(${args})`)
    delete context.applyFn
    return result
}

let obj = {
    a: 1
}

fn.myApply(obj, [66,11])

实现 bind

bindcall 的区别就在于 bind 会返回一个绑定 this 的新函数

function fn(val,val2){
    console.log(this.a)
    console.log('val', val, val2)
}

Function.prototype.myBind = function(context){
// 这里直接使用我们自己实现的 call 函数
    let args = Array.prototype.slice.myCall(arguments);

//     ES5 的写法
//     let me = this;
//     return function () {
//         return me.myApply(context, args.slice(1))
//     }
    return () => {
//     使用已经实现的 apply 函数来绑定 this 并且 return 这个函数
        this.myApply(context, args.slice(1))
    }
}


let obj = {
    a: 1
}

fn.myBind(obj, 66,11)