關於綁定首先要說下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; } thisArg['fn'] = this;
// 通過對象執行函數將this指向該對象 var result = thisArg['fn'](...argumentArr) delete thisArg['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 = [] } thisArg['fn'] = this; var result = thisArg['fn'](...argumentArr) delete thisArg['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方法