一起來學習一下this關鍵字
一、定義
this是在函式被呼叫時確定的,它的指向完全取決於函式呼叫的地方,而不是它被宣告的地方(箭頭函式除外)。當一個函式被呼叫時,會建立一個執行上下文,它包含函式在哪裡呼叫(呼叫棧)、呼叫方式、引數等資訊,this屬性會記錄這些資訊,會在函式執行的過程中被用到。
this在函式的指向有以下幾種場景:
1)作為建構函式被new呼叫;
2)作為物件的方法使用;
3)作為函式直接呼叫;
4)被call、apply、bind呼叫;
5)箭頭函式中的this;
二、this在各種場景中的指向
1、new 繫結
函式如果作為建構函式使用new呼叫時,this繫結的是新建立的建構函式的例項。
functionTest() { console.log(this) } var o = new Test() // 輸出: Test 例項,this 就是 o
實際上使用new呼叫建構函式時,會依次執行以下操作:
A:建立一個新物件;
B:建構函式的prototype被賦值給這個新物件的_proto_;
C:將新物件賦值給當前的this;
D:執行建構函式;
E:如果函式沒有返回其他物件,那麼new表示式中的函式呼叫會自動返回這個新物件,如果返回的不是物件將被忽略;
2、顯式繫結
我們可以通過call、apply、bind
call()方法與 apply()方法的作用相同,它們的區別僅在於接收引數的方式不同,call 方法接受的是引數列表,而 apply 方法接受的是一個引數陣列。
func.call(thisArg, arg1, arg2, ...) // call 用法 func.apply(thisArg, [arg1, arg2, ...]) // apply 用法
bind()會建立一個函式的例項,其 this 值會被繫結到傳給 bind()函式的值,並返回一個新的函式,且在呼叫新函式時,將給定引數列表作為原函式的引數序列的前若干項。
func.bind(thisArg[, arg1[, arg2[, ...]]]) // bind 用法
var food = { name: '漢堡', price: '5塊錢', getPrice: function(place) { console.log(place + this.price) } } food.getPrice('KFC ') // 瀏覽器中輸出: "KFC 5塊錢" var getPrice1 = food.getPrice.bind({ name: '雞腿', price: '7塊錢' }, '肯打雞 ') getPrice1() // 瀏覽器中輸出: "肯打雞 7塊錢"
注意:如果你把null
或undefined
作為this
的繫結物件傳入call
、apply
、bind
,這些值在呼叫時會被忽略,實際應用的是預設繫結規則。
var a = 'hello' function foo() { console.log(this.a) } foo.call(null) // 瀏覽器中輸出: "hello"
3、隱式繫結
函式是否在某個上下文物件中呼叫,如果是的話this
繫結的是那個上下文物件。
var a = 'hello' var obj = { a: 'world', foo: function() { console.log(this.a) } } obj.foo() // 瀏覽器中輸出: "world"
4、預設繫結
非嚴格模式下this
繫結到全域性物件(瀏覽器下是winodw
,node 環境是global
),嚴格模式下this
繫結到undefined
(因為嚴格模式不允許this
指向全域性物件)。
var a = 'hello' function foo() { var a = 'world' console.log(this.a) console.log(this) } foo() // 相當於執行 window.foo() 輸出: "hello" Window 物件
注意:以下情況
var a = 'hello' var obj = { a: 'world', foo: function() { console.log(this.a) } } var bar = obj.foo bar() // 瀏覽器中輸出: "hello"
此時bar
函式,也就是obj
上的foo
方法為什麼又指向了全域性物件呢,是因為bar
方法此時是作為函式獨立呼叫的,所以此時的場景屬於預設繫結,而不是隱式繫結。這種情況和把方法作為回撥函式的場景類似:
var a = 'hello' var obj = { a: 'world', foo: function() { console.log(this.a) } } function func(fn) { fn() } func(obj.foo) // 瀏覽器中輸出: "hello"
引數傳遞實際上也是一種隱式的賦值,只不過這裡obj.foo
方法是被隱式賦值給了函式func
的形參fn
,而之前的情景是自己賦值,兩種情景實際上類似。這種場景我們遇到的比較多的是setTimeout
和setInterval
,如果回撥函式不是箭頭函式,那麼其中的this
指向的就是全域性物件。
5、this繫結的優先順序
new 繫結 > 顯式繫結 > 隱式繫結 > 預設繫結||this 的判斷順序
A:new 繫結:函式是否在new
中呼叫?如果是的話this
繫結的是新建立的物件;
B:顯式繫結:函式是否是通過bind
、call
、apply
呼叫?如果是的話,this
繫結的是指定的物件;
C:隱式繫結:函式是否在某個上下文物件中呼叫?如果是的話,this
繫結的是那個上下文物件;
D:如果都不是的話,使用預設繫結。如果在嚴格模式下,就繫結到undefined
,否則繫結到全域性物件;
6、箭頭函式中的this
箭頭函式是根據其宣告的地方來決定this
的
var a = 'hello' var obj = { a: 'world', foo: () => { console.log(this.a) } } obj.foo() // 瀏覽器中輸出: "hello"
var a = 20 var obj = { a: 40, foo:() => { console.log(this.a) function func() { this.a = 60 console.log(this.a) } func.prototype.a = 50 return func } } var bar = obj.foo() // 瀏覽器中輸出: 20 bar() // 瀏覽器中輸出: 60 new bar() // 瀏覽器中輸出: 60
稍微解釋一下:
1、var a = 20
這句在全域性變數 window 上建立了個屬性a
並賦值為 20;
2、首先執行的是obj.foo()
,這是一個箭頭函式,箭頭函式不建立新的函式作用域直接沿用語句外部的作用域,因此obj.foo()
執行時箭頭函式中this
是全域性 window,首先打印出 window 上的屬性a
的值 20,箭頭函式返回了一個原型上有個值為 50 的屬性a
的函式物件func
給bar
;
3、繼續執行的是bar()
,這裡執行的是剛剛箭頭函式返回的閉包func
,其內部的this
指向 window,因此this.a
修改了window.a
的值為 60 並打印出來;
4、然後執行的是new bar()
,根據之前的表述,new 操作符會在func
函式中建立一個繼承了func
原型的例項物件並用this
指向它,隨後this.a = 60
又在例項物件上建立了一個屬性a
,在之後的列印中已經在例項上找到了屬性a
,因此就不繼續往物件原型上查找了,所以打印出第三個 60;