JavaScript(1)之——this指標

zhunny發表於2019-03-01

  讀了《你不知道的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。

相關文章