讀了《你不知道的JavaScript(上)》之後總結一下this指標,this到底是什麼?它到底指向什麼?
什麼是this指標?
想知道this指標是什麼。首先需要明白執行上下文的概念以及結構。我先簡單介紹一下執行上下文,具體的介紹會另寫一章。
執行上下文是當前所執行的程式碼的執行環境,而Js的執行環境包括:全域性環境,函式環境和eval(已經不推薦使用)。在執行不同的程式碼時,會進入當前程式碼的執行上下文。因此在執行一段Js程式時必然會產生多個執行上下文,Js引擎會用棧(執行上下文棧)這個資料結構來裝載它們。
瀏覽器首次載入指令碼,全域性環境對應的執行上下文入棧,且它永遠都在棧底,每進入一個不同的程式碼片段(不同的作用域),它的執行上下文就會入棧,當該段程式碼執行完之後就會將它的執行上下文出棧。而執行程式碼的順序就又是另一個知識點了,涉及到event loop事件迴圈。
一個執行上下文可以將它抽象的看作一個物件object,它包含一些屬性,VO(變數物件),作用域鏈以及this。
現在我們可以知道this就是執行上下文的一個屬性,會在函式執行的過程中用到。
this的指向
首先丟擲一個重要的結論:this既不指向函式自身也不指向函式的詞法作用域。this實際上是在函式被呼叫時發生的繫結,它指向什麼完全取決於函式在哪裡被呼叫。
確定this的指向,也可以稱為決定this的繫結物件。需要找到函式的呼叫位置之後,判斷需要應用以下哪四條繫結規則:
1. 預設規則
獨立函式呼叫(函式呼叫時沒有任何修飾),該函式的this指標指向全域性環境。
ES5環境下的非嚴格模式:
var a = 1;
function thisTo() {
var a = 2;
console.log(this.a);
}
thisTo();
複製程式碼
在chrome控制檯下測試結果輸出1。
ES6環境下自動採用嚴格模式:
const a = 1;
function thisTo() {
const a = 2;
console.log(this.a);
}
thisTo();
複製程式碼
在chrome控制檯下測試結果為undefined。即嚴格模式下this會繫結到undefined。
2. 隱式繫結
函式被呼叫時,某一個物件擁有該函式引用,則該函式的this指向該物件。
ES5和ES6的結果相同,不再附上ES6的程式碼:
var a = 1;
var obj = {
a: 2,
thisTo: thisTo
};
function thisTo() {
var a = 3;
console.log(this.a);
}
obj.thisTo();
複製程式碼
在chrome控制檯下測試結果輸出2。
隱式繫結規則又下個問題,被隱式繫結的函式可能會丟失它的繫結物件,這時它會使用預設規則,將this指向全域性物件或者undefined。
var a = 1;
var obj = {
a: 2,
thisTo: thisTo
};
function thisTo() {
var a = 3;
console.log(this.a);
}
var loseThis = obj.thisTo;
loseThis();
複製程式碼
在chrome控制檯下測試結果輸出1。
雖然loseThis是obj.thisTo的一個引用,但實際上,它引用的是thisTo,相當於一次獨立函式呼叫,應用了預設規則。
類似於function(obj.thisTo)
或者setTimeout(obj.thisTo,100)
都是實際上引用了thisTo,應用了預設規則,因此我們應該儘可能的明確this的指向,讓它固定繫結的在某個上下文物件上,不會因為某個意外而改變指向。
3. 顯式繫結
呼叫函式的call和apply方法,讓某個物件強制呼叫該函式,使得函式的this指向該物件。call和apply的第一個引數是一個物件,在呼叫函式時會將this繫結到該物件上。
var a = 1;
var obj = {
a: 2,
};
function thisTo() {
var a = 3;
console.log(this.a);
}
thisTo.call(obj);
複製程式碼
在chrome控制檯下測試結果輸出2。
顯式繫結仍然無法解決丟失繫結的問題:
var a = 1;
var obj1 = {
a: 2,
};
var obj2 = {
a: 4,
};
function thisTo() {
var a = 3;
console.log(this.a);
}
thisTo.call(obj1);
thisTo.call(obj2);
thisTo();
複製程式碼
在chrome控制檯下測試結果輸出2,4,1。this繫結的物件會變化,可能在某些回撥函式時不經意修改了this的繫結物件,造成this指向丟失。顯式繫結的一個變種可以解決這個問題,它的this指向一經繫結就不能再修改。
var a = 1;
var obj1 = {
a: 2,
};
var obj2 = {
a: 4,
};
function thisTo() {
var a = 3;
console.log(this.a);
}
var hard = thisTo.bind(obj1);
hard();
setTimeout(hard, 1000);
hard.call(obj2);
hard = thisTo.bind(obj2);
hard();
複製程式碼
在chrome控制檯下測試結果輸出2,2,4,2。上面一種方式建立了函式hard,它的內部強制將thisTo的this繫結到obj1。無論之後如何呼叫修飾函式hard,它都會在obj1上呼叫thisTo。但是將函式hard重新賦值,在它內部重新將thisTo的this繫結到obj2上是可以的。
4. new繫結
呼叫建構函式之後新建立的實力物件會繫結到函式呼叫的this上。
function Cat(name) {
this.name = name;
this.miaow = function() {
console.log(this.name + "miaomiaomiao")
}
}
var cat1 = new Cat('kitty');
console.log(cat1.name);
cat1.miaow();
var cat2 = new Cat('tom');
console.log(cat2.name);
cat2.miaow();
複製程式碼
控制檯輸出:
kitty
kittymiaomiaomiao
tom
tommiaomiaomiao
複製程式碼
使用new來呼叫函式,會自動執行以下操作:
(1)建立一個例項物件。
(2)這個物件的[[prototype]]屬性會和原型物件關聯。
(3)這個物件會繫結到函式呼叫的this。
(4)返回這個例項物件。
優先順序
new繫結>顯示繫結>隱式繫結>預設繫結
this的例外
上述四條規則已經可以包含所有正常的函式。但是ES6的箭頭函式是個例外。它不使用this的四種標準規則,而是根據外層(函式或者全域性)作用域來決定this。