第 361 篇 · 第 14 卷 前端,沒有極限 2026 年 6 月 9 日 · 台北
ls -lt ./posts --since=2013 REC · node 361 · uplink

javascript

JavaScript This 系列文:this 為什麼指向 window

2019 年 03 月 21 日 · 5 分鐘閱讀 · By Wang Casper

本篇是接續先前的:鐵人賽:JavaScript 的 this 到底是誰?,會更深入的探討 JavaScript This 的運作及觀念,這一系列的文章順序上不會從基礎開始介紹,會建議先從先前文章開始閱讀。

純粹的調用 (Simple call)

直接呼叫函式的情況下,this 會指向全域,在以下範例中可以取到 name 的值,但一般來說很不推薦這樣的寫法,後面會提到這樣的寫法是怎麼造成的。

var name = '小明';
function callSomeone() {
  console.log(this.name)
};
callSomeone(); // 小明

IIFE、Callback、閉包也屬於此類別嗎?

上述的函式只有單一層級,為了加深大家的印象,我們透過各種改變 “函式” 作用域的方式看是否會改變 this 的結果。

IIFE

立即函式,這算是直接在函式內直接在呼叫另一個函式,而這個結果下一樣與純粹的呼叫結果相同。

var name = '小明';
(function() {
  function callSomeone() {
    console.log(this.name)
  };
  callSomeone(); // 小明
})();

無論我們把函式宣告放在 IIFE 內與外結果都是一致的。

var name = '小明';
function callSomeone() {
  console.log(this.name)
}
(function() {
  callSomeone(); // 小明
})();

Closure

這樣結果依然相同,並不會因為獨立的作用域改變造成 this 的不同。

var name = '小明'
function easyCard(base = 100) {
  var money = base
  return function(update = 10) {
    money = money + update
    console.log(this.name, money)
  }
}

var MingEasyCard = easyCard(100)
MingEasyCard()
// '小明' 110

Callback function

callback function 也是一樣的結果,透過以上這些範例了解到,並不會因為作用域的改變導致 This 的不同,更重要的還是在於函式是不是與物件有扯上關係,至此為止我們實驗了各種函式呼叫方式,都可以知道函式再直接呼叫的情況下,this 都是指向 window。

function myEasyCard(callback) {
  var money = 100
  return callback(money); // window
}

myEasyCard(function (money) {
  console.log(this, money); // window
})

而 callback function 會提到另一個常見的案例,就是陣列相關的處理方式,參考 MDN 的說明 forEach 的結構如下:

arr.forEach(function callback(currentValue[, index[, array]]) {
    //your iterator
}[, thisArg]);

因此 forEach 中間的 callback 一樣是屬於直接呼叫,所以會 this 會指向 window 物件。

var a = [1, 2, 3];
a.forEach(function(item) {
  console.log(this, item)
});

嚴謹模式

觀念說明開始,為什麼 this 會指向 window 物件

這個階段我們加上 'use strict' 則會進入嚴謹模式,接下來直接呼叫的內容都會變成 undefined的奇特景象,可以理解這是為了避免不必要的錯誤提示,在此也建議別透過一般函式呼叫的 this 取用 window

直接套上嚴謹模式則會出現錯誤範例:

'use strict'
var name = '小明';
function callSomeone() {
  console.log(this); // 這裡改成了 this,因為沒辦法找到 name
}
callSomeone(); // undefined

而 undefined 與 window 有什麼關係呢?我們可以先介紹另一個觀念 call()call 是可將物件傳入並替代函式內的 this:

var Ming = {
  name: '小明'
}
function callSomeone(num) {
  console.log(this.name, num);
}
callSomeone.call(Ming, 2); // 小明, 2

透過 .call(Ming, 2) 的方式,可將 Ming 傳入並取代 this,後面帶上一個參數 2 則是函式的參數。因此 .call() 前者為套用 this 的物件,後者以後都是函式的參數。參考下圖:函式中的 this 為 call() 的第一個值,而函式參數為第二個(包含後續的值)。

接下來我們將 Ming 物件替換,直接定義一個 name 在全域之上,並且傳入 undefined 的值來替代 this,猜猜會發生什麼事!?

var name = '全域魔王' 

function callSomeone(num) {
  console.log(this.name, num);
}
callSomeone.call(undefined, 2);

此時會發現運作上依然沒有問題,傳入 undefined 進入後 this 會直接指向全域

這麼神奇的狀態也算是 JavaScript 的 Feature,發生的理由可以參考:MDN 的說明。

MDN:若這個函數是在非嚴苛模式( non-strict mode ),null、undefined將會被置換成全域變數。

因此,上述程式碼在嚴謹模式下將無法正確運行,因為傳入 undefined 時將會正確以 undefined 作運行。同樣概念下,在一般全域環境套用 ‘use strict’ this 作為一般調用時,將會直接套用 undefined 而不會是 window。

而上述程式碼於嚴謹模式下,將會正確的傳入 undefined。

'use strict'
var name = '全域魔王' 

function callSomeone(num) {
  console.log(this, num);
}
callSomeone.call(undefined, 2);

接下來,可以將本文的上方的程式碼都加上 'use strict' 都會得到相同的結果,this 都會指向 undefined。


延伸閱讀:This 都是物件

前一篇介紹到 「this 大部分取決於 它在哪個物件下被呼叫」,所以其中改變 this 的方法之一則是透過物件下呼叫函式:

function callName() {
  console.log(this.name);
}

var auntie = {
  name: '漂亮阿姨',
  callName: callName  
  // 這裡的 function 指向全域的 callName function
}

auntie.callName() // '漂亮阿姨',呼叫是在物件下調用,那麼 this 則是該物件

除此之外,還可以透過本文所介紹的 call 來改變 this 的值,先前範例都是傳入「物件」,此部分我們傳入數值來看看其型別:

function callSomeone(num) {
  console.log(typeof(this), typeof(num));
}
callSomeone.call(1, 2);
// object number

此部分的 this 我們使用數字 1,函式參數使用數字 2,結果會發生 this 的型別變成了 object!?

重新透過 console 查看 this 會得到以下的結果,這是透過建構式產生的數值(new Number(1)),透過 call 所傳入的純值會被使用 new 建構式的方式產生,所以型別依然維持是「物件」(可參考:MDN),因此 this 終究為物件形式。

MDN:原生型態的值將會被封裝