1. 程式人生 > 程式設計 >我所理解的JavaScript中的this指向

我所理解的JavaScript中的this指向

前言

JS 中的 this 指向是一個經常被問到的問題,網上也有很多文章是關於 this 的。本文整理一下我理解下的 this 以及一些我比較疑惑的關於 this 問題。

this 指向

有幾個 this 的指向問題是幾乎每篇文章都會說的,比如作為函式直接呼叫,作為物件的方法呼叫, new 運算子執行中的 this 行為。比較通用的說法是, this 指向的是直接呼叫該函式的物件。其實也很好理解,就是為什麼需要 this 這個關鍵字,就是我們有需要在函式內部對呼叫函式的物件進行操作的需求。但是有時候我們遇到的情況並不是像書上或 mdn 上遇到的典型的情況, this 的行為可能就會讓我們感到有點疑惑。

函式的直接呼叫

當我們直接呼叫一個已經宣告的函式,那麼在非嚴格模式下,該函式內部的 this 指向的是全域性物件,瀏覽器環境下就是 window 物件。

function f1(){
 return this;
}
//在瀏覽器中:
f1() === window; //在瀏覽器中,全域性物件是window

//在Node中:
f1() === global;

當函式是在全域性環境下定義的時候,這種現象是可以理解的,因為全域性環境下定義的函式其實就是掛載在全域性物件上的一個屬性,比附上面的 f1 也可以理解為 window.f1。但我認為嚴格模式下的行為才是更符合 this 這個關鍵字的目的的,特別是我們的函式可能是在非全域性環境(比如另一個函式中)定義和呼叫的,這種情況下 this

還指向 window 是不太合理的。所以在嚴格模式下,一個函式直接呼叫,它的 this 指向的是 undefined,如果我們想要得到非嚴格模式下的結果,那我們呼叫函式的方法就要改為 window.f1(),而如果函式是在非全域性環境下定義的話,那麼始終返回的是 undefined。我認為這樣的行為是更符合邏輯的。

'use strict'
function d () {
 function e() {
  console.log(this)
 }
 console.log(this)
}

d()
//undefined
//undefined

window.d()
//Window{}
//undefined

這裡在全域性模式下使用 use strict 只是為了測試,實際使用還是儘量放在函式內區域性使用嚴格模式,全域性下的嚴格模式很容易導致出錯。

函式作為物件的屬性呼叫

這也是在程式碼中非常常見的場景,我認為這是比函式呼叫更好理解,也更能幫助我們理解 this 行為的場景。簡單的來說就是 this 指向的是 直接 呼叫函式的那個物件。並且要注意的是,這跟函式在哪裡定義的是無關的,我們看 this,看的就是從哪裡呼叫的函式。

//在物件內部定義
var o = {
 prop: 37,f: function() {
 return this.prop;
 }
};

console.log(o.f()); // 37

//在物件外部定義
var o = {prop: 37};

function independent() {
 return this.prop;
}

o.f = independent;

console.log(o.f()); // 37

//在物件內部定義,但是給外部變數引用並執行
var o = {
 prop: 37,f: function() {
  console.log(this)
 return this.prop;
 }
};
var prop = 100;
var m = o.f;
console.log(m());
//Window{}
//100

上面的段落我給 直接 這兩個字加粗了,想要表達的意思是當我們通過多個物件的屬性巢狀找到並呼叫函式,那麼最後那個最接近函式的物件就是函式 this 的指向。

var o = {
 a:10,b:{
  a:12,fn:function(){
   console.log(this.a); //12
  }
 }
}
o.b.fn();

var o = {
 a:10,b:{
  // a:12,fn:function(){
   console.log(this.a); //undefined
  }
 }
}
o.b.fn();

