手把手教你实现三种绑定方式(call、apply、bind)

关于绑定首先要说下this的指向问题。

我们都知道:

函数调用时this指向window

对象调用函数时this指向对象本身

 

看下面得例子:

// 1
function test(){
    const name = 'test1';
    console.log(this.name)
}
constname = 'test2'
test() // test2
// 当前函数调用指向了window,所以打印的为test2

// 2
var obj = {
    name:'test1',
    get(){
        console.log(this.name)
    }
}
var name = 'test2';
obj.get() // test1
// 当前通过对象调用。this指向obj,打印test1

// 3
var obj = {
    name:'test1',
    get(){
        console.log(this.name)
    }
}
var name = 'test2';
var fn = obj.get;
fn() // test2
// 将obj.get作为值赋值给fn,执行fn,这时候相当于通过函数执行,this重新指向

 

那如何将this指向到我们指定的对象中呢?

js提供改变this指向的三种方法:call、apply、bind

其中call和apply执行改变this的同时会执行函数,bind是会返回一个函数。

而call与apply的区别在于参数,call可以传多个参数,而apply传一个数组作为参数。下面来看看实现:

 

模拟实现call

Function.prototype.myCall = function(thisArg, ...argumentArr){
    if(typeof this !== 'function') {
        throw new TypeError(`${this} is not function`)
    };
    if(thisArg === undefined || thisArg === null){
        thisArg = window;
    }
  //生成一个对象
var obj = Object.create(thisArg) obj['fn'] = this;
  // 通过对象执行函数将this指向该对象
var result = obj['fn'](...argumentArr) delete obj['fn']; return result } var obj = { name:'test1', get(data1, data2){ console.log(this.name, data1, data2) } } obj.get.myCall({name: 'test2'}, 1, 2, 3) // test2,1,2 // 原理就是通过传入的新的对象来执行这个函数,就是通过对象调用函数的方式

 

模拟实现apply

Function.prototype.myApply = function(thisArg, argumentArr){
    if(typeof this !== 'function') {
        throw new TypeError(`${this} is not function`)
    };
    if(thisArg === undefined || thisArg === null){
        thisArg = window;
    }
    if(argumentArr === undefined || argumentArr === null){
        argumentArr = []
    }
    var obj = Object.create(thisArg)
    obj['fn'] = this;
    var result = obj['fn'](...argumentArr)
    delete obj['fn'];
    return result
}

var obj = {
    name:'test1',
    get(data1, data2){
        console.log(this.name, data1, data2)
    }
}
obj.get.myApply({name: 'test2'}, [1, 2, 3]) // test2,1,2

// 我们发现与call唯一的不同就是入参,call使用rest 参数,将多余的参数整合成argument形式,而apply入参直接是数组

 

模拟实现bind

Function.prototype.myBind = function(thisArg, ...argumentArr){
    if(typeof this !== 'function') {
        throw new TypeError(`${this} is not function`)
    };
    if(thisArg === undefined || thisArg === null){
        thisArg = window;
    }
    var self = this
    var bindfn = function(){
        return self.call(thisArg, ...argumentArr)
    }
    bindfn.prototype = self.prototype
    return bindfn
}

var obj = {
    name:'test1',
    get(data1, data2){
        console.log(this.name, data1, data2)
    }
}
const fn = obj.get.myBind({name: 'test2'}, 1, 2, 3)
fn() //  test2,1,2

// 相比于前两个bind会有不一样,bind使用到了闭包,
// 我们之前知道函数执行this是指向window,但是这里我们执行却指向了我们的目标对象,实现这样的方式就是闭包

如果我们没有赋值操作执行var self = this,而是直接使用this执行的话

this.call(thisArg, ...argumentArr)

这个时候this是指向window的,这个时候会报this.call is not a function当我们进行赋值给self ,那么这个时候self 就形成了返回的函数fn的专属背包而不被销毁,每当我们执行fn的时候取到的this都是obj.get方法