Javascript Function

Posted by Noel on 2014-08-24

     Javascript(以下簡稱JS)的函式比以往所認識語言C, JAVA, PHP來得特別些。因為第一份工作主要都是在寫Rails所以接觸Ruby,覺得這語言很酷很方便,然後也因為今年開始自學JQuery也順便想弄懂Javascript的原理而開始接觸,之前有朋友說其實Ruby有些地方是從Javascript借鏡的,當時因為對JS還很不熟,所以也沒體會,但隨著看的範例多跟練習越來越多,也開始有所感觸,所以也趁著這機會記錄一下JS相關的function應用:
一般使用
1
2
3
4
5
6
7
function godzilla(food){
console.log('I eat the '+food);
console.log('I am a monster!');
}
// godzilla('fish');
//I eat the fish
//I am a monster!

     這是最基本的使用方式,跟其他語言大致相當

匿名Function


     也就是所謂的匿名函式,為什麼稱作匿名呢?那是因為對JS來說function本身可以視為一個物件(它也確實是個物件),而我們把JS的function視為一個可傳遞的值或物件,所以我們也就可以把它傳入一個變數或是當做參數使用:
1
2
3
4
5
6
7
8
9
var godzilla = function(food){
console.log('I eat the '+food);
console.log('I am a monster!');
}
typeof godzilla
// "function"
godzilla('fish');
//I eat the fish
//I am a monster!

     當然,可以塞入一個變數,那我們就可以像是變數一樣的任意使用它,又或著是把它來當做參數給另一個變數做callback使用:

1
2
3
4
5
6
7
8
9
function monstersShow(monster, godzilla){
console.log('I am the '+ monster);
godzilla();
}
monstersShow('KingKong', godzilla);
// I am the KingKong
// I eat the undefined
// I am a monster!


     上述作法其實比較多此一舉,因為既然它是可以是個物件,那其實在當參數使用的時候也不見得需要在把它先塞到一的變數,而是在呼叫該function的時候直接定義欲傳入function,所以我們可以直接寫:
1
2
3
4
5
6
7
monstersShow('KingKong', 
function(food){
console.log('I eat the '+food);
console.log('I am a monster!');
}
);


     直接這樣寫也可以work,而且這種作法在JQuery的event handler相當常見

如果要更謹慎地使用callback,可以這樣:

1
2
3
4
5
6
7
8
9
10
11
12
 function godzilla(food, callback) {
console.log('I want to eat '+ food);
if(typeof(callback) == 'function') { //判斷callback是否為function
console.log('callback is actually a function, then we would execute it..');
callback();
}
}
godzilla('fish',function() { console.log('Dear Doctort, you made it!'); });
// 輸出
// I want to eat fish
// Dear Doctort, you made it!


     更理解了嗎?其實 callback 就只是個參數代稱而已,你想怎麼稱呼它都可以,原理是你只要想像你欲傳遞的 function 全都被指定給callback,然後再以 callback() 來使用傳遞進去的 function 。

Function的參數陣列arugments


     在 JS 中的 function 其實都有個預設好的陣列參數物件(類似 Array)arguments ,我們都知道相對於 C 家族的語言,JS 是很自由的,像是在呼叫的 function 時,即使多傳幾個沒定義的參數,程式也不會噴錯,或是明明設有參數的 function,直接不帶參數呼叫該 function,也是不會噴錯,只是需要用到該參數的地方會顯示 undefined 而已。
     然而,剛剛都只是題外話,JS 的 function 真正酷的地方在那個預設的陣列參數 arugments ,其實他不是真正的參數也不是個真的陣列,我知道這樣講很抽像,但只要把它想成 event 在 event handler 裡的這個事件物件的參數就很好懂了;而這邊這個 arguments 也有個屬性叫做 length ,顧名思義就是你傳入這個 function 的參數長度,而我們傳入的參數其實也可以藉由 arguments[i] 來呼叫。所以也可以把它想做你傳入的參數其實全都存到這個 arguments 陣列裡去了,那我們在定義一個 JS 的 function 時,其實你不用事先定義任何傳入的參數也可以,然後可以直接靠 arguments 來取用你強行塞進去這個 function 裡的參數,但是這樣做不見得是個好方法就是,因為你必須很清楚你傳入的參數位置為何,老是這樣 argument[0], argument[1] 的呼叫似乎有點不易閱讀;直接看個例子吧:
1
2
3
4
5
6
7
8
9
10
11
 
