這系列的主題其實是節錄自去年 (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


在下篇文章當中,我們會來繼續深入介紹 functionthis 的關係,也會談到 JavaScript 的 call()apply()bind() 的作用。以及在不同應用面,我們要如何來判斷目前的 this 是誰? 又要如何強制指定 this 是誰。