本章探討函式呼叫的核心機制,包括兩個隱式參數 arguments 與 this、四種函式呼叫方式,以及解決 function context 問題的技巧。
使用隱式函式參數#
函式被呼叫時,除了明確定義的參數外,還會隱式傳入兩個參數:arguments 與 this。它們不會出現在函式簽名中,但可以在函式內部像一般參數一樣存取。
arguments 參數#
arguments是一個包含所有傳入引數的類陣列物件(array-like),擁有length屬性,可透過索引存取個別引數- 它不是真正的 JavaScript 陣列,無法使用
sort等陣列方法 - 主要用途:存取所有傳入的引數,無論是否有對應的具名參數,可用來實作函式多載(function overloading)或接受任意數量引數的函式
function sum() {
var sum = 0;
for (var i = 0; i < arguments.length; i++) {
sum += arguments[i];
}
return sum;
}
assert(sum(1, 2, 3) === 6, "We can add three numbers");arguments 與參數的別名關係(aliasing):
- 在非嚴格模式下,
arguments物件與具名參數互為別名 – 修改arguments[0]會同時改變對應的具名參數,反之亦然 - 在 strict mode 下,這種別名關係被禁用,
arguments與具名參數各自獨立
ES6 的
rest參數是真正的陣列,在許多場景下可取代arguments物件。但理解arguments仍然重要,因為在維護舊程式碼時會經常遇到。
this 參數:函式上下文#
this參數代表函式上下文(function context),指向與該次函式呼叫相關聯的物件- 在 Java 或 C# 中,
this由函式定義的位置決定;但在 JavaScript 中,this的值很大程度取決於函式被呼叫的方式
this不僅由函式定義的方式和位置決定,也會受到呼叫方式的強烈影響。這是物件導向 JavaScript 的核心概念之一。
函式呼叫的四種方式#
JavaScript 中函式可以透過四種方式呼叫,每種方式對 this 的影響各不相同:
作為函式呼叫(as a function)#
- 使用
()運算子直接呼叫,且表達式不是物件的屬性參照 - 非嚴格模式:
this指向全域物件(window) - 嚴格模式:
this為undefined
function ninja() {
return this;
}
function samurai() {
"use strict";
return this;
}
assert(ninja() === window); // 非嚴格模式
assert(samurai() === undefined); // 嚴格模式
作為方法呼叫(as a method)#
- 函式被指定為物件的屬性,並透過該屬性呼叫時,
this指向該物件 - 同一個函式可以被不同物件引用,
this會隨著呼叫的物件而改變
function whatsMyContext() {
return this;
}
var ninja1 = { getMyThis: whatsMyContext };
var ninja2 = { getMyThis: whatsMyContext };
assert(ninja1.getMyThis() === ninja1); // this 是 ninja1
assert(ninja2.getMyThis() === ninja2); // this 是 ninja2
以方法呼叫函式是撰寫物件導向 JavaScript 的關鍵,讓你可以在方法內透過
this引用該方法「所屬」的物件。

Figure 4.2: 當函式作為方法呼叫時,this 指向該方法所屬的物件
作為建構函式呼叫(as a constructor)#
使用 new 關鍵字呼叫函式時,會觸發以下特殊行為:
- 建立一個新的空物件
- 該物件作為
this傳入建構函式(成為函式上下文) - 新建立的物件作為
new運算子的回傳值
建構函式的回傳值規則:
- 若建構函式回傳一個物件,該物件會取代
new產生的物件成為回傳值 - 若回傳非物件值(如數字、字串),回傳值會被忽略,仍回傳新建立的物件
function Ninja() {
this.skulk = function () {
return this;
};
}
var ninja1 = new Ninja();
assert(ninja1.skulk() === ninja1); // this 指向新建立的物件
建構函式通常以大寫字母開頭的名詞命名(如
Ninja、Emperor),而一般函式和方法以小寫字母開頭的動詞命名(如skulk、creep)。如果不小心以一般方式呼叫建構函式(忘了new),在非嚴格模式下屬性會被加到window上,在嚴格模式下則會崩潰。
使用 apply 和 call 方法呼叫#
apply和call是所有函式都有的方法,讓你能明確指定任意物件作為函式上下文apply(context, argsArray):第二個參數是引數陣列call(context, arg1, arg2, ...):引數逐一列出
function juggle() {
var result = 0;
for (var n = 0; n < arguments.length; n++) {
result += arguments[n];
}
this.result = result;
}
var ninja1 = {};
juggle.apply(ninja1, [1, 2, 3, 4]); // ninja1.result === 10
juggle.call(ninja1, 5, 6, 7, 8); // ninja1.result === 26
如何選擇 apply 和 call:
- 引數已經在陣列中 -> 用
apply - 引數是獨立的值 -> 用
call - 兩者功能相同,差別僅在引數的傳遞方式
在 callback 中強制設定函式上下文的實用範例:自行實作 forEach 函式,使用 call 將每個迭代項目設為 callback 的函式上下文。

Figure 4.5: 使用 call 和 apply 可以手動指定函式的 this 上下文
修正函式上下文的問題#
在 callback 函式(如事件處理器)中,this 可能不如預期。例如按鈕的 click handler 中,this 會指向觸發事件的 HTML 元素,而非建立 handler 的物件。
使用箭頭函式(Arrow Functions)#
- 箭頭函式沒有自己的
this,它會記住定義時所在環境的this值 - 在建構函式內使用箭頭函式作為 callback,
this會正確指向新建立的物件
function Button() {
this.clicked = false;
this.click = () => {
this.clicked = true;
assert(button.clicked, "The button has been clicked");
};
}
Figure 4.6: 箭頭函式沒有自己的 this 上下文,而是繼承定義時的外圍上下文
箭頭函式與物件字面值的陷阱: 若箭頭函式定義在全域程式碼的物件字面值中,
this會是全域的window物件,而非該物件字面值。因為箭頭函式在建立時擷取this,而物件字面值不會產生新的函式上下文。

Figure 4.7: 在物件字面量中定義的箭頭函式,其 this 指向全域物件
使用 bind 方法#
bind方法可為任何函式建立一個新函式,其this永遠綁定到指定的物件,不受呼叫方式影響bind不會修改原始函式,而是建立一個全新的函式
var boundFunction = button.click.bind(button);
elem.addEventListener("click", boundFunction);
// boundFunction 的 this 永遠是 button
assert(boundFunction !== button.click); // 是全新的函式
本章重點整理#
- 函式呼叫時隱式傳入
arguments(所有引數的集合)和this(函式上下文) - 函式有四種呼叫方式,每種對
this的影響不同:- 作為函式:非嚴格模式下
this是window,嚴格模式下是undefined - 作為方法:
this是呼叫方法的物件 - 作為建構函式:
this是新建立的物件 - 透過
call/apply:this是第一個引數指定的物件
- 作為函式:非嚴格模式下
- 箭頭函式沒有自己的
this,繼承定義時的this值 bind方法建立一個this永遠綁定到指定物件的新函式