JavaScript 是需要被瞭解
前一篇,我們針對了物件, 數字, 字串, 陣列, 迴圈提供了一些小知識,續前篇,我們再談一些關於函式的小知識。
函式小知識
函式(function)可說是 JavaScript 裡頑皮又最有趣的…(函式)物件。還記的「萬物皆物件,萬物皆可放」這兩句話嗎。
具名函式
function add(x, y) { var total = x + y; return x + y; }
這是一個非常完整的函式,正式名稱叫具名函式,但一般我們簡稱的函式就是具名函式:
- 名稱,name
- 參數,x 與 y
- 區域變數,total
- 運算,x + y
- 回傳值,return
目前為止都是中規中舉,函式是拿來呼叫的,所以我們來不正常呼叫吧!
// 傳入兩個 undefined // undefined + undefined = NaN (就不是整數呀) document.writeln(add()); // NaN document.writeln(add(5, 9, 999)); // 14,函式忽略第三個參數
第二個才是好玩的地方,JavaScript 對於多傳入的參數給省略了,但其實不是,因為我們可以在函式之內存取一個 arguments 的變數,arguments 內有傳遞給函式的所有值。這與一般程式語言的用法不同,一般程式語言的函式,你定義幾個參數就是能傳入幾個參數,頂多是依參數的不同設計為重載,但你能傳遞的參數個數還是被函式所限制,JavaScript 透過 arguments 變數的幫忙,可以把所有函式設計成接受無限的傳入值。讓我們改寫原始函式:
function avg() { var sum = 0; // 透過 arguments.length 取得要加總的次數 for (var i = 0, total = arguments.length; i < total; i++) { sum += arguments[i]; } return sum / total; // 平均值 } document.writeln(avg(1,3,5,7,9,2,4,6,8,10)); // 5.5
用起來好像很不錯,但 arguments 變數有一個問題,就是它只接受用 ,(逗點)分隔的參數值,所以如果我們還想要處理來自陣列的值,我們就要再寫一個函式來處理陣列:
// arr 為陣列 function avgArray(arr) { var sum = 0; // 透過 arr.length 取的要加總的次數 for (var i = 0, total = arr.length; i < total; i++) { sum += arr[i]; } return sum / total; } var arr = [2,4,6,8,10,1,3,5,7,9]; document.writeln(avgArray(arr)); // 5.5
明明是一樣的事,為什麼要做兩分工呢?還好,函式也是物件,要讓 arguments 變數也可以接受陣列值,最要一點小技巧,我們必須使用到一個函式物件所提供的方法 apply()。
// Function.apply(thisArg, arrayArg); // thisArg, 物件內部的 this 會等於 thisArg; // arrayArg,傳入一個陣列的參數 var arr = [2,4,6,8,10,1,3,5,7,9]; document.writeln(avg.apply(null, arr)); // 5.5
這邊我們先關注在 ArrayArg 這個參數就好,這行 avg.apply(null, arr);
翻譯成白話就是「執行 avg 函式物件,而且幫傳入二個參數,一個給 this 使用,另一個是陣列的參數」,經過如此改良之後,arguments 變數就看得懂傳入陣列是參數。或許這樣想,經過 Function.apply()
的幫忙,它把我們要傳入的陣列 [2,4,6,8,10,1,3,5,7,9] 變成 2,4,6,8,10,1,3,5,7,9 (去除陣列實字符號[])然後傳入。
還有一個函式物件方法為 call(),用法上與 apply() 相似,差異只有第二個陣列參數,我們必須一個一個傳入,例如:avg.call(null, 1,3,5,7,9,2,4,6,8,10)
。
匿名函式
匿名函式,人如其名,也就是沒有名稱的函式,它是函式中非常強大的武器,有點可以為所欲為,要小心服用。我們來看例子:
var avg = function() { var sum = 0; // 透過 arguments.length 取得要加總的次數 for (var i = 0, total = arguments.length; i < total; i++) { sum += arguments[i]; } return sum / total; // 平均值 } document.writeln(avg.apply(null, arr)); // 5.5
把原先的 function avg() {}
的名稱改使用為 var avg = function() {}
,此語法就運作的意義上是相同,但此技巧帶來的效果非常強大,我自可以在任何原本放表達式的地方換成放入一個完整的函式定義。
var a = 1; var b = 2; // 1. 最外( )包匿名函式 (function() { var b = 3; // 此 b 是匿名函式的區域變數 a += b; // 這裡改變了全域變數 a })(); // 2. 這裡的 () 呼叫自己執行一次 document.writeln(a); // 4 document.writeln(b); // 2
匿名函式裡的 b 被保護起來了。
變數的範圍
這裡先我們跳離開函式,來討論一個在 JavaScript 裡極為重要的內容。在 JavaScript 裡變數是沒有範圍(scope,或稱影響範圍,作用範圍)的觀念,範圍也就是常說的【全域變數】與【區域變數】,範圍控制變數與參數的可見性與生命週期。在 JavaScript 裡任何地方宣告的變數的範圍都是【全域變數】,好玩吧!但在上面的小小範例中,我們說明了一個極為重要的內容,在函式內宣告的變數為【區域變數】,這是 JavaScript 裡唯一範圍是非全域,會產生區域變數的方法。(另一種 Closure 也是函式的變型,簡言之,函式是唯一產生區域變數的方法)。我們看個簡單的例子,
// 計算平方 function square(num) { total = num * num; return total; } var total = 50; var number = square(20); console.log(total); // 這裡的 total 是多少
number = 20 * 20 = 400,這裡沒問題,重點在最後一行的 total 會是多少?答案也是 400,為什麼呢?前面不是剛說「在函式內宣告的變數為【區域變數】」,我沒教錯。JavaScript 給我們帶來很多方便性,它是一個很隨性的語言,變數使用前要宣告不宣告都可以,問題在於未宣告的變數都以【全域變數】來處理,也就造成函式內部的 total 是以全域變數運作,它覆寫了原始 var total 這個宣告的值。我們整理一下:
- 非 function 宣告(var)都是作用於 global
- function 內宣告(var)都是作用於 local
- 小心 function 內的 global 會直接影響外部 global
正確的算式應該為:
function square(num) { var total = num * num; return total; }
減少全域變數
減少全域變數的使用,可以確保 JavaScript 的程式品質,和提升除錯的容易度。減少全域變數的其中一種方式,幫你的 JavaScript 應用程式建立唯一一個全域變數的物件集合。
// 透過一個物件來整理整個應用程式的全域變數 var BruceApps = {}; // 個人資料相關全域變數 BruceApps.person = { "first-name": "Bruce", "last-name": "Chen", "nickname" : "KKBruce", "age": 18, "live": "Taiwan", "work": "Developer" }; // 個人社交相關全域變數 BruceApps.social = { "www": "http:\/\/www.kkbruce.net", "blog": "http:\/\/blog.kkbruce.net", "plurk": "http:\/\/www.plurk.com\/kk_bruce\/invite", "Google+專頁": "http:\/\/gplus.to/kkbruce", "twitter": "http:\/\/twitter.com\/kkbruce", "weibo": "http:\/\/weibo.com\/kkbruce" }; // 要取用時 console.log(BruceApps.social.GooglePlus);
還有一種方式是Closure,這裡直接介紹太危險,先把函式摸熟,這樣瞭解Closure時,就很簡單些了。
遞迴呼叫
回到函式身上,JavaScript 的函式是可以遞迴呼叫的,這在處理 DOM 元素時特別有用,但問題來了,我是匿名函式你怎麼呼叫呢?匿名呼叫匿名,這樣嗎?arguments 物件除了提供取得一系列參數外,還提供一個 callee 屬性,此屬性能讓我們指向目前的函式。例如:
function selfFunCounter() { // arguments.callee 指向目前函式 // arguments.callee.count 確認是否有此屬性 if (!arguments.callee.count) { arguments.callee.count = 0; // 沒有,增加設定屬性及值為 0 } return arguments.callee.count += 1; // 每次 +1 }; console.log(selfFunCounter()); // 1 console.log(selfFunCounter()); // 2 console.log(selfFunCounter()); // 3
沒有留言:
張貼留言
感謝您的留言,如果我的文章你喜歡或對你有幫助,按個「讚」或「分享」它,我會很高興的。