在討論函式時,很多人都會把這兩個搞混,我自己也不例外。
雖然講錯別人也聽得懂,但是我們還是要搞清楚這兩個的定義到底是什麼!
看完還是有點不懂?沒關係,上圖!
發現在前幾天介紹宣告函式時沒有特別說明兩者名稱的差異,雖然這跟上面的引數與參數一樣說錯別人也懂,但我們還是要分清楚喔!
宣告式:
function funA(){
...
}
表達式:
var myFun = function(){
...
}
在呼叫函式時候除了傳遞自己給的引數外,你知道還會傳遞兩個隱含參數: arguments 和 this 嗎?
什麼事「隱含」,就是這些參數沒有明確列在函式署名( function signature)中,只會默默傳遞給函式,並且可以在函式內存取他們,也可以在函式中被引用。
我們今天只會先介紹 arguments , this 會在之後的篇幅中提到。
是傳遞給函式的所有參數之集合,不用管相對應的參數是否有明確定義。這也讓我們可以實現 JS 本身不支援的函式重載 (function overloading) 及 可變參數數量的可變函式 (variadic function) 。
講到這邊可能會有人困惑說什麼是函式重載?所謂函式重載就是函式的名稱都一樣,但是會根據不同的輸入擁有不同的輸出。
看不太懂也沒關係,在我們講完 arguments 後會提到怎麼實作。
我們先來看一個簡單的例子:
function show(a,b,c){
console.log(a,b,c);
console.log(arguments);
console.log(arguments[0]);
console.log(arguments.length);
console.log(typeof arguments);
}
show(1,2,3);
可以看到 arguments
儲存了所有傳遞參數的值。 而且還可以用lenght
屬性,但這邊要注意到,它並不是一個陣列,是一個叫做類陣列的東西,也就是說無法使用 JS 提供給 array 的函式庫。
function show(a,b,c){
arguments.sort(function(a,b){
return a - b;
});
}
show(1,2,3);//Uncaught TypeError: arguments.sort is not a function
這邊要注意到一點,如果我們修改了 arguments
的值,原本的參數也會被修改:
function modifyArg(a,b,c){
console.log(a,b,c);//1 2 3
arguments[0] *= 20;
console.log(a,b,c);//20 2 3
}
modifyArg(1,2,3);
在前面有提到過說可以利用 arguments 作可變函式:
function dynamicArg(){
console.log(arguments);
}
dynamicArg(1,2,3);//Arguments(3) [1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]
再來,我們來看一下到底怎麼作函式重載呢:
function mutli(){
let argLength = arguments.length;
if(argLength ===1){
console.log(argLength[0])
}else if(argLength ===2){
console.log(argLength[0],argLength[1])
}
...
}
這是最簡單的函式重載,雖然可以運作但實在看了頭有點痛。
我們今天來假設一個情境,我們想要有一個函式可以對人名進行搜索,根據不同的參數長度回傳搜尋的值,這時候我會就會需要一個 object 物件:
var people = {
name:["Dean Edwards", "Alex Russell", "Dean Tom","Alex Tsai","Tom Cruise","Tom Ford"]
}
我們想要能find('Dean')
時回傳 ["Dean Edwards","Dean Tom"]
;find()
時回傳所有人;find('Tom Cruise')
時回傳 ["Tom Cruise"]
。
於是我們需要一個 函式:
var people = {
name:["Dean Edwards", "Alex Russell", "Dean Tom","Alex Tsai","Tom Cruise","Tom Ford"]
}
function addMethod (obj, key, fn) {
var old = obj[key];
obj[key] = function () {
if (fn.length === arguments.length) {
return fn.apply(this,arguments);
} else if (typeof old === "function") {
return old.apply(arguments);
}
}
}
接著,我們就可以寫搜索人名的函式了:
//沒有參數傳入,回傳所有人
function findAll () {
return people.name;
}
// 傳入一個參數時,回傳firstName相同的人名陣列
function findFirstName (firstName) {
var ret = [];
for (var i = 0; i < people.name.length; i++) {
if (people.name[i].indexOf(firstName) !== -1) {
ret.push(people.name[i]);
}
}
return ret;
}
// 傳入兩個參數時,回傳firstName和lastName都相同的人名陣列
function findFullName (firstName, lastName) {
var ret = [];
for (var i = 0; i < people.name.length; i++) {
if (people.name[i] === (firstName + " " + lastName)) {
ret.push(people.name[i]);
}
}
return ret;
}
寫完之後,將他們經由addMethod()
傳入people
物件裡面:
addMethod(people, "find", findAll);
addMethod(people, "find", findFirstName);
addMethod(people, "find", findFullName);
之後就可以用了:
console.log(people.find()); //["Dean Edwards", "Alex Russell", "Dean Tom", "Alex Tsai", "Tom Cruise", "Tom Ford"]
console.log(people.find("Tom")); //["Tom Cruise", "Tom Ford"]
console.log(people.find("Dean Edwards")); //["Dean Edwards"]
或許有人會很困惑,為什麼我們可以這樣用,讓我們來逐一分析這個addMethod
函式:
function addMethod (obj, key, fn) {
var old = obj[key];
obj[key] = function () {
if (fn.length === arguments.length) {
return fn.apply(this,arguments);
} else if (typeof old === "function") {
return old.apply(arguments);
}
}
}
可以看到我們每次呼叫時,都會先儲存原先的people.find
到變數old
,這時候就會造成閉包的現象,這我們後面篇幅會提到,簡單的說,我們可以藉由people.find
不斷往上找,直到找不到為止。
所以當我們呼叫 people.find("Tom")
時,會執行:
if (fn.length === arguments.length) {
return fn.apply(this,arguments);
}
else if (typeof old === "function") {
return old.apply(this,arguments);
}
這時候的fn
是findFullName
但由於傳入參數長度跟 findFullName
長度不相等,所以會執行:
else if (typeof old === "function") {
return old(arguments);
}
這時候的old
等於people.find("Tom")
,但是裡面包附的fn
已經換成,findFirstName
由於傳入參數長度跟 findFirstName
長度相等,便會直接執行,不會在往上找。
或許會有人認為,我為了寫一個函式重載,還得寫這麼多複雜的函式,不如需要時在呼叫就好了啊!
但是函式重載的好處在於,只需要苦過一次,後面就可以用短短的一行函式就得到想要的結果了!
參考資料:
忍者 JavaScript 開發技巧探秘
JavaScript中的函数重载(Function overloading)