function arrayPlus(){
var total = 0;
for(i=0;i<arguments.length;i++){
total += arguments[i];
}
return total;
}
console.log(arrayPlus(1,2,3,4,5)
// 15


     arguments當然還有其他功用,但這就得自己多去摸索了,如果想更詳細了解arguments也可以去查文件囉!

進階範例:運用Prototype和Callback來自定forEach給Array


     在 JQuery 跟 Ruby 裡都已經預設有 forEach 或 each 等方便給 array 或 hash 使用的方法,簡單地說,此 function 會直接迭代呼叫它的 array 或h ash 內的元素並傳給它本身的 callback 方法,像是`[1,2,3,4,5].forEach(function(element) { console.log(element);} )`,其中傳給 forEach 的匿名 function 正是之前提到的 callback 方法(一般來說就是可當參數傳入使用的方法),而該 function 的參數 element 正是被迭代入的 array 或 hash 元素。
     這個 forEach 等會自動走訪 array 或 hash 的元素並且讓他們執行某 callback 是種直覺右方變得用法,在以前的 JAVA 或 PHP 要做到同樣的事寫起來就比較麻煩,code 可能會多好幾行,而到底這種方便的方法是怎麼被實作入 JS 的勒,用以下的範例來試試看吧!!在之中我們會先用到 prototype 這物件來實現 JS 的繼承功能(把想被繼承的方法或屬性塞入給 prototype 就可以了,更深入了解 prototype 將會在另外討論):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Array.prototype.myEach = function(callback){
for(i=0;i<this.length;i++){
callback(this[i]);
}
};// 這邊是先定義 myEach 並且透過 prototype 指定給 Array,讓之後所有的 Array 都可以用此方法
[1,2,3,4,5].myEach(function(element){
console.log(element);
});
//輸出
//1
//2
//3
//4
//5




Function的其他使用 - 自我調用(Self-Invoking)


     簡單地說就是宣告完 function 後立即執行,而且該 function 只會執行一次,之後無法在用
1
2
3
4
5
6
(function(drink) {
console.log('I like to have a cup of '+ drink);
})('ice tea');
// 輸出
// 'I like to have a cup of ice tea'


     這有什麼用處呢?一般是用來初始化或是只需執行一次的任務,但其實本人自己也沒用過幾次,所以就先學起來吧。
1
2
3
4
5
6
var a = function(drink) {
console.log('I like to hava a cup of '+ drink);
}('ice tea');
// 輸出
// I like to have a cup of ice tea


     這樣寫也 work,但是好像更多此一舉了點就是。

Function的其他使用 - 內部函數


     簡單地說,就是 function 裡面又定義 function,而在內部的 function 就稱為內部或私有 function,不能被直接呼叫,而內部 function 的範圍內的變數也無法被外部 function 所存取使用。另一方面,對於內部 function 來說,則有發生 closure(閉包)的機會,就是內部 function 可以存取外部 function 的閒置變數,拿近來使用,因此延長了該閒置變數的存活期間,詳細的 closure 介紹與使用如果有機會再另外做記錄,這裡先來給個簡單的範例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function outer() {
var outerSpace = 'space godzilla';
console.log(outerSpace + 'is a super monster!');
function inner() {
var innerSpace = 'Just godzilla';
console.log(innerSpace + 'is local monster and ' + outerSpace + 'is not!');
}
inner();
return inner;
}
outer();
// 輸出
// space godzillais a super monster!
// Just godzillais local monster and space godzillais not!
inner();
// ReferenceError: inner is not defined 失敗
var getInner = outer();
// space godzillais a super monster!
// Just godzillais local monster and space godzillais not!
getInner();
// Just godzillais local monster and space godzillais not!
outer()();
// space godzillais a super monster!
// Just godzillais local monster and space godzillais not!
// Just godzillais local monster and space godzillais not! 多出現了一次


     來解釋吧,當第一次直接呼叫 outer()時,結果很直覺的就是跑了它該跑的。那我想直接呼叫 inner 的話勒,則會顯示失敗的錯誤訊息,因為內部 function 不能直接使用。然後接著我再 17 行的地方又把 outer() 的回傳結果傳給變數 getInner ,因為第 9 行 outer 有定義 return Inner ,所以此時 getInner 就代表 Inner 了,所以可以直接使用!!而至於為何會有 18,19 行的關係是因為小弟這隻範例程式寫的不夠好,因為只要呼叫到 outer() ;無論如何都會先執行一次啊,所以就會有那兩行結果。而最後,第22行是直接靠 outer 呼叫來執行 Inner 的結果,但這種作法其實會先執行一次 outer 本身後才跑 Inner,所以第 25 行才會又出現一次。
     其實回顧一下第 6 行的 outerSpace,它是屬於外部 function 的變數,但是仍然可以被內部 function 使用,也因此延長它的存活時間,這算是種 closure 的基本例子。而這 outerSpace 對內部 function 來說我們可以稱作為閒置變數,你也可以覆寫它,然而因為 JS 的 closure 是綁住變數本身而非變數的值,所以一旦覆寫了話,當然也會連動影響到外部 function 的 outerSpace ,範例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function outer() {
var outerSpace = 'space godzilla';
console.log(outerSpace + 'is a super monster!');
function inner() {
var innerSpace = 'Just godzilla';
console.log(innerSpace + 'is local monster and ' + outerSpace + 'is not!');
outerSpace = 'space godzilla is changed from inner!'
}
inner();
console.log(outerSpace);
return inner;
}
outer();
// 輸出
// space godzilla is a super monster!
// Just godzilla is local monster and space godzilla is not!
// space godzilla is changed from inner!


在第7行的地方又覆寫了一次 outSpace 所以最後 outer 輸出的 outerSpace 也跟著被改變了!
### 目前關於 Function 的部份就先寫到這,期待下篇應用再繼續努力,有問題或錯誤的話歡迎指正,感謝~!