關於繫結首先要說下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方法