0 前言
全是干貨的技術殿堂
文章收錄在我的 GitHub 倉庫,歡迎Star/fork: JAVA-Interview-Tutorial
https://github.com/Wasabi1234/Java-Interview-Tutorial
this 是函數運行時,在函數體內部自動生成的一個對象,只能在函數體內部使用。
教科書般的解釋,字都認識,怎么連在一起還是不知道啥意思呢?
1 this的值究竟是什么呢?
函數的不同場合,this有不同值。
總的來說,this就是函數運行時所在的環境對象。
1.1 簡單函數調用
函數的最通常用法,屬全局性調用,因此this就代表全局對象。
- 看下面案例
1.2 作為對象方法的調用
函數還可以作為某個對象的方法調用,這時this就指這個上級對象。

記住一條:當function被作為method調用時,this指向調用對象。另外,JavaScript并不是OO的,而是object based的一種語言。
1.3 構造函數
所謂構造函數,就是通過這個函數,可以生成一個新對象。這時,this就指這個新對象。


- 上面兩套代碼等效 可以寫class test,但本質上new test()的時候,還是test構造函數,差不多,class主要是向java之類的語言抄的,可以直接當java的類用,但本質上test還是個構造函數,因為js一開始就沒有class 只能用構造函數,函數test運行時,內部會自動有一個this對象可以使用。
運行結果為1。為了表明這時this不是全局對象,我們對代碼做一些改變:

運行結果為2,表明全局變量x的值根本沒變。
1.4 Apply 調用
apply()是函數的一個方法,作用是改變函數的調用對象。它的第一個參數就表示改變后的調用這個函數的對象。因此,這時this指的就是這第一個參數。

apply()的參數為空時,默認調用全局對象。因此,這時的運行結果為0,證明this指的是全局對象。
如果把最后一行代碼修改為

運行結果就變成了1,證明了這時this代表的是對象obj。
2 深入內存分析
學懂 JavaScript 語言,一個標志就是理解下面兩種寫法,可能有不一樣的結果。

上面代碼中,雖然obj.foo和foo指向同一個函數,但是執行結果可能不一樣。請看下面的例子。

- 對于obj.foo()來說,foo運行在obj環境,所以this指向obj
- 對于foo()來說,foo運行在全局環境,所以this指向全局環境。所以,兩者的運行結果不一樣。
為什么會這樣?函數的運行環境到底是誰決定的?為什么obj.foo()就是在obj環境執行,而一旦var foo = obj.foo,foo()就變成全局環境執行了?
帶著靈魂的思考,我們深入解析下
2.1 內存布局

上面的代碼將一個對象賦值給變量obj.
- JS 引擎會先在內存里面,生成一個對象{ foo: 5 },然后把這個對象的內存地址賦值給變量obj。
變量obj是一個地址(reference)。后面如果要讀取obj.foo,引擎先從obj拿到內存地址,然后再從該地址讀出原始的對象,返回它的foo屬性。
原始的對象以字典結構保存,每一個屬性名都對應一個屬性描述對象。舉例來說,上面例子的foo屬性,實際上是以下面的形式保存的。

{
foo: {
[[value]]: 5
[[writable]]: true
[[enumerable]]: true
[[configurable]]: true
}
}
注意,foo屬性的值保存在屬性描述對象的value屬性里面。
3 函數
這樣的結構是很清晰的,問題在于屬性的值可能是一個函數。

- 引擎會將函數單獨保存在內存中,然后再將函數的地址賦值給foo屬性的value屬性
{
foo: {
[[value]]: 函數的地址
...
}
}
由于函數是一個單獨的值,所以它可以在不同的環境(上下文)執行。

4 環境變量
JavaScript 允許在函數體內部,引用當前環境的其他變量。

上面代碼中,函數體里面使用了變量x。該變量由運行環境提供。
現在問題就來了,由于函數可以在不同的運行環境執行,所以需要有一種機制,能夠在函數體內部獲得當前的運行環境(context)。所以,this就出現了,它的設計目的就是在函數體內部,指代函數當前的運行環境。

上面代碼中,函數體里面的this.x就是指當前運行環境的x。
var f = function () {
console.log(this.x);
}
var x = 1;
var obj = {
f: f,
x: 2,
};
// 單獨執行
f() // 1
// obj 環境執行
obj.f() // 2
上面代碼中,函數f在全局環境執行,this.x指向全局環境的x。

在obj環境執行,this.x指向obj.x。

回到本文開頭提出的問題,obj.foo()是通過obj找到foo,所以就是在obj環境執行。一旦var foo = obj.foo,變量foo就直接指向函數本身,所以foo()就變成在全局環境執行。
參考
- Javascript 的 this 用法
- JavaScript 的 this 原理