What's THIS in JavaScript ? [上]
這系列的主題其實是節錄自去年 (2016) 我在五倍紅寶石開設的課程,講的是 「This」 在 JavaScript 這門程式語言裡所代表的各種面貌。 然而最近無論是社群還是課堂教學,發現仍有不少剛入門的朋友對 JavaScript 的 This
代表的意義不太熟悉,那麼我想整理出這幾篇文章也許可以釐清你對 This
的誤解,反正資料也都還在,不如就整理出來與大家分享順便做個紀錄。
系列文快速連結:
What's this?
也許你在其他物件導向的程式語言曾經看過 this,也知道它會指向某個建構子 (constructor) 所建立的物件。 但在 JavaScript 裡,this 所代表的不僅僅是那個被建立的物件。
先來看看 ECMAScript 標準規範 對 this 的定義:
「The this keyword evaluates to the value of the ThisBinding of the current execution context.」
「this 這個關鍵字代表的值為目前執行環境的 ThisBinding。」
然後來看看 MDN 對 this 的定義:
「In most cases, the value of this is determined by how a function is called.」
「在大多數的情況下,this 會因為 function 的呼叫方式而有所不同。」
好,如果上面兩行就看得懂的話那麼就不用再往下看了,恭喜你。
...... 我想應該不會,至少我光看這兩行還是不懂。 所以,this 到底是什麼?
This 是什麼?
this
是 JavaScript 的一個關鍵字。this
是 function 執行時,自動生成的一個內部物件。- 隨著 function 執行場合的不同,
this
所指向的值,也會有所不同。 - 在大多數的情況下,
this
代表的就是呼叫 function 的物件 (Owner Object of the function)。
好,先來個例子吧,從大家最熟悉的物件講起:
var getGender = function(){
return people1.gender;
};
var people1 = {
gender: 'female',
getGender: getGender
};
var people2 = {
gender: 'male',
getGender: getGender
};
console.log( people1.getGender() );
console.log( people2.getGender() );
來,猜猜 console 後的結果是什麼?
.
.
.
猜你妹啊,都寫死了 return people1.gender;
,當然是 'female' 。
那麼,來換一個:
var getGender = function(){
return this.gender;
};
var people1 = {
gender: 'female',
getGender: getGender
};
var people2 = {
gender: 'male',
getGender: getGender
};
console.log( people1.getGender() );
console.log( people2.getGender() );
這個時候,你應該會得到 「female」 與 「male」 兩種結果。
所以回到前面講的重點,從這個例子可以看出,即便 people1 與 people2 的 getGender method 參照的都是同一個 getGender function,但由於呼叫的物件不同,所以執行的結果也會不同。
people1 的 this.gender 指的是 people1 的 gender 屬性 ('female'), 而 people2 的 this.gender 指的是 people2 的 gender 屬性 ('male')。
雖然上面聽起來很像廢話,但現在我們知道了 this 會因執行的環境與上下文 (context) 的不同,而有不同的結果。
this 不等於 function
上面講過, this 代表的是 function 執行時所屬的物件。 而在 JavaScript 這個語言內,除了基本型別外的一切都是「物件」,那麼當 function 本身就是物件時,又如何呢?
一樣,來看個簡單的範例:
var foo = function() {
this.count++;
};
foo.count = 0;
for( var i = 0; i < 5; i++ ) {
foo();
}
猜猜看,當這段程式碼執行後, foo.count
會是多少?
.
.
.
.
.
.
.
.
.
.
.
.
答案是 0 。 我知道你可能不能接受,來聽我解釋。
前面講過,this 代表的是什麼? 「this 代表的是 function 執行時所屬的物件」對吧?
在上面這個範例當中, foo 是 function,也是「全域變數」。 而全域變數在 JavaScript 當中,其實就是全域物件的屬性。 全域物件在瀏覽器是 window,在 node 內叫做 global。
換言之,在呼叫 foo()
的時候,實際上 foo 所屬的物件就是 window
。
所以說,當 foo()
在 for 迴圈裡面跑得很開心的時候, this.count++
始終都是對 windoo.count
在做遞增的處理。
而 windoo.count
理論上一開始會是 undefined
,在做了五次的 ++
之後,你會得到一個 NaN 的結果,而 foo.count
依然是個 0 。
記住,this 代表的是 function 執行時所屬的物件,而不是 function 本身。
再來一個範例:
var bar = function() {
console.log( this.a );
};
var foo = function() {
var a = 123;
this.bar();
};
foo();
相信經過前一個例題後,聰明的你應該知道 foo();
的執行結果應該是 undefined
了!
在這個範例中, foo()
可以透過 this.bar
取得 bar()
,是因為 this.bar
實際上是指向 window.bar
,而 bar()
的 this.a
並非是 foo
中的 123
,而是指向 window 的 a,所以會得到 undefined
的結果。
再來看下個範例。
var foo = 'foo';
var obj = {
foo: 'foo in Object'
};
var sayFoo = function() {
console.log( this.foo );
};
obj.sayFoo = sayFoo;
obj.sayFoo(); // ?
sayFoo(); // ?
如果你跟著這篇文章看到這裡,應該不難判斷 obj.sayFoo()
與 sayFoo()
執行後的結果分別是什麼,這裡我就保留解答。
想知道結果的朋友丟到 console 裡跑跑看就知道結果囉。
巢狀迴圈中的 this
在上篇的最後,來講一下很多人容易踩中的誤區,看範例:
var obj = {
func1: function(){
console.log( this === obj );
var func2 = function(){
// 這裡的 this 跟上層不同!
console.log( this === obj );
};
func2();
}
};
obj.func1();
在這個範例當中,會有兩次的 console。
首先可以看到在 func1
裡面的 console.log( this === obj )
(第四行) 會回應 true
。
這個應該沒有問題,在本篇文章的前面已經介紹過,當 function 是某個物件的 method 時,他的 this 會指向物件本身。
但是,到了 func2
的時候,一樣的 console.log( this === obj )
(第八行),卻是回應 false
。
這裡必須說明兩個重點:
- JavaScript 中,用來切分變數的最小作用範圍 (scope),也就是我們說的有效範圍的單位,就是 function。
- 當沒有特定指明 this 的情況下,預設綁定 (Default Binding)
this
為 「全域物件」,也就是 window。
換言之,在 func2
裡頭的 this,若是沒有特別透過 call()
、 apply()
或是 bind()
來指定的話,那麼這裡的 this
就是 window
。
但仍有例外,在 ES5 的嚴格模式下,會禁止 this 自動指定為全域物件,像這樣:
var obj = {
func1: function(){
"use strict";
console.log( this === obj );
var func2 = function(){
// 宣告成嚴格模式後,這裡的 this 會變成 undefined。
console.log( this );
};
func2();
}
};
obj.func1();
所以我說那個 this 到底是什麼又不是什麼
看到這裡,你應該要有的基本觀念:
- this 不是 function 本身
- this 也不是 function 的 scope
- this 與 function 在何處被宣告完全無關,而是取決於 function 被呼叫的方式
- this 指的是,當 function 執行時,這個 scope 的 owner
- 當 function 是某個物件的 method,this 指的就是上層物件
- 全域變數的上層物件就是 window
在下篇文章當中,我們會來繼續深入介紹 function
與 this
的關係,也會談到 JavaScript 的 call()
、 apply()
與 bind()
的作用。以及在不同應用面,我們要如何來判斷目前的 this 是誰? 又要如何強制指定 this 是誰。