為什麼我說這個場景能夠幫助我們理解,原因就是它反映出 this 這個關鍵字的本質。JS 中的函式也是一種物件,在我們的執行環境中的活動物件儲存的也只是函式物件的一個引用,如果這個引用是儲存在活動物件中的某個物件的屬性中(即我們通過活動物件中的某個物件的屬性找到該函式),那麼函式執行的時候 this 就會指向這個物件,這也是為什麼多層物件的呼叫,還是最靠近函式的那個物件作為 this。雖然在程式碼中我們的函式是在物件中定義的,但是實際在記憶體中,物件中只儲存著函式的引用,函式自己是在一個單獨的記憶體空間中。所以我們通過哪個物件找到函式並執行,函式中的 this 就指向這個物件。上面的直接呼叫 this 返回 undefined 也是說得通的。

通過原型的呼叫

有時我們是通過原型來執行公用的函式,此時已然符合我們上面的邏輯,我們通過哪個例項 找到 函式,那麼 this 就指向那個例項。

var o = {
 f: function() { 
 return this.a + this.b; 
 }
};
var p = Object.create(o);
p.a = 1;
p.b = 4;

console.log(p.f()); // 5

箭頭函式

箭頭函式並沒有自己的 this,箭頭函式中的 this是它所在的執行環境中的 thismdn 寫的是封閉的詞法環境),當你遇到箭頭函式中的 this 不確定的時候,你可以想象把這個箭頭函式換成 console.log(this),這個 console 的輸出就是箭頭函式中 this 的值,並且箭頭函式的 this 是繫結的,不會改變(有時候看上去改變了是所在的 context 改變了)。還有一點需要注意的是,用 callapplybind 來呼叫箭頭函式,第一個引數是沒有意義的,也就是無法改變 this,如果仍需要使用,第一個引數應該傳 null。看 mdn 給出的示例。

var globalObject = this;
var foo = (() => this);
console.log(foo() === globalObject); // true

// 接著上面的程式碼
// 作為物件的一個方法呼叫
var obj = {foo: foo};
console.log(obj.foo() === globalObject); // true

// 嘗試使用call來設定this
console.log(foo.call(obj) === globalObject); // true

// 嘗試使用bind來設定this
foo = foo.bind(obj);
console.log(foo() === globalObject); // true

// 建立一個含有bar方法的obj物件,
// bar返回一個函式,
// 這個函式返回this,
// 這個返回的函式是以箭頭函式建立的,
// 所以它的this被永久繫結到了它外層函式的this。
// bar的值可以在呼叫中設定,這反過來又設定了返回函式的值。
var obj = {
 bar: function() {
 var x = (() => this);
 return x;
 }
};

// 作為obj物件的一個方法來呼叫bar,把它的this繫結到obj。
// 將返回的函式的引用賦值給fn。
var fn = obj.bar();

// 直接呼叫fn而不設定this,
// 通常(即不使用箭頭函式的情況)預設為全域性物件
// 若在嚴格模式則為undefined
console.log(fn() === obj); // true

// 但是注意,如果你只是引用obj的方法,
// 而沒有呼叫它
var fn2 = obj.bar;
// 那麼呼叫箭頭函式後,this指向window,因為它從 bar 繼承了this。
console.log(fn2()() == window); // true

其他情況

還有一些情況我覺得比較簡單,就一筆帶過。
1. 當函式被用作事件處理函式時,它的 this 指向觸發事件的元素。
2. 當代碼被內聯 on-event 處理函式呼叫時,它的this指向監聽器所在的 DOM 元素,需要注意的是隻有最外層的 this 是這樣,如果裡面還有巢狀函式,則巢狀函式的 this 在非嚴格模式下仍然指向全域性物件。
3. 建構函式中的 this 請看之前的文章JavaScript中new操作符的解析和實現
4. bindcallapply 都一樣,函式的 this 被繫結到第一個引數上。

總結

以上就是我所總結的 JS 中的 this 的一些要點,如果有什麼遺漏或者錯誤的地方,歡迎指正。

到此這篇關於我所理解的JavaScript中的this指向的文章就介紹到這了,更多相關JavaScript this指向內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!