函數function在js是非常重要的存在,我們平常所討論的js的”物件導向”,都是在它的基礎上的,可以說我們應該相當的瞭解,它存了太多獨特的地方了。
每個函數都是function類型的執行個體
首先讓我們來理解這句話:每個函數其實是function類型的執行個體。也就是說我們聲明的函數都是對象,有自己的屬性和方法,函數名不過是指向該對象的一個指標。
看下面這個例子:
function myfuc(arg) { alert(arg); } var anfuc = myfuc; myfuc = null; anfuc(1); // 報錯,還是彈出1??
結果是彈出1,myfuc不過是指向我們函數對象的一個指標,它指向null之後,並不影響anfuc的,因此調用無影響。
函數的內部屬性
arguments和this:arguments是一個類數組對象,它包含調用函數時傳入的所有參數,你在函數裡可以用arguments[0]、arguments[1]…來訪問傳入參數;此外,它還有一個屬性callee,它也也是一個指標,指向擁有這個arguments對象的函數,這個屬性在我們遞迴中相當有用,比直接的函數名易維護且健壯。
function myfuc(arg) { if (arg <= 1) { return 1; } else { return arg + myfuc(arg - 1); } } function anfuc(arg) { if (arg <= 1) { return 1; } else { return arg + arguments.callee(arg - 1); } }
這兩個函數雖然寫法上有點不一樣,但是是相同的目的,在這裡強烈推薦採用第二種:在任何時候你想修改該函數名,只需要修改聲明即可,同時看下邊的例子。
var myfuc2 = myfuc; var anfuc2 = anfuc; myfuc = null; anfuc = null; alert(anfuc2(3)); // 彈出6 alert(myfuc2(3)); // 沒彈出,並報錯
myfuc2的調用中,遞迴時還去尋找myfuc調用,但是早已“物是人非”了,而anfuc2裡arguments.callee始終指向當前調用的函數,也就是anfuc2。
this屬性,函數執行時,指向其執行所在的範圍,正確的理解它的意義是很重要的。
var name = "window"; function sayName() { alert(this.name); } var o = new Object(); o.name = "object"; o.sayName = sayName; sayName(); // 彈出window o.sayName(); //彈出 object
儘管sayName和o.sayName指向同個對象,但是他們的調用確輸出了不一樣的結果。我們知道,我們聲明的全域變數和函數是屬於window的,其實相當於window.name,window.sayName,而這個this動態指向了擁有該方法的對象。
函數的屬性和方法
剛剛介紹的是在函數休內才能使用的屬性,而既然函數是對象,那麼它肯定有可供我們使用的屬性和方法。
length屬性:函式宣告時希望接受到參數的個數。
prototype屬性:這個。。這個。。這個是js強大所在,它不僅能讓js比較完美的實現“類”、具有一些物件導向語言的一些特性如繼承等,更能擴充內建對象所不具備的功能和屬性。
apply方法和call方法:這兩個方法都是讓函數“自己調用自己”的,只不過傳參方式不一樣而已。
沒有重載的重載
js是沒有重載的,試圖重載都會覆蓋有前一個的函式宣告,不過通過js的特性,我們可以實作類別似重載的一些特性。由於函數是對象,聲明不過是指向該對象的指標而已,因些js在定位函數時只會根據函數名,根本不會考慮你傳的參數,因此我們可以用以下兩種方式實作類別似重載,這兩種方式本質是一樣的,只是表現形式上的差別。
1,arguments
function sayHi() { var result = "default"; if (arguments[0]) { result = arguments[0]; } if (arguments[1]) { result += arguments[1]; } if (arguments[2]) { result += arguments[2]; } if (arguments[3]) { result += arguments[3]; } // 此處你甚至可以遍曆arguments來支援無限的參數 return result; } alert(sayHi("a")); // a alert(sayHi("a", "b")); // ab alert(sayHi("a","b","c")); //abc
從調用上來看,是不是實現了重載。
2,聲明所有參數
function sayHi(a,b,c/*還可以更多*/) { var result = "default"; if (a) { result = a; } if (b) { result += b; } if (c) { result += c; } // 如果還有可以支援更多 return result; }
調用是一樣的,結果也是一樣的。
第一種方式更靈活,第二種則更易讀,對調用者來說更友好。推薦一般參數不多的話,使用第二種,如果要支援無限參數,只有採用第一種。極度不推薦兩種混用,那會讓代碼結非常亂,維護和以後擴充都會有不少麻煩。
函數的建立方式
一般來說我們有兩種方式建立函數:
1,函式宣告方式:function func(){}
2,函數運算式:var func = function(){}
這兩種難道就只是表現形式的差別嗎??當然不是,解析器在解析js時,會優先處理函式宣告,會在所有任何需要執行的代碼之前讓函數可用;而函數運算式只有在執行到所有行時,才會被解析執行。
也就是說:
sayHi(); function sayHi() { alert("hi"); }
這樣的方式,代碼是沒問題的,並且會彈出hi.
但是
sayHi(); var sayHi = function() { alert("hi"); }
這種方式,會報錯,缺少對象。
匿名函數和閉包
匿名函數也稱做lambda函數,一般情況下,我們會在以下幾種情況下會用到匿名函數:
1,回呼函數
2,函數做為傳回值
3,外掛程式形式的開發
如我們經常看到的如:
(function(){// 代碼})();(function($){// 代碼})(jQuery);
第二個是jquery推薦的外掛程式開發的方式,這裡聲明一個匿名函數,然後立即執行。函數內部聲明變數和函數,除了綁定給全域變數的一些變數和函數,我們在外部是不能訪問,起到隔離性的作用,不會對頁面造成影響,內部沒有閉包的存在,函數執行完畢後,變數函數就會被回收。
很多的匿名函數往往能看到閉包的身影,閉包又是什嗎?
看一個例子:
function createFunc(){var name = "123";return function(){alert(name);};}var func = createFunc();func(); // 彈出123
這是一個閉包的簡單例子,createFunc返回了一個匿名函數,一般情況下,一個函數執行完畢後,其內部變數就會被回收,但是createFunc返回的函數裡卻保持著對內部變數name的引用,因些我們在外部調用然後執行func,仍然可以使用,只要func存在,我們執行createFunc時所建立的內部變數就會處於記憶體中的一個位置,直到我們顯示的設定func = null,這次createFunc()建立的name才會被回收。
閉包就是有權訪問另一個函數範圍變數的函數。它會讓另一個函數內部的變數仍然處理有效狀態,這有時會為我們帶來莫大的方便,但是我們有時可能要考慮閉包的使用:就是因為閉包的存在,函數執行完畢後,其內部所有的變數就不能被立即回收,仍然處於記憶體中。
js的函數即獨特又很重要,用其它語言的方式使用js的函數,即不能體現出它的強大,有時還可能陷入困惑。它也使js強大,使得js具備一些封裝、繼承等進階特性,更有利於我們開發健壯、易維護的應用。