這篇教學會介紹 JavaScript 中 apply() 和 call() 的使用方法,以及運用到 apply() 和 call() 的應用,包括 Call Forwarding、Cache 和 Method Borrowing。
目錄
JavaScript apply()
JavaScript 中 apply()
是 Function 物件的一個方法,用法是:
someFunction.apply(context, arguments)
其中 context
是呼叫這個 function 所使用的 this
值,而 arguments
則是呼叫這個 function 時的參數。
舉個例子,一般來說我們要呼叫一個 function 時,我們會這麼寫:
someFunction(1, 2, 3);
其實我們也可以用 apply()
達成同樣的效果,用法如下:
someFunction.apply(undefined, [1, 2, 3]);
JS apply() 的 context 參數
那當我們需要指定 function 的 this
時怎麼辦呢?舉例來說,當我們呼叫一個物件的方法時,this
會等於物件本身:
obj.someFunction(1, 2, 3) // this === obj
這個時候我們就需要指定 apply()
的 context
參數。用法如下:
const someFunction = obj.someFunction;
someFunction.apply(obj, [1, 2, 3]);
此時 someFunction 的 this
就是 obj
。
JavaScript call()
JavaScript 中 call()
是 Function 物件的另一個方法,用法是:
someFunction.call(context, arg1, arg2, ...)
其中 context
是呼叫這個 function 所使用的 this
值,而 arg1
, arg2
, … 則是呼叫這個 function 時的參數。
call 和 apply 十分類似,唯一的差別在於呼叫 apply 時的參數是一個陣列,而 call 的參數則是分開的。
舉個例子,一般來說我們要呼叫一個 function 時,我們會這麼寫:
someFunction(1, 2, 3);
我們可以用 call()
達成同樣的效果,用法如下:
someFunction.call(undefined, 1, 2, 3)
JS call() 的 context 參數
那當我們需要指定 function 的 this
時怎麼辦呢?舉例來說,當我們呼叫一個物件的方法時,this
會等於物件本身:
obj.someFunction(1, 2, 3)
這個時候我們就需要指定 call()
的 context
參數。用法如下:
const someFunction = obj.someFunction;
someFunction.call(obj, 1, 2, 3);
JS apply 和 call 的應用 #1: Call Forwarding
有時候,我們想要在一個函式原本的功能之上附加額外的功能,這個時候可以使用 Call Forwarding 的技巧。
Call Forwarding 簡單來說就是:回傳一個 wrapper function,這個 wrapper function 會把原本的 function 包起來,加上自己想要的邏輯後呼叫原本的 function。
基本的使用方法如下:
function wrapper(func) {
const wrappedFunction = function() {
const result = func.apply(this, arguments)
// Some custom logic...
return result
}
return wrappedFunction
}
const wrappedFoo = wrapper(foo)
wrappedFoo(bar, baz)
wrapper
函式的參數是一個函式func
,回傳值是另一個函式 wrappedFunction
。
注意在 wrappedFunction 裡面,會呼叫 func
,核心是用到 apply()
的這一行:func.apply(this, arguments)
。注意:我們會用 wrapper
函式的 this
和 arguments
當作參數傳給 func
。
這很重要,因為唯有當 this
和 arguments
都被考慮進去,呼叫 func
的結果才會和原本一模一樣。
結論:當 wrappedFunction
被呼叫的時候,會呼叫原本 func
並且加上你想要的邏輯,最後回傳 func
被執行的結果。
JS function 中特別的 arguments 變數
arguments
是一個函式內部特別的變數,對應到函式被呼叫時使用的參數。
舉例來說,以下例子會印出參數 [1, 2, 3]
。
function printArgs() {
console.log(arguments)
}
printArgs(1, 2, 3) // [1, 2, 3]
printArgs(1, 2, 3, 4) // [1, 2, 3, 4]
當我們呼叫一個 function 的時候,可能事先不知道呼叫者會傳幾個參數,這時候 arguments
就很好用了。
arguments
變數的另外一個替代方案是用 spread operator 取出參數 array:
function printArgs(...args) {
console.log(args)
}
printArgs(1, 2, 3) // [1, 2, 3]
printArgs(1, 2, 3, 4) // [1, 2, 3, 4]
apply 和 call 的應用 #2: Cache
假設有個計算量非常費時的函式:
function slow(x) {
// Some CPU heavy task
return x
}
我們希望加上cache的功能,如果用相同的參數去呼叫這個函式第二次的話,就直接回傳上一次計算過的結果:
function cachingDecorator(func) {
let cache = new Map() // The cache
return function() {
const key = hash(arguments) // Some hash function
if (cache.has(key)) {
return cache.get(key) // Retrieve value from the cache
}
const result = func.apply(this, arguments) // Compute result
cache.set(key, result) // Save value to the cache
return result
}
}
slow = cachingDecorator(slow)
slow(1)
slow(1) // Cached!
slow(1) // Cached!
apply 和 call 的應用 #3: Method Borrowing
上例中的 hash()
的實作需要注意。
以下寫法有問題,因為 arguments
不是一個array,沒有 join
方法:
function hash() {
return arguments.join(',') // Not working!
}
但是利用 call()
,就可以借用 Array.prototype.join
方法:
function hash() {
return [].join.call(arguments, ',')
}