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 是誰。



