手把手教你實現三種綁定方式(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